Android Realm跨线程访问异常求助:用executeTransactionAsync仍报错
解决Realm跨线程访问异常的方案
先帮你理清问题根源:Realm的托管对象(包括从RealmResults中获取的AppItem实例)是和创建它们的线程绑定的——你在AsyncTask的doInBackground(后台线程)里获取的AppItem,拿到onPostExecute(主线程)里访问时,就会触发线程非法访问的异常,哪怕你用了executeTransactionAsync也没用,因为事务里操作的是另一个线程创建的Realm对象。
下面是具体的修复步骤和代码优化:
1. 优化IntentService中的存储逻辑
你之前手动创建并关闭Realm实例的方式存在问题,因为executeTransactionAsync本身会自动在后台线程管理Realm的打开与关闭,手动关闭可能导致事务执行时Realm已被释放。修改后的代码如下:
// 无需手动创建/关闭Realm实例,executeTransactionAsync会自动处理 Realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { for (AppItem item : items) { AppItem item2 = realm.createObject(AppItem.class, UUID.randomUUID().toString()); item2.mName = item.mName; item2.mCount = item.mCount; item2.mUsageTime = item.mUsageTime; // 注意:补充mPackageName的赋值(后面代码会用到这个字段) item2.mPackageName = item.mPackageName; } } });
2. 修复AsyncTask的跨线程对象访问问题
这里有两种可靠的解决方案:
方案一:将Realm托管对象转为非托管对象
在doInBackground中使用copyFromRealm把Realm中的数据复制为普通Java对象,脱离Realm的线程绑定限制:
// AsyncTask的doInBackground方法 @Override protected List<AppItem> doInBackground(Void... voids) { Realm backgroundRealm = Realm.getDefaultInstance(); try { RealmResults<AppItem> results = backgroundRealm.where(AppItem.class).findAll(); // 复制为非托管对象列表,可安全跨线程传递 return backgroundRealm.copyFromRealm(results); } finally { backgroundRealm.close(); } } // AsyncTask的onPostExecute方法 @Override protected void onPostExecute(List<AppItem> appItems) { super.onPostExecute(appItems); // 在事务中根据主键重新查询托管对象进行更新 Realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { for (AppItem item : appItems) { if (item.mUsageTime <= 0) continue; mTotal += item.mUsageTime; // 根据主键找到对应的托管对象 AppItem managedItem = realm.where(AppItem.class) .equalTo("id", item.getId()) // 假设你的主键字段是id .findFirst(); if (managedItem != null) { managedItem.mCanOpen = mPackageManager.getLaunchIntentForPackage(item.mPackageName) != null; } } } }); }
方案二:仅传递主键列表,在目标线程重新查询
如果数据量较大,传递主键列表比复制整个对象更高效:
// AsyncTask的doInBackground方法 @Override protected List<String> doInBackground(Void... voids) { Realm backgroundRealm = Realm.getDefaultInstance(); try { RealmResults<AppItem> results = backgroundRealm.where(AppItem.class).findAll(); // 提取所有对象的主键列表 List<String> itemIds = new ArrayList<>(); for (AppItem item : results) { itemIds.add(item.getId()); } return itemIds; } finally { backgroundRealm.close(); } } // AsyncTask的onPostExecute方法 @Override protected void onPostExecute(List<String> itemIds) { super.onPostExecute(itemIds); Realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { for (String itemId : itemIds) { AppItem managedItem = realm.where(AppItem.class) .equalTo("id", itemId) .findFirst(); if (managedItem == null || managedItem.mUsageTime <= 0) continue; mTotal += managedItem.mUsageTime; managedItem.mCanOpen = mPackageManager.getLaunchIntentForPackage(managedItem.mPackageName) != null; } } }); }
核心总结
Realm的核心规则是线程绑定:任何Realm实例、RealmResults、托管对象都只能在创建它们的线程中访问。跨线程传递数据时,要么用copyFromRealm转为普通对象,要么只传递主键等标识,在目标线程重新查询获取可操作的托管对象。
内容的提问来源于stack exchange,提问作者MrRobot9
相关产品推荐
相关产品推荐

