android asynctask取消 - AsyncTask是真的在概念上有缺陷還是我錯過了一些東西?




asynctask用法 cancel (11)

你最好將AsyncTask看作與Activity,Context,ContextWrapper等更緊密結合的東西。當它的範圍被完全理解時,它更加方便。

確保您的生命週期中有取消政策,以便它最終將被垃圾收集,不再保留對您的活動的參考,並且它也可以被垃圾收集。

如果你不需要取消你的AsyncTask而在你的Context上運行,你會遇到內存洩漏和NullPointerException異常,如果你只需要提供一個Toast這樣的反饋信息,那麼你的應用程序上下文的單例將有助於避免NPE問題。

AsyncTask並不全是壞事,但絕對有很多魔法可以導致一些不可預見的陷阱。

我已經調查了幾個月的這個問題,提出了不同的解決方案,我不滿意,因為它們都是大規模的黑客。 我仍然無法相信一個在設計上存在缺陷的類將它變成了框架,並且沒有人在談論它,所以我想我必須錯過一些東西。

問題在於AsyncTask 。 根據它的文件

“允許執行後台操作並在UI線程上發布結果,而無需操縱線程和/或處理程序。”

然後該示例繼續顯示onPostExecute()如何調用一些示例showDialog()方法。 但是,這似乎完全是設計的 ,因為顯示對話框始終需要對有效Context的引用,並且AsyncTask 絕不能持有對上下文對象的強引用

原因很明顯:如果活動被破壞而觸發任務會怎樣? 這可能會一直發生,例如因為您翻轉了屏幕。 如果任務持有對創建它的上下文的引用,那麼您不僅要堅持一個無用的上下文對象(該窗口已被銷毀,並且任何 UI交互都將失敗,並發生異常!),您甚至可能冒險創建一個內存洩漏。

除非我的邏輯在這裡有缺陷,否則這將轉化為: onPostExecute()完全沒用,因為如果您無權訪問任何上下文,此方法在UI線程上運行有什麼用處? 在這裡你無法做任何有意義的事情。

一種解決方法是不將上下文實例傳遞給AsyncTask,而是將Handler實例傳遞給AsyncTask。 這是有效的:由於處理程序鬆散地綁定上下文和任務,因此您可以在它們之間交換消息而不會冒險洩漏(對吧?)。 但這意味著AsyncTask的前提,即你不需要打擾處理程序,是錯誤的。 它也好像在濫用Handler,因為你在同一個線程上發送和接收消息(你在UI線程上創建它,並通過在UI線程上執行的onPostExecute()發送它)。

最重要的是,即使採用了這種解決方法,仍然存在當上下文被破壞時,您沒有記錄它所激發的任務的問題。 這意味著在重新創建上下文時,例如在屏幕方向更改後,您必須重新啟動任何任務。 這是緩慢而浪費的。

我對此的解決方案( 在Droid-Fu庫中實現 )是維護WeakReference從組件名稱到其獨特應用程序對像上當前實例的映射。 無論何時啟動AsyncTask,它都會在該映射中記錄調用上下文,並且在每個回調中,它都會從該映射中獲取當前上下文實例。 這可以確保您永遠不會引用過時的上下文實例, 並且您始終可以訪問回調中的有效上下文,以便您可以在那裡進行有意義的UI工作。 它也不會洩漏,因為引用很弱並且在給定組件的實例不再存在時被清除。

儘管如此,這是一個複雜的解決方法,並且需要對Droid-Fu庫類中的一些子類進行子類化,這使得它成為一種相當侵入性的方法。

現在我只想知道:我是大量缺少什麼東西還是AsyncTask真的完全有缺陷? 你的經歷如何與它一起工作? 你是如何解決這些問題的?

感謝您的輸入。


至於“使用它的經驗”:有possible將所有AsyncTasks與該進程一起殺死 ,Android將重新創建活動堆棧,以便用戶不會提及任何內容。


原因很明顯:如果活動被破壞而觸發任務會怎樣?

手動取消onDestroy() AsyncTask的活動。 手動重新將新活動關聯到onCreate()AsyncTask 。 這需要一個靜態的內部類或一個標準的Java類,加上大概10行代碼。


可能我們所有人,包括谷歌,都從MVC的角度濫用AsyncTask

一個活動是一個控制器 ,並且控制器不應該開始可能超過視圖的操作 。 也就是說,AsyncTasks應該從Model中使用 ,從一個沒有綁定到Activity生命週期的類中 - 記住Activity在循環中被銷毀。 (至於視圖 ,你通常不會編程從例如android.widget.Button派生的類,但是你可以。通常,你對View做的唯一事情就是xml。)

換句話說,將AsyncTask派生物放入活動的方法中是錯誤的。 OTOH,如果我們不能在活動中使用AsyncTasks,AsyncTask會失去其吸引力:它曾被廣告為一種快速簡便的修復方法。


你是對的 - 這就是為什麼遠離在活動中使用異步任務/加載器來獲取數據的運動正在獲得動力。 新的方法之一是使用Volley框架,一旦數據準備好就提供回調 - 與MVC模型更加一致。 Volley在2013年Google I / O大會上受到歡迎。不知道為什麼更多人不知道這一點。



在您的活動中保留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]);
            }
        }
    }

}

如何這樣的事情:

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

就我個人而言,我只是擴展了Thread並使用回調接口來更新UI。 如果沒有FC問題,我永遠無法讓AsyncTask正常工作。 我也使用非阻塞隊列來管理執行池。


看起來AsyncTask不僅僅是概念上的缺陷 。 兼容性問題也無法使用。 Android文檔閱讀:

首次引入時,AsyncTasks在單個後台線程上被串行執行。 從DONUT開始,將其更改為允許多個任務並行操作的線程池。 啟動HONEYCOMB後,任務將重新在單個線程上執行,以避免並行執行導致的常見應用程序錯誤。 如果您真的想要並行執行,可以使用 THREAD_POOL_EXECUTOR 的此方法 executeOnExecutor(Executor, Params...) 版本 ; 不過,請參閱那裡的評論,以了解它的使用警告。

executeOnExecutor()THREAD_POOL_EXECUTOR都是在API級別11 (Android 3.0.x,HONEYCOMB)中添加的。

這意味著如果您創建兩個AsyncTask來下載兩個文件,第二個下載將不會開始,直到第一個下載完成。 如果您通過兩台服務器聊天,並且第一台服務器關閉,則在連接到第一台服務器之前,您將不會連接到第二台服務器。 (當然,除非您使用新的API11功能,但這會使您的代碼與2.x不兼容)。

如果你想要同時定位2.x和3.0+,那麼這個東西變得非常棘手。

另外, docs說:

警告:由於運行時配置更改(例如,當用戶更改屏幕方向時),您在使用工作線程時可能遇到的另一個問題會在您的活動中意外重啟,從而可能會損壞工作線程 要了解如何在這些重新啟動過程中持久化您的任務以及在活動被銷毀時如何正確取消任務,請參閱Shelves示例應用程序的源代碼。


我將其添加到我的自定義對話框片段中,因此我不必擔心外部的任何邏輯。 使用boolean shown字段覆蓋show()onDismiss()方法:

  private static boolean shown = false;

    @Override
    public void show(FragmentManager manager, String tag) {
        if (shown) return;

        super.show(manager, tag);
        shown = true;
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        shown = false;
        super.onDismiss(dialog);
    }

如果要檢查是否顯示,可以為shown布爾值創建一個getter。





android concurrency handler android-asynctask