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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:45:04