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)
问题原因
- 主线程阻塞:Retrofit默认回调运行在主线程,而SQLite表重建、循环插入属于耗时IO操作。当数据量较大时,主线程阻塞时间超过5秒(警告显示
duration=5005ms),触发MIUI的APP_SCOUT_HANG卡顿检测。 - 数据库连接频繁开关:每次调用
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
相关产品推荐
相关产品推荐

