【问题标题】:Android: got CalledFromWrongThreadException in onPostExecute() - How could it be?Android:在 onPostExecute() 中得到 CalledFromWrongThreadException - 怎么可能?
【发布时间】:2012-05-03 06:19:11
【问题描述】:

我有一个使用 ACRA 的应用已经投入生产了几周,在今天报告了一个奇怪的错误之前,我的错误为零。

我有:

    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

来自堆栈跟踪中的此方法(已追溯):

at my.app.CountdownFragment$1.void onPostExecute(java.lang.Object)(SourceFile:1)

这是相关的来源sn-p:

    private void addInstructionsIfNeeded() {
    if (S.sDisplayAssist) {
        new AsyncTask<String, Void, String>() {

            @Override
            protected String doInBackground(String... params) {
                return null;
            }

            /*
             * runs on the ui thread
             */
            protected void onPostExecute(String result) {

                Activity a = getActivity();

                if (S.sHelpEnabled && a != null) {

                    in = new InstructionsView(a.getApplicationContext());

                    RelativeLayout mv = (RelativeLayout) a
                            .findViewById(R.id.main_place);

                    mv.addView(in.prepareView());
                }

            };

        }.execute("");
    }
}

addInstructionsIfNeeded() 是从处理程序调度的消息(UI 主题)中调用的。

  • onPostExecute() 在 UI 线程上运行,为什么我有“错误的线程”?
  • 此代码已在 150 多台设备上运行,超过 100000 次(根据 Flurry),从未出现此错误。
  • 原始设备是运行 SDK 4.0.4 的三星 SGH-I997

我的问题是:怎么可能?

编辑: 这一切都发生在一个片段中

【问题讨论】:

  • 问题出在Activity a = getActivity(); 行中,您应该在进入 AsyncTask 之前执行此操作。您应该使用类构造函数进行此类初始化
  • 如果我在成员变量中保留对活动的引用,在某些情况下,它不会使过时的活动对象保持活动状态,从而导致内存泄漏吗? (直到内部类实例死亡)

标签: android


【解决方案1】:

我遇到了同样的问题,这是另一个 android 框架错误...

发生了什么:

在某些情况下,应用程序可以有多个“循环器”,因此可以有多个“UI 线程”

