1. ホーム
  2. アンドロイド

[解決済み】AsyncTaskは本当に概念的に欠陥があるのか、それとも私が何かを見逃しているだけなのか?

2022-05-04 12:18:53

質問

私はこの問題を数ヶ月間調査し、さまざまな解決策を考え出しましたが、どれも大規模なハッキングなので満足していません。設計に欠陥のあるクラスがフレームワークに入り、誰もそれについて話していないことが未だに信じられないので、私が何かを見逃しているだけなのだと思います。

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

をバックグラウンドで実行することができます。 を実行し、その結果を UIスレッドを操作することなく スレッドおよび/またはハンドラです。

この例では、引き続き、いくつかの例示的な showDialog() メソッドが呼び出されるのは onPostExecute() . しかし、これは まったくもって作為的である というのも、ダイアログを表示するには、常に有効な Context と、AsyncTask は、コンテキストオブジェクトへの強い参照を決して保持してはなりません。 .

理由は明白で、タスクを起動させたアクティビティが破壊されたらどうなるか?例えば、画面を反転させた場合など、よくあることです。もしタスクがそれを作成したコンテキストへの参照を保持していたら、役に立たないコンテキストオブジェクトを保持することになるだけでなく(ウィンドウは破壊されたので 任意の UIインタラクションは例外で失敗します!)さらに、メモリリークを引き起こす危険性さえあります。

ここで私の論理に欠陥がなければ、これは次のように訳されます。 onPostExecute() なぜなら、もしコンテキストにアクセスできないのなら、このメソッドがUIスレッドで実行されることにどんな意味があるでしょうか?ここで意味のあることは何もできないのです。

回避策としては、AsyncTaskにコンテキストインスタンスを渡さず、AsyncTaskのインスタンスとして Handler インスタンスです。Handlerはコンテキストとタスクを緩く結合するので、リークのリスクなしにそれらの間でメッセージを交換することができます(そうでしょうか)。しかし、それではAsyncTaskの前提、つまりハンドラを気にする必要がない、というのは間違っていることになります。また、同じスレッドでメッセージを送受信している(UIスレッドで作成し、同じくUIスレッドで実行されるonPostExecute()でそれを通して送信する)ので、Handlerを乱用しているようにも思えます。

さらに言えば、この回避策をとったとしても、コンテキストが破壊されたときに レコードなし が発生します。つまり、画面の向きを変えた後など、コンテキストを再作成するときに、タスクを再スタートさせなければならないのです。これは遅くて無駄なことです。

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

しかし、これは複雑な回避策であり、Droid-Fuライブラリのクラスのいくつかをサブクラス化する必要があるため、かなり邪魔な方法となっています。

あとは単純に知りたいですね。 私が何かを見落としているだけなのか、それともAsyncTaskは本当に全く欠陥のないものなのでしょうか?それを使ってどのように仕事をした経験がありますか?どのようにこれらの問題を解決しましたか?

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

解決方法は?

こんなのはどうでしょう。

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