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

Android中Retrofit2请求后操作SQLite出现APP_SCOUT_HANG警告

解决Retrofit回调中SQLite操作导致的APP_SCOUT_HANG警告

问题背景

使用Retrofit2调用服务器接口获取数据,在onResponse回调内检查数据库版本:若版本更新则删除重建SQLite表,随后批量插入数据。数据可正常填充,但控制台持续触发APP_SCOUT_HANG警告,栈信息指向insertData方法。

相关代码片段

@Override
public void onResponse(@NonNull Call<List<Movie>> call, @NonNull Response<List<Movie>> response) {
    movieList = response.body();
    myDb = new DatabaseHelper(getApplicationContext());

    if (movieList != null) {
        if (response.isSuccessful() && movieList.size() > 0) {
            int liveVersion = 0;
            liveVersion = Integer.parseInt(movieList.get(0).getVersiondb());

            if (liveVersion > version || version == 0) { 
                myDb.clearTable();
                for (int i = 0; i < movieList.size(); i++) {
                    myDb.insertData(movieList.get(i).getKateg(),movieList.get(i).getFullname(), movieList.get(i).getVersiondb());
                }
            }
        } 
    } 
}

void clearTable() {
    SQLiteDatabase db = this.getWritableDatabase();
    db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
    db.execSQL("CREATE TABLE " + TABLE_NAME + "(ID INTEGER PRIMARY KEY AUTOINCREMENT, KATEG TEXT, FULLNAME TEXT, VERSION TEXT)");
    db.close();
}

void insertData(String kateg, String fullname, String version) {
    SQLiteDatabase db = this.getWritableDatabase();

    ContentValues contentValues = new ContentValues();
    contentValues.put(COL_2,kateg);
    contentValues.put(COL_3,fullname);
    contentValues.put(COL_4,version);

    db.insert(TABLE_NAME,null,contentValues);
    db.close();
}

警告信息

(Current message: duration=5005ms seq=39 h=android.os.Handler c=retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1$$ExternalSyntheticLambda0)
MIUIScout App myapp.com W  Event:APP_SCOUT_HANG Thread:main backtrace:
at java.lang.String.charAt(Native Method)
at java.lang.String.equals(String.java:1271)
at android.database.DatabaseUtils.getSqlStatementType(DatabaseUtils.java:1574)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:1050)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:654)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:590)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:62)
at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:34)
at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1700)
at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1571)
at my.app.DatabaseHelper.insertData(DatabaseHelper.java:99)
at my.app.StartActivity$1.onResponse(StartActivity.java:84)
at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1.lambda$onResponse$0$retrofit2-DefaultCallAdapterFactory$ExecutorCallbackCall$1(DefaultCallAdapterFactory.java:89)
at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1$$ExternalSyntheticLambda0.run(Unknown Source:6)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:210)
at android.os.Looper.loop(Looper.java:299)
at android.app.ActivityThread.main(ActivityThread.java:8319)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:556)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1038)

问题原因

  1. 主线程阻塞:Retrofit默认回调运行在主线程,而SQLite表重建、循环插入属于耗时IO操作。当数据量较大时,主线程阻塞时间超过5秒(警告显示duration=5005ms),触发MIUI的APP_SCOUT_HANG卡顿检测。
  2. 数据库连接频繁开关:每次调用insertData都打开/关闭SQLite连接,频繁的IO操作进一步加重主线程负担,延长阻塞时间。

解决方案

1. 将数据库操作移至子线程

把耗时的数据库操作从主线程剥离,使用ExecutorService执行:

// 定义全局单线程执行器,避免线程泛滥
private ExecutorService dbExecutor = Executors.newSingleThreadExecutor();

@Override
public void onResponse(@NonNull Call<List<Movie>> call, @NonNull Response<List<Movie>> response) {
    movieList = response.body();
    myDb = new DatabaseHelper(getApplicationContext());

    if (movieList != null && response.isSuccessful() && movieList.size() > 0) {
        int liveVersion = Integer.parseInt(movieList.get(0).getVersiondb());
        if (liveVersion > version || version == 0) {
            // 子线程执行数据库操作
            dbExecutor.execute(() -> {
                myDb.clearTable();
                myDb.bulkInsertMovies(movieList);
                // 若需更新UI,切换回主线程
                runOnUiThread(() -> {
                    // 比如刷新列表、提示更新完成
                });
            });
        }
    }
}

2. 优化批量插入逻辑

新增批量插入方法,使用SQLite事务减少IO次数,复用ContentValues和数据库连接:

// DatabaseHelper类中新增批量插入方法
void bulkInsertMovies(List<Movie> movieList) {
    SQLiteDatabase db = this.getWritableDatabase();
    db.beginTransaction(); // 开启事务
    try {
        ContentValues contentValues = new ContentValues();
        for (Movie movie : movieList) {
            contentValues.put(COL_2, movie.getKateg());
            contentValues.put(COL_3, movie.getFullname());
            contentValues.put(COL_4, movie.getVersiondb());
            db.insert(TABLE_NAME, null, contentValues);
            contentValues.clear(); // 复用ContentValues,减少对象创建
        }
        db.setTransactionSuccessful(); // 标记事务成功
    } finally {
        db.endTransaction(); // 结束事务,自动提交/回滚
        db.close();
    }
}

3. 移除原循环插入逻辑

删除onResponse中的循环调用insertData的代码,改用上述bulkInsertMovies方法。

效果说明

  • 子线程执行数据库操作,彻底避免主线程阻塞,消除APP_SCOUT_HANG警告。
  • 事务+批量插入将多次IO操作合并为一次,大幅提升数据插入效率,减少系统资源消耗。

内容的提问来源于stack exchange,提问作者Darksymphony

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:28:13