--旁注-- 我在这个答案中使用了“UI 线程”这个术语,因为当人们说“UI 线程”时,他们通常指的是主线程或入口线程, Android 与之前的许多其他操作系统一样,允许多个消息泵(在 Android 中称为 Looper,请参阅:http://en.wikipedia.org/wiki/Event_loop)用于不同的 UI 树,因此适用于所有意图和目的的 android 能够运行多个在某些情况下使用“UI 线程”并使用该术语会导致模棱两可... --end side note--

这意味着:

由于一个应用程序可以有多个“UI 线程”和一个 AsyncTask 始终“在 UI 线程上运行”[ref],因此有人决定 [糟糕地] 而不是 AsyncTask 始终在其创建线程上运行(在 99.999999% 的情况下将是正确的“UI 线程”)他们决定使用 hocus pocus(或制作不佳的快捷方式,由您决定)在“主循环器”上执行..

示例:

    Log.i("AsyncTask / Handler created ON: " + Thread.currentThread().getId());
    Log.i("Main Looper: " + Looper.getMainLooper().getThread().getId() + "      myLooper: "+ Looper.myLooper().getThread().getId());

    new AsyncTask<Void, Void, Void>() {

        @Override
        protected Void doInBackground(Void... params) {
            Log.i("doInBackground ran ON: " + Thread.currentThread().getId());
            // I'm in the background, all is normal

            handler.post(new Runnable() {

                @Override
                public void run() {
                    Log.i("Handler posted runnable ON: " + Thread.currentThread().getId());
                    // this is the correct thread, that onPostExecute should be on
                }
            });

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            Log.i("onPostExecute ran ON: " + Thread.currentThread().getId());
            // this CAN be the wrong thread in certain situations
        }

    }.execute();

如果从上述不良情况调用,输出将如下所示:

    AsyncTask / Handler created ON: 16
    Main Looper: 1      myLooper: 16
    doInBackground ran ON: 12
    onPostExecute ran ON: 1
    Handler posted runnable ON: 16

这对于AsyncTask来说是一个巨大的失败

如图所示,在我的具体情况下,可以使用Handler.post(Runnable) 缓解这种情况,我的“UI 线程”情况的双重性是由于我正在创建一个对话框以响应从 @987654330 调用的 JavaScript 接口方法@,基本上:WebView 有自己的“UI 线程”,这就是我目前正在运行的那个......

据我所知(没有真正关心或阅读太多)似乎AsyncTask 类的回调方法通常会运行单个静态实例化处理程序(请参阅:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.3_r1/android/os/AsyncTask.java#AsyncTask.0sHandler),这意味着它总是会在他们错误地称为“UI线程”的“主线程”或“入口线程”上执行(假定为发生UI交互的任何线程,例如在这种情况下为多个线程)这是安卓团队的劣质工艺和劣质文件......弱酱,酱弱

希望这对你有帮助-ck

【讨论】:

  • 感谢分享您的研究!这很有趣。
  • 哇。我为“谢谢”垃圾评论道歉,但真的,谢谢。就我而言,我在快捷方式创建活动中运行了一个异步任务,它失败了会出现各种消息/处理程序相关的错误。我使用执行器 + 处理程序代替我的异步任务,一切都很好。
  • 你知道为什么WebView有自己的线程池吗?在这种情况下,这似乎不完全是 Asynctask 的错 - 您代码中的实际错误是由于 webview 的回调在错误的 UI 线程上引起的。
  • @DavidT。那么回调将异步发生,例如。由于 WebView 在您的进程之外运行,因此没有同步。实现是否可以在适当的处理程序上发布消息,以便在启动 UI 线程上发生回调?当然可以,但它会减慢进程并不允许您从回调返回到 JavaScript,这目前是可行的并且非常有用。所以不行。 JavaScript 界面是正确的并且行为可以理解。
  • @DavidT。 myLooper 是 WebView 中的循环器,在我的场景中运行在 16 上(一年多前在真实设备上)。我建议你开始一个新的 SO 问题,我会看看我是否可以帮助你更多,这个评论部分太长了......
【解决方案2】:

有同样的问题。在我的情况下解决了

简要说明:

  1. 第一次非 UI 线程上使用 looper 运行 AsyncTask.class 并初始化 sHandler 到构造的处理程序那个非用户界面looper。
  2. 现在 sHandler 已连接到 非 UI 线程,用于 AsyncTask 子类的 ANY 实例和 onPreExecute,onProgressUpdate 和 onPostExecute 方法将在该非 UI 线程上调用(除非 AsyncTask.class 将被卸载)
  3. 在上述任何方法中处理 UI 的任何尝试都将导致 android.view.ViewRootImpl$CalledFromWrongThreadException 崩溃
  4. 为避免这种情况,应始终UI 线程上运行(至少第一次)AsyncTask,以便让 AsyncTask 的sHandler-用UI的looper初始化字段

故事:

有两个生产应用:A - 主要的 android 应用和 B - 一些实用应用。

在集成应用 B ito 应用 A 后,我们收到了很多崩溃:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

对于从 AsynckTask.onPostExecute()

运行的方法

经过一番调查,实用程序 B 似乎在其 HandlerThread

中使用了 AsyncTask

在 AsyncTask 的源代码中找到了踪迹:

private static final InternalHandler sHandler = new InternalHandler();

这是用于将 onPostExecute() 发送到 UI 线程的处理程序。

这个处理程序是静态的,它将在类加载期间被初始化,即第一次出现new AsyncTask()

这意味着 onPostExecute 将始终发布到第一次调用 new AsyncTask() 的线程(除非 AsyncTask.class 将被卸载并再次加载)

就我而言,流程是这样的:

1 - starting app A
2 - initializing B form A
3 - B creates its own HandlerThread and launches AsyncTask <- now onPostExecute wil be posted to this HandlerThread no matter where from an instance of AsyncTask will be launched in future
4 - create AsyncTask in the app A for a long operation and update UI in its onPostExecute
5 - when executing onPostExecute() the CalledFromWrongThreadException is thrown

然后我的一个朋友给我看了android.developers的相关文档(线程规则部分):

必须在 UI 线程上加载 AsyncTask 类。这个完成了 从 JELLY_BEAN 自动开始。任务实例必须创建于 用户界面线程。必须在 UI 线程上调用 execute(Params...)。

希望能帮助弄清楚情况)

【讨论】:

  • 无限感谢,尽管不应该如此,但您的解释是完美的。我只是设置了一个默认的空白 AsyncTask 并从 onUiThread 可运行的内部执行它。
【解决方案3】:

也许是乱舞? 我在使用 Flurry 3.2.1 时遇到了这个异常。但是当我回到 Flurry 时 3.2.0 我没有这个例外

使用 Flurry 3.2.2 及更高版本。

【讨论】:

  • 同样,在切换到较旧的 jar 之前,我一遍又一遍地遇到问题。
  • 同样使用 Flurry 3.2.1。在调用 Flurry 之前只运行一次空的 AsyncTask 即可解决问题。
  • Hai Anivaler,我也有同样的问题。你能有 Flurry 3.2.0.jar 来解决我的崩溃吗?
  • 是的。我有。给我写电子邮件。
  • 这已在 Flurry 3.2.2 中修复,该版本于 8 月 26 日发布。
【解决方案4】:

在应用程序 onCreate 中放置以下代码行应该可以解决问题:

     /**
     * Fixing AsyncTask Issue not called on main thread
     */
    try {
        Class.forName("android.os.AsyncTask");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

似乎问题是在 AsyncTask 类首次在不是我们的主线程的不同主线程上启动时创建的,我通过将底部的代码添加到我的 Application onCreate 来检查它

    new Thread(new Runnable() {


        @Override
        public void run() {

            Log.i("tag","1.3onPostExecute ran ON: " + Thread.currentThread().getId());
            Looper.prepare();
            new AsyncTask<Void,Void,Void>(){
                @Override
                protected Void doInBackground(Void... params) {
                    Log.i("tag","2onPostExecute ran ON: " + Thread.currentThread().getId());
                    return null;
                }

                @Override
                protected void onPostExecute(Void aVoid) {
                    Log.i("tag","1.2onPostExecute ran ON: " + Thread.currentThread().getId());
                    super.onPostExecute(aVoid);
                }
            }.execute();
            Looper.loop();
            Looper.myLooper().quit();
        }
    }).start();

此代码将在不是应用程序主线程的主线程中初始化 AsynTask,并将导致应用程序在任何其他 AsyncTask 中崩溃,该 AsyncTask 将在执行后执行任何 UI。因 CalledFromWrongThreadException 而崩溃

希望它更清楚一些事情。

感谢大家对此的大力帮助。

【讨论】:

  • 你能解释一下为什么这条线会有帮助吗? (最好通过编辑答案)。谢谢!
  • 感谢您的编辑,我了解到您强制在主线程中实例化 AsyncTask 类,然后再在线程中创建它。我认为您可以通过在主线程上创建 AsyncTask 的实际实例来实现相同的效果,然后从线程中调用执行。但是我的 +1 仍然......
  • 我同意这会起作用,实际上对我来说重要的是测试解决方案,现在我知道它有效,谢谢!
【解决方案5】:

在哪里

runOnUiThread(new Runnable() {
    public void run() { /*code*/ } );

在您的代码中

/*
             * runs on the ui thread
             */
            protected void onPostExecute(String result) {

                Activity a = getActivity();

                if (S.sHelpEnabled && a != null) {

                    in = new InstructionsView(a.getApplicationContext());

                    runOnUiThread(new Runnable() {
                       public void run() {

                    RelativeLayout mv = (RelativeLayout) a
                            .findViewById(R.id.main_place);

                    mv.addView(in.prepareView());
                   }
                }

            };

试试这个代码。我认为这可以解决问题

【讨论】:

  • DoesnPostExecute() always 无论如何都在 UI 线程上运行? (所以 runOnUiThread 将是一个冗余)
【解决方案6】:

我认为问题在于Activity a = getActivity(); 这行我认为你应该在进入 AsyncTask 之前这样做

【讨论】:

  • 感谢您的想法。我不确定它是否相关,因为 getActivity() 没有操纵 UI。它曾经做过一次(在某些极端情况测试中)返回 null,这就是我在继续之前检查它的原因。
猜你喜欢
  • 1970-01-01
  • 2022-07-10
  • 2012-03-22
  • 1970-01-01
  • 2012-04-07
  • 1970-01-01
  • 1970-01-01
  • 2023-03-30
相关资源
最近更新 更多