Android如何使用已封装的PostgreSQL数据库类执行SQL查询并解决主线程卡顿
问题根因
- 卡顿的核心原因是
Thread.join()方法会阻塞调用它的线程:你在主线程调用db.load(),load方法启动子线程后直接执行thread.join(),等于主线程必须等数据库操作完全完成才能继续运行,自然会出现掉帧警告。 - 仅能成功运行一次的原因是你把
con.close()写在了while(rs.next())循环内部,第一次遍历结果集就会关闭数据库连接,第二次循环读取结果时连接已经释放,直接抛出异常。 - 额外问题:每次点击加载按钮都新建
Database实例,每次都会重新创建数据库连接,既浪费资源也容易出现连接泄漏。
修复步骤
1. 新增实体类存储查询结果
先新建Inventory类统一存储查询出来的库存数据:
public class Inventory { private int id; private String description; private String amount; private String local; // 生成对应字段的getter、setter方法即可 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getAmount() { return amount; } public void setAmount(String amount) { this.amount = amount; } public String getLocal() { return local; } public void setLocal(String local) { this.local = local; } }
2. 改造Database类的load方法
新增回调接口传递结果,去掉join()阻塞逻辑,修复资源关闭顺序:
public class Database { // 原有代码保持不变,新增查询回调接口 public interface QueryCallback { void onSuccess(List<Inventory> data); void onFail(String errorMsg); } // 改造后的load方法 public void load(QueryCallback callback) { Thread thread = new Thread(new Runnable() { @Override public void run() { Connection con = null; Statement stmt = null; ResultSet rs = null; List<Inventory> resultList = new ArrayList<>(); try { Class.forName("org.postgresql.Driver"); con = DriverManager.getConnection(url, user, pass); stmt = con.createStatement(); rs = stmt.executeQuery("SELECT * FROM INVENTORY"); while (rs.next()){ Inventory item = new Inventory(); item.setId(rs.getInt("ID")); item.setDescription(rs.getString("DESCRIPTION")); item.setAmount(rs.getString("AMOUNT")); item.setLocal(rs.getString("LOCAL")); resultList.add(item); } // 查询成功,切换到主线程回调结果 new Handler(Looper.getMainLooper()).post(() -> callback.onSuccess(resultList)); } catch (Exception e) { e.printStackTrace(); // 查询失败,切换到主线程回调错误 new Handler(Looper.getMainLooper()).post(() -> callback.onFail(e.getMessage())); } finally { // 资源倒序关闭,放在finally块保证不管成功失败都会释放 try { if (rs != null) rs.close(); } catch (Exception e) { e.printStackTrace(); } try { if (stmt != null) stmt.close(); } catch (Exception e) { e.printStackTrace(); } try { if (con != null) con.close(); } catch (Exception e) { e.printStackTrace(); } } } }); thread.start(); // 完全删除原来的thread.join()相关代码,不要阻塞主线程 } // 原有其他代码保持不变 }
3. 优化MainActivity调用逻辑
全局复用Database实例,通过回调接收结果更新UI:
class MainActivity : AppCompatActivity() { // 全局只初始化一次Database实例 private val db by lazy { Database() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } override fun onOptionsItemSelected(item: MenuItem): Boolean { val id = findViewById<EditText>(R.id.ID) return when (item.itemId) { R.id.load -> { db.load(object : Database.QueryCallback { override fun onSuccess(data: List<Inventory>) { // 主线程直接更新UI,比如给列表设数据、打印日志 Toast.makeText(this@MainActivity, "查询成功,共${data.size}条数据", Toast.LENGTH_SHORT).show() data.forEach { Log.d("库存数据", "ID:${it.id} 描述:${it.description} 数量:${it.amount} 位置:${it.local}") } } override fun onFail(errorMsg: String?) { // 主线程提示错误 Toast.makeText(this@MainActivity, "查询失败:$errorMsg", Toast.LENGTH_SHORT).show() } }) true } else -> super.onOptionsItemSelected(item) } } }
额外优化建议
- 确认AndroidManifest.xml已经申请INTERNET权限,否则会出现连接失败问题:
<uses-permission android:name="android.permission.INTERNET" /> - 可以复用已有的数据库连接,不用每次查询都新建连接,减少网络开销
- 后续复杂异步逻辑可以换成Kotlin协程实现,代码更简洁易维护
内容的提问来源于stack exchange,提问作者Igor Lima
相关产品推荐
相关产品推荐

