265

私はこの問題を何ヶ月も調査し、さまざまな解決策を考え出しましたが、それらはすべて大規模なハックであるため、満足していません. 設計に欠陥のあるクラスがフレームワークに組み込まれ、誰もそれについて話していないとはまだ信じられないので、何かが欠けているに違いないと思います。

問題はAsyncTask. ドキュメントによると、

「スレッドやハンドラーを操作することなく、バックグラウンド操作を実行し、UI スレッドで結果を公開できます。」

showDialog()この例では、いくつかの例示的なメソッドが でどのように呼び出されるかを引き続き示しますonPostExecute()。ただし、ダイアログを表示するには常に valid への参照が必要であり、 AsyncTaskcontext object への強い参照を保持してはならないため、これは完全に不自然に思えます。Context

その理由は明らかです。タスクをトリガーしたアクティビティが破棄された場合はどうなるでしょうか。これは、画面を反転したなどの理由で、常に発生する可能性があります。タスクがそれを作成したコンテキストへの参照を保持する場合、役に立たないコンテキスト オブジェクトを保持しているだけでなく (ウィンドウは破棄され、 UI の操作はすべて例外で失敗します!)、メモリーリーク。

ここで私のロジックに欠陥がない限り、これは次のように解釈されます:onPostExecute()コンテキストにアクセスできない場合、UI スレッドでこのメソッドを実行することは何のメリットがあるのでしょうか? ここでは意味のあることは何もできません。

回避策の 1 つは、コンテキスト インスタンスを AsyncTask に渡すのではなく、Handlerインスタンスに渡すことです。これは機能します: Handler はコンテキストとタスクを緩やかにバインドするため、リークの危険を冒すことなくそれらの間でメッセージを交換できます (そうですか?)。しかし、それは AsyncTask の前提、つまりハンドラーを気にする必要がないという前提が間違っていることを意味します。同じスレッドでメッセージを送受信しているため、Handler を悪用しているようにも見えます (UI スレッドでメッセージを作成し、UI スレッドでも実行される onPostExecute() で送信します)。

さらに、その回避策を使用しても、コンテキストが破棄されると、起動されたタスクの記録がないという問題がまだ残っています。つまり、画面の向きを変更した後など、コンテキストを再作成するときにタスクを再起動する必要があります。これは遅くて無駄です。

これに対する私の解決策 ( Droid-Fu ライブラリに実装されているWeakReference) は、コンポーネント名から一意のアプリケーション オブジェクトの現在のインスタンスへの s のマッピングを維持することです。AsyncTask が開始されるたびに、呼び出しコンテキストがそのマップに記録され、コールバックごとに、そのマッピングから現在のコンテキスト インスタンスがフェッチされます。これにより、古いコンテキスト インスタンス参照することがなくなり、コールバックで常に有効なコンテキストにアクセスできるため、そこで意味のある UI 作業を行うことができます。また、参照が弱く、特定のコンポーネントのインスタンスが存在しなくなるとクリアされるため、リークもありません。

それでも、これは複雑な回避策であり、Droid-Fu ライブラリ クラスの一部をサブクラス化する必要があるため、かなり煩わしいアプローチになります。

今、私は単に知りたいのです:私は何かを大幅に見逃しているのでしょうか、それとも AsyncTask は本当に完全に欠陥がありますか? あなたの経験はそれでどのように機能していますか? これらの問題をどのように解決しましたか?

ご意見ありがとうございます。

4

12 に答える 12

87

このようなものはどうですか:

class MyActivity extends Activity {
    Worker mWorker;

    static class Worker extends AsyncTask<URL, Integer, Long> {
        MyActivity mActivity;

        Worker(MyActivity activity) {
            mActivity = activity;
        }

        @Override
        protected Long doInBackground(URL... urls) {
            int count = urls.length;
            long totalSize = 0;
            for (int i = 0; i < count; i++) {
                totalSize += Downloader.downloadFile(urls[i]);
                publishProgress((int) ((i / (float) count) * 100));
            }
            return totalSize;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            if (mActivity != null) {
                mActivity.setProgressPercent(progress[0]);
            }
        }

