You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 11:24:01