在 IntentService 和 AsyncTask (Android) 之间使用共享代码时,领域“从不正确的线程访问”错误

Posted

技术标签:

【中文标题】在 IntentService 和 AsyncTask (Android) 之间使用共享代码时,领域“从不正确的线程访问”错误【英文标题】:Realm `access from incorrect thread` error when using shared code between IntentService and AsyncTask (Android) 【发布时间】:2016-02-26 07:49:57 【问题描述】:

我有一些代码可以下载“当前”对象的 JSON。但是,每当警报响起时(当应用程序未运行任何 UI 时),IntentService 都需要调用相同的代码,并且在应用程序运行时也需要由 AsyncTask 调用。

但是,我收到一条错误消息,提示 Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created. 但是,我不明白此堆栈跟踪是如何或为什么会出现在不同的线程上的。

我能够通过复制所有共享代码并将其直接粘贴到 DownloadDealService 的 onHandleIntent 方法中来摆脱错误,但它非常草率,我正在寻找一个不需要重复代码的更好的解决方案.

如何在不重复代码的情况下消除此错误?谢谢。

public class DownloadDealService extends IntentService

    ...
    @Override
    protected void onHandleIntent(Intent intent)
    
        Current todaysCurrent = Utils.downloadTodaysCurrent(); //<--- included for background info
        String dateString = Utils.getMehHeadquartersDate(); //(omitted)
        Utils.onDownloadCurrentCompleteWithAlarm(todaysCurrent, dateString); //<------ calling this...
    


public class Utils

    // ...other methods ommitted...

    //This method is not in the stack trace, but I included it for background information.
    public static Current downloadTodaysCurrent()
    
        //Set up Gson object... (omitted)
        //Set up RestAdapter object... (omitted)
        //Set up MehService class... (omitted)

        //Download "Current" object from the internet.
        Current current = mehService.current(MehService.API_KEY);
        return current;
    

    //Included for background info- this method is not in the stack trace.
    public static void onDownloadCurrentComplete(Current result, String dateString)
    
        if(result.getVideo() == null)
        
            Log.e("HomePage", "Current was not added on TaskComplete");
            return;
        
        remainder(result, dateString);
    

    public static void onDownloadCurrentCompleteWithAlarm(Current result, String dateString)
    
        //Set alarm if download failed and exit this function... (omitted)

        remainder(result, dateString);//<------ calling this...
        Utils.sendMehNewDealNotification(App.getContext());
    

    public static void remainder(Current result, String dateString)
    
        Realm realm = RealmDatabase.getInstance();

        //Add "Current" to Realm
        Current current = Utils.addCurrentToRealm(result, realm); //<------ calling this...
    

    public static Current addCurrentToRealm(Current current, Realm realm)
    
        realm.beginTransaction(); //<---- Error is here
        Current result = realm.copyToRealmOrUpdate(current);
        realm.commitTransaction();
        return result;
    

堆栈跟踪:

E/androidRuntime: FATAL EXCEPTION: IntentService[DownloadDealService]
Process: com.example.lexi.meh, PID: 13738
java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
    at io.realm.Realm.checkIfValid(Realm.java:191)
    at io.realm.Realm.beginTransaction(Realm.java:1449)
    at com.example.lexi.meh.Utils.Utils.addCurrentToRealm(Utils.java:324)
    at com.example.lexi.meh.Utils.Utils.remainder(Utils.java:644)
    at com.example.lexi.meh.Utils.Utils.onDownloadCurrentCompleteWithAlarm(Utils.java:635)
    at com.example.lexi.meh.Home.DownloadDealService.onHandleIntent(DownloadDealService.java:42)
    at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:136)
    at android.os.HandlerThread.run(HandlerThread.java:61)

我有一个 AsyncTask 也调用其中一些 Utils 方法:

public class DownloadAsyncTask extends AsyncTask<Void, Integer, Current>

    // ... (more methods ommitted)...

    protected Current doInBackground(Void... voids)
    
        return Utils.downloadTodaysCurrent(); //<---- shared Utils method
    


//Async class's callback in main activity:
public class HomePage extends AppCompatActivity implements DownloadAsyncTaskCallback, DownloadAsyncTaskGistCallback<Current, String>

    // ... (more methods ommitted)...

    public void onTaskComplete(Current result, String dateString)
    
        Utils.onDownloadCurrentComplete(result, dateString);
    

【问题讨论】:

【参考方案1】:

[更新]基于附加信息

RealmDatabase.getInstance() 正在返回在主线程上创建的 Realm 实例。这个实例被用在了 IntentService 的线程上。这导致了崩溃。

Realm 实例不能在除创建它们的线程之外的任何其他线程上使用。


你不能在线程之间传递 Realm 对象。您可以做的是传递对象的唯一标识符(即@PrimaryKey),然后在另一个线程上通过其 id 获取对象。像这样:realm.where(YourRealmModel.class).equalTo("primaryKeyVariable", id).findFirst()。

欲了解更多详情,请查看 Realm 的官方文档和示例:

跨线程使用领域https://realm.io/docs/java/latest/#threading 线程示例https://github.com/realm/realm-java/tree/master/examples/threadExample

【讨论】:

你能看看我的代码并告诉我我在线程之间传递了什么吗?我不明白。老实说,我以为我在同一个线程上。据我所知,我只在线程之间共享方法,没有在线程之间传递任何领域对象。 很难理解您的代码示例中的内容。但我假设您在 AsyncTask 的 doInBackround 中获取结果对象(在工作线程上执行),然后将其作为结果传递给 onPostExecute() (在主线程上执行)。然后尝试通过剩余()方法从那里访问它,这会炸毁你的应用程序。 我怀疑您正在对 RealmDatabase 类中的 Realm 实例进行一些自定义缓存。这导致您从与您获得它的原始线程不同的线程调用它。只需使用 Realm.getDefaultInstance()。 给你。 Realm 实例只能在它创建的线程上使用。在您的情况下,它是在主线程上创建的,但在 IntentService 线程中使用。 我能够通过在我的IntentService 中使用Realm.getInstance(this) 来解决该问题,并将其传递给Util 方法使用(因为这些方法无法访问线程上下文)。所以现在,Util 方法可以被多个线程使用,每个线程都有一个正确的 Realm 实例。【参考方案2】:

如果你在 IntentService 中使用 Realm,你将使用新的 Realm。例如

Realm.getInstance(RealmConfiguration config) 

我在 IntentService 中使用了 Realm

@Override
protected void onHandleIntent(Intent intent) 
    Realm realm = Realm.getDefaultInstance();
    ...
    realm.close(); // important on background thread

【讨论】:

【参考方案3】:

问题是你从不同的线程调用方法,你必须使用同一个线程,创建你自己的HandlerThread。

【讨论】:

以上是关于在 IntentService 和 AsyncTask (Android) 之间使用共享代码时,领域“从不正确的线程访问”错误的主要内容,如果未能解决你的问题,请参考以下文章

IntentService与Service的区别

android 中IntentService的使用场景

IntentService,用完即走

如何使用 Intentservice 获取位置信息?

IntentService 和 Activity 生命周期

Android IntentService