24

私は ACRA を使用して数週間アプリを運用していますが、今日 1 つの奇妙なエラーが報告されるまでエラーはありませんでした。

私が持っている:

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

addInstructionsIfNeeded()ハンドラーがディスパッチしたメッセージ (UI スレッド) から呼び出される場所。

  • onPostExecute()UIスレッドで実行されるのに、なぜ「間違ったスレッド」になったのですか?
  • このコードはすでに 150 を超えるデバイスで実行され、(Flurry によると) 100000 回以上実行されましたが、このエラーは発生しませんでした。
  • 元のデバイスは、SDK 4.0.4 を実行する Samsung SGH-I997 です。

私の質問は次のとおりです。

編集:これはすべてフラグメントで発生します

4

6 に答える 6

45

私は同じ問題に苦しんでいました。これは別のAndroidフレームワークのバグです...

何が起こっている:

特定の状況では、アプリケーションは複数の「ルーパー」、つまり複数の「UI スレッド」を持つことができます。

--補足--この回答では、「UIスレッド」という用語を最もゆるい意味で使用しています。「UIスレッド」と言うとき、通常、メインスレッドまたはエントリスレッドを意味し、Androidはそれ以前の他の多くのOSと同様に許可されているためです。複数のメッセージ ポンプ ( LooperAndroid では a と呼ばれます。http: //en.wikipedia.org/wiki/Event_loopを参照してください) のためのさまざまな UI ツリー用。そのような android は、すべての意図と目的で複数の「UI スレッドを実行できる」ためです。 」 特定の状況でその用語を使用すると、あいまいさが蔓延します... -- サイドノートの終わり--

これの意味は:

アプリケーションは複数の「UI スレッド」とAsyncTask常に「Runs on the UI thread」[ref]を持つことができるため、誰かが [貧弱に] AsyncTask の代わりにその作成スレッドで常に実行することを決定しました (99.999999% のケースでは正しい「UI スレッド」である必要があります) 彼らは「メイン ルーパー」で実行するためにホーカス ポーカス (または作成が不十分なショートカット) を使用することにしました..

例:

    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 スレッド」状況の二重性は、 から呼び出された JavaScript インターフェース メソッドに応答してダイアログを作成していたという事実によって引き起こされましたWebViewWebViewUIスレッド」であり、それは私が現在実行していたものでした..

私が言えることから(あまり気にしたり、読みすぎたりすることなく)、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

于 2012-05-21T14:55:46.660 に答える
10

同じ問題がありました。私の場合は解決しました

簡単な説明:

  1. ルーパーを使用して非 UIスレッドで初めてAsynckTask を実行すると、AsyncTask.classがロードされ、その非 UIルーパーで構築されたハンドラーにsHandlerが初期化されます。
  2. これで、 sHandlerはAsyncTaskサブクラスの任意のインスタンスの非 UIスレッドに接続され、 onPreExecuteonProgressUpdate、およびonPostExecuteメソッドがその非 UIスレッドで呼び出されます (AsyncTask.class がアンロードされない限り)。
  3. 上記のいずれかのメソッド内で UI を処理しようとすると、android.view.ViewRootImpl$CalledFromWrongThreadExceptionでクラッシュします。
  4. このような状況を回避するには、AsyncTask のsHandlerフィールドをUI のルーパーで初期化できるようにするために、 UI スレッドで AsyncTask を常に(少なくとも初めて) 実行する必要があります。

物語:

A - メインの Android アプリとB - ユーティリティ アプリの2 つのプロダクション アプリがありました。

アプリBをアプリ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()の出現です。

これは、 new AsyncTask()が初めて呼び出されたスレッドにonPostExecuteが常にポストされることを意味します (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 スレッドで作成する必要があります。execute(Params...) は UI スレッドで呼び出す必要があります。

状況を明確にするのに役立つことを願っています)

于 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

アプリケーション 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 を初期化し、実行後の UI を実行する他の A​​syncTask でアプリケーションをクラッシュさせます。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 に答える