        @Override
        protected void onPostExecute(Long result) {
            if (mActivity != null) {
                mActivity.showDialog("Downloaded " + result + " bytes");
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mWorker = (Worker)getLastNonConfigurationInstance();
        if (mWorker != null) {
            mWorker.mActivity = this;
        }

        ...
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        return mWorker;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mWorker != null) {
            mWorker.mActivity = null;
        }
    }

    void startWork() {
        mWorker = new Worker(this);
        mWorker.execute(...);
    }
}
于 2010-07-29T02:10:40.363 に答える
20

その理由は明らかです。タスクをトリガーしたアクティビティが破棄された場合はどうなるでしょうか。

AsyncTaskアクティビティとinの関連付けを手動で解除しonDestroy()ます。AsyncTask新しいアクティビティをin に手動で再度関連付けonCreate()ます。これには、静的な内部クラスまたは標準の Java クラスのいずれかが必要で、さらにおそらく 10 行のコードが必要です。

于 2010-07-29T00:59:41.940 に答える
15

概念的に欠陥あるだけではないようAsyncTaskです。また、互換性の問題により使用できません。Androidドキュメントは次のとおりです。

最初に導入されたとき、AsyncTasksは単一のバックグラウンドスレッドでシリアルに実行されていました。 DONUT以降、これはスレッドのプールに変更され、複数のタスクを並行して操作できるようになりました。 HONEYCOMBを開始すると、並列実行によって引き起こされる一般的なアプリケーションエラーを回避するために、タスクは単一スレッドでの実行に戻ります。 本当に並列実行が必要な場合は executeOnExecutor(Executor, Params...) 、このメソッドのバージョンを THREAD_POOL_EXECUTOR;で使用できます。ただし、その使用に関する警告については、そこにある解説を参照してください。

executeOnExecutor()THREAD_POOL_EXECUTORは両方ともAPIレベル11(Android 3.0.x、HONEYCOMB)で追加されています。

つまり、AsyncTask2つのファイルをダウンロードするために2つのファイルを作成した場合、最初のダウンロードが終了するまで2番目のダウンロードは開始されません。2つのサーバーを介してチャットし、最初のサーバーがダウンしている場合、最初のサーバーへの接続がタイムアウトする前に、2番目のサーバーに接続することはありません。(もちろん、新しいAPI11機能を使用しない限り、コードは2.xと互換性がなくなります)。

また、2.xと3.0+の両方をターゲットにしたい場合は、非常に注意が必要です。

さらに、ドキュメントには次のように書かれています。

注意:ワーカースレッドを使用するときに発生する可能性のあるもう1つの問題は、ランタイム構成の変更(ユーザーが画面の向きを変更した場合など)によるアクティビティの予期しない再起動であり、ワーカースレッドが破壊される可能性があります。これらの再起動のいずれかでタスクを永続化する方法と、アクティビティが破棄されたときにタスクを適切にキャンセルする方法については、Shelvesサンプルアプリケーションのソースコードを参照してください。

于 2013-01-30T11:05:41.397 に答える
12

おそらく、Google を含む私たち全員が、 MVCの観点AsyncTaskから誤用しています。

Activity はControllerであり、 Controller はViewよりも長生きする可能性のある操作を開始すべきではありません。つまり、AsyncTasks はModelから、Activity ライフサイクルにバインドされていないクラスから使用する必要があります。Activity はローテーションで破棄されることに注意してください。(Viewに関しては、通常、android.widget.Button などから派生したクラスをプログラムすることはありませんが、可能です。通常、Viewについて行うことは xml だけです。)

つまり、アクティビティのメソッドに AsyncTask 派生物を配置するのは間違っています。OTOH、アクティビティで AsyncTasks を使用してはならない場合、AsyncTask はその魅力を失います。以前は、迅速かつ簡単な修正として宣伝されていました。

于 2013-01-30T11:54:47.163 に答える
5

AsyncTask からのコンテキストへの参照でメモリ リークのリスクがあるというのは本当かどうかはわかりません。

それらを実装する通常の方法は、Activity のいずれかのメソッドのスコープ内に新しい AsyncTask インスタンスを作成することです。アクティビティが破棄された場合、AsyncTask が完了すると到達不能になり、ガベージ コレクションの対象になるのでしょうか? したがって、AsyncTask 自体はぶらぶらしないため、アクティビティへの参照は問題になりません。

于 2010-07-28T23:52:57.137 に答える
2

アクティビティに WeekReference を保持する方がより堅牢です。

public class WeakReferenceAsyncTaskTestActivity extends Activity {
    private static final int MAX_COUNT = 100;

