Android:在onPostExecute()中得到了CalledFromWrongThreadException - 它怎么可能?

时间:2012-05-03 06:19:12

标签: android

我有一个应用程序在生产中使用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("");
    }
}

从处理程序调度消息(UI thead)调用addInstructionsIfNeeded()的位置。

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

我的问题是:它怎么可能?

修改: 这一切都发生在片段

6 个答案:

答案 0 :(得分:44)

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

正在发生的事情:

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

- 旁注 - 我在这个答案的最宽松的句子中使用术语“UI线程”,因为当人们说“UI线程”时,他们通常表示主要或入口线程, Android就像之前的许多其他操作系统一样,允许多个消息泵(在Android中称为Looper,请参阅: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)在我的特定情况下缓解我的“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交互的任何线程,例如多线程)在这种情况下)这是来自机器人团队的伪劣工艺和伪劣文件......弱酱,酱汁很弱

希望这可以帮助你 -

答案 1 :(得分:10)

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

简要解释:

  1. 使用looper在非UI 线程上运行AsynckTask 第一次导致将AsyncTask.class和初始化 sHandler 加载到构造的处理程序上非UI looper。
  2. 现在 sHandler 连接到非UI 线程,用于 ANY AsyncTask子类实例和 onPreExecute ,<将在非UI 线程上调用strong> onProgressUpdate onPostExecute 方法(除非将卸载AsyncTask.class)
  3. 任何在上述任何方法中处理UI的尝试都会导致 android.view.ViewRootImpl $ CalledFromWrongThreadException
  4. 崩溃
  5. 要避免这种情况,应该始终运行(至少第一次 UI线程上的AsyncTask,以便让AsyncTask的 sHandler -field可以使用 UI的循环进行初始化
  6. 故事:

    有两个制作应用: A - 主要Android应用和 B - 一些实用应用。

    在集成应用 B ito app 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线程规则部分)中的相关文档:

      

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

    希望能帮助明确这种情况)

答案 2 :(得分:9)

也许原因是乱舞? 当我使用Flurry 3.2.1 时,我有这个例外。但当我回到Flurry 3.2.0 时,我没有这个例外

使用乱舞 3.2.2 及以上。

答案 3 :(得分:3)

在Application onCreate中放置以下代码行可以解决问题:

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

似乎问题是在AsyncTask类首次在另一个主线程上启动时创建的,这个主线程不是我们的主线程,我通过在底部添加代码来检查它,我的应用程序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中崩溃,后者将在后执行时执行任何UI。与CalledFromWrongThreadException崩溃

希望它能更清楚一点。

感谢所有人给予的大力帮助。

答案 4 :(得分: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());
                   }
                }

            };

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

答案 5 :(得分:0)

我认为问题出在第Activity a = getActivity();行我认为你应该在进入AsyncTask之前做到这一点