24

我有一个应用程序在生产中使用了几个星期,使用 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)

这是相关的源代码片段:

    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("");
    }
}

从处理程序调度的消息( UIaddInstructionsIfNeeded()主题)调用的位置。

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

我的问题是:怎么可能?

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

4

6 回答 6

45

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

怎么了:

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

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

这表示:

由于一个应用程序可以有多个“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)在我的特定情况下,可以使用 a 来缓解这种情况,我的“UI 线程”情况的双重性是由于我正在创建一个对话框以响应从 a 调用的 JavaScript 接口方法WebView,基本上: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 交互的任何线程,例如在这种情况下为多个线程),这既是 android 团队的劣质工艺和劣质文档......弱酱,酱弱

希望这对你有帮助-ck

于 2012-05-21T14:55:46.660 回答
10

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

简要说明:

  1. 第一次在带有 looper 的非 UI线程上运行 AsynckTask会导致将 AsyncTask.class 和初始化sHandler加载到在该非 UI looper 上构造的处理程序。
  2. 现在sHandler连接到AsyncTask 子类的任何实例的非 UI线程,并且将在该非 UI线程上调用onPreExecuteonProgressUpdateonPostExecute方法(除非 AsyncTask.class 将被卸载)
  3. 在上述任何方法中处理 UI 的任何尝试都将导致android.view.ViewRootImpl$CalledFromWrongThreadException崩溃
  4. 为了避免这种情况,应该始终在UI 线程上运行(至少是第一次)AsyncTask ,以便让 AsyncTask 的sHandler -field 使用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 线程的处理程序。

这个处理程序是静态的,它将在类加载期间被初始化,即第一个新的 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的相关文档(线程规则部分):

AsyncTask 类必须在 UI 线程上加载。这是从 JELLY_BEAN 自动完成的。任务实例必须在 UI 线程上创建。必须在 UI 线程上调用 execute(Params...)。

希望它可以帮助弄清楚情况)

于 2013-04-26T14:39:32.230 回答
9

也许原因是Flurry?我在使用 Flurry 3.2.1时遇到了这个异常。但是当我回到 Flurry 3.2.0时,我没有这个异常

使用 Flurry 3.2.2及更高版本。

于 2013-07-17T10:09:41.240 回答
3

将以下代码行放在 Application 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 而崩溃

希望它能清除更多的东西。

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

于 2014-05-18T12:42:35.210 回答
1

哪里

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());
                   }
                }

            };

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

于 2012-05-03T06:27:13.197 回答
0

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

于 2012-05-03T06:22:18.850 回答