    private ProgressBar progressBar;

    private AsyncTaskCounter mWorker;

    @SuppressWarnings("deprecation")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task_test);

        mWorker = (AsyncTaskCounter) getLastNonConfigurationInstance();
        if (mWorker != null) {
            mWorker.mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(this);
        }

        progressBar = (ProgressBar) findViewById(R.id.progressBar1);
        progressBar.setMax(MAX_COUNT);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_async_task_test, menu);
        return true;
    }

    public void onStartButtonClick(View v) {
        startWork();
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        return mWorker;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mWorker != null) {
            mWorker.mActivity = null;
        }
    }

    void startWork() {
        mWorker = new AsyncTaskCounter(this);
        mWorker.execute();
    }

    static class AsyncTaskCounter extends AsyncTask<Void, Integer, Void> {
        WeakReference<WeakReferenceAsyncTaskTestActivity> mActivity;

        AsyncTaskCounter(WeakReferenceAsyncTaskTestActivity activity) {
            mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(activity);
        }

        private static final int SLEEP_TIME = 200;

        @Override
        protected Void doInBackground(Void... params) {
            for (int i = 0; i < MAX_COUNT; i++) {
                try {
                    Thread.sleep(SLEEP_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(getClass().getSimpleName(), "Progress value is " + i);
                Log.d(getClass().getSimpleName(), "getActivity is " + mActivity);
                Log.d(getClass().getSimpleName(), "this is " + this);

                publishProgress(i);
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            if (mActivity != null) {
                mActivity.get().progressBar.setProgress(values[0]);
            }
        }
    }

}
于 2012-09-20T22:56:33.957 に答える
1

所有するアクティビティのメソッドをオーバーライドして、そこからonPause()キャンセルしないのはなぜAsyncTaskですか?

于 2012-08-08T05:23:27.770 に答える
1

あなたの言うとおりです。アクティビティで非同期タスク/ローダーを使用してデータを取得することから離れる動きが勢いを増しているのはそのためです。新しい方法の 1 つは、データの準備ができたら本質的にコールバックを提供するVolleyフレームワークを使用することです。MVC モデルとの一貫性がはるかに高くなります。Volley は Google I/O 2013 で導入されました。これを知らない人が多い理由はわかりません。

于 2014-07-15T14:25:15.170 に答える
0

個人的には、Thread を拡張し、コールバック インターフェイスを使用して UI を更新するだけです。FC の問題がなければ AsyncTask を正しく動作させることはできませんでした。また、ノンブロッキング キューを使用して実行プールを管理しています。

于 2010-07-29T00:03:22.917 に答える
0

キャンセルが機能すると思ったが、機能しない。

ここで彼らはそれについてRTFMしています:

""タスクが既に開始されている場合、mayInterruptIfRunning パラメーターは、タスクを停止するために、このタスクを実行しているスレッドを中断する必要があるかどうかを決定します。"

ただし、これはスレッドが割り込み可能であることを意味するものではありません。それは Java の問題であり、AsyncTask の問題ではありません。」

http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d

于 2010-12-14T01:21:25.137 に答える
-1

「それを使った経験」に関しては、すべての AsyncTasks とともにプロセスを強制終了することができます。Android はアクティビティ スタックを再作成して、ユーザーが何も言及しないようにします。

于 2013-01-30T05:16:39.570 に答える