Android開發者:你真的會用AsyncTask嗎?

導讀】在Android應用開發的過程當中,咱們須要時刻注意保證應用程序的穩定和UI操做響應及時,由於不穩定或響應緩慢的應用將給應用帶來很差的印象,嚴重的用戶卸載你的APP,這樣你的努力就沒有體現的價值了。本文試圖從AsnycTask的做用提及,進一步的講解一下內部的實現機制。若是有一些開發經驗的人,讀完以後應該對使用AsnycTask過程當中的一些問題豁然開朗,開發經驗不豐富的也能夠從中找到使用過程當中的注意點。html

##爲什麼引入AsnyncTask?java

在Android程序開始運行的時候會單獨啓動一個進程,默認狀況下全部這個程序操做都在這個進程中進行。一個Android程序默認狀況下只有一個進程,可是一個進程倒是能夠有許線程的。android

在這些線程中,有一個線程叫作UI線程,也叫作Main Thread,除了Main Thread以外的線程均可稱爲Worker Thread。Main Thread主要負責控制UI頁面的顯示、更新、交互等。所以全部在UI線程中的操做要求越短越好,只有這樣用戶纔會以爲操做比較流暢。一個比較好的作法是把一些比較耗時的操做,例如網絡請求、數據庫操做、複雜計算等邏輯都封裝到單獨的線程,這樣就能夠避免阻塞主線程。爲此,有人寫了以下的代碼:git

private TextView textView;
    public void onCreate(Bundle bundle){
        super.onCreate(bundle);
        setContentView(R.layout.thread_on_ui);
        textView = (TextView) findViewById(R.id.tvTest);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    HttpGet httpGet = new HttpGet("http://www.baidu.com");
                    HttpClient httpClient = new DefaultHttpClient();
                    HttpResponse httpResp = httpClient.execute(httpGet);
                    if (httpResp.getStatusLine().getStatusCode() == 200) {
                        String result = EntityUtils.toString(httpResp.getEntity(), "UTF-8");
                        textView.setText("請求返回正常,結果是:" + result);
                    } else {
                        textView.setText("請求返回異常!");
                    }
                }catch (IOException e){
                   e.printStackTrace();
                }
            }
        }).start();
    }

運行,不出所料,異常信息以下:github

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

怎麼破?能夠在主線程建立Handler對象,把textView.setText地方替換爲用handler把返回值發回到handler所在的線程處理,也就是主線程。這個處理方法稍顯複雜,Android爲咱們考慮到了這個狀況,給咱們提供了一個輕量級的異步類能夠直接繼承AsyncTask,在類中實現異步操做,並提供接口反饋當前異步執行的結果以及執行進度,這些接口中有直接運行在主線程中的,例如onPostExecute,onPreExecute等方法。數據庫

也就是說,Android的程序運行時是多線程的,爲了更方便的處理子線程和UI線程的交互,引入了AsyncTask。緩存

##AsnyncTask內部機制網絡

AsyncTask內部邏輯主要有二個部分:多線程

一、與主線的交互,它內部實例化了一個靜態的自定義類InternalHandler,這個類是繼承自Handler的,在這個自定義類中綁定了一個叫作AsyncTaskResult的對象,每次子線程須要通知主線程,就調用sendToTarget發送消息給handler。而後在handler的handleMessage中AsyncTaskResult根據消息的類型不一樣(例如MESSAGE_POST_PROGRESS會更新進度條,MESSAGE_POST_CANCEL取消任務)而作不一樣的操做,值得一提的是,這些操做都是在UI線程進行的,意味着,從子線程一旦須要和UI線程交互,內部自動調用了handler對象把消息放在了主線程了。源碼地址併發

mFuture = new FutureTask<Result>(mWorker) {
           @Override
            protected void More ...done() {
                Message message;
               Result result = null;

                try {
                    result = get();
               } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
               } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
                           new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
                    message.sendToTarget();
                    return;
                } catch (Throwable t) {
                    throw new RuntimeException("An error occured while executing "
                           + "doInBackground()", t);
                }

                message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                        new AsyncTaskResult<Result>(AsyncTask.this, result));
                message.sendToTarget();
           }
        };
private static class InternalHandler extends Handler {
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void More ...handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
                case MESSAGE_POST_CANCEL:
                    result.mTask.onCancelled();
                    break;
            }
        }
    }

二、AsyncTask內部調度,雖然能夠新建多個AsyncTask的子類的實例,可是AsyncTask的內部Handler和ThreadPoolExecutor都是static的,這麼定義的變量屬於類的,是進程範圍內共享的,因此AsyncTask控制着進程範圍內全部的子類實例,並且該類的全部實例都共用一個線程池和Handler。代碼以下:

public abstract class AsyncTask<Params, Progress, Result> {
    private static final String LOG_TAG = "AsyncTask";

    private static final int CORE_POOL_SIZE = 5;
    private static final int MAXIMUM_POOL_SIZE = 128;
    private static final int KEEP_ALIVE = 1;

    private static final BlockingQueue<Runnable> sWorkQueue =
            new LinkedBlockingQueue<Runnable>(10);

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread More ...newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

    private static final int MESSAGE_POST_RESULT = 0x1;
    private static final int MESSAGE_POST_PROGRESS = 0x2;
    private static final int MESSAGE_POST_CANCEL = 0x3;

從代碼還能夠看出,默認核心線程池的大小是5,緩存任務隊列是10。意味着,若是線程池的線程數量小於5,這個時候新添加一個異步任務則會新建一個線程;若是線程池的數量大於等於5,這個時候新建一個異步任務這個任務會被放入緩存隊列中等待執行。限制一個APP內AsyncTask併發的線程的數量看似是有必要的,但也帶來了一個問題,假若有人就是須要同時運行10個而不是5個,或者不對線程的多少作限制,例若有些APP的瀑布流頁面中的N多圖片的加載。

另外一方面,同時運行的任務多,線程也就多,若是這些任務是去訪問網絡的,會致使短期內手機那可憐的帶寬被佔完了,這樣整體的表現是誰都很難很快加載徹底,由於他們是競爭關係。因此,把選擇權交給開發者吧。

事實上,大概從Android從3.0開始,每次新建異步任務的時候AsnycTask內部默認規則是按提交的前後順序每次只運行一個異步任務。固然了你也能夠本身指定本身的線程池。

能夠看出,AsyncTask使用過程當中須要注意的地方很多

  • 因爲Handler須要和主線程交互,而Handler又是內置於AsnycTask中的,因此,AsyncTask的建立必須在主線程。
  • AsyncTaskResult的doInBackground(mParams)方法執行異步任務運行在子線程中,其餘方法運行在主線程中,能夠操做UI組件。
  • 不要手動的去調用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法,這些都是由Android系統自動調用的
  • 一個任務AsyncTask任務只能被執行一次。
  • 運行中能夠隨時調用cancel(boolean)方法取消任務,若是成功調用isCancelled()會返回true,而且不會執行onPostExecute() 方法了,取而代之的是調用 onCancelled() 方法。並且從源碼看,若是這個任務已經執行了這個時候調用cancel是不會真正的把task結束,而是繼續執行,只不過改變的是執行以後的回調方法是onPostExecute仍是onCancelled。

##AsnyncTask和Activity OnConfiguration

上面提到了那麼多的注意點,還有其餘須要注意的嗎?固然有!咱們開發App過程當中使用AsyncTask請求網絡數據的時候,通常都是習慣在onPreExecute顯示進度條,在數據請求完成以後的onPostExecute關閉進度條。這樣作看似完美,可是若是您的App沒有明確指定屏幕方向和configChanges時,當用戶旋轉屏幕的時候Activity就會從新啓動,而這個時候您的異步加載數據的線程可能正在請求網絡。當一個新的Activity被從新建立以後,可能由從新啓動了一個新的任務去請求網絡,這樣以前的一個異步任務不經意間就泄露了,假設你還在onPostExecute寫了一些其餘邏輯,這個時候就會發生意想不到異常。

通常簡單的數據類型的,對付configChanges咱們很好處理,咱們直接能夠經過onSaveInstanceState()和onRestoreInstanceState()進行保存與恢復。Android會在銷燬你的Activity以前調用onSaveInstanceState()方法,因而,你能夠在此方法中存儲關於應用狀態的數據。而後你能夠在onCreate()或onRestoreInstanceState()方法中恢復。

可是,對於AsyncTask怎麼辦?問題產生的根源在於Activity銷燬從新建立的過程當中AsyncTask和以前的Activity失聯,最終致使一些問題。那麼解決問題的思路也能夠朝着這個方向發展。Android官方文檔也有一些解決問題的線索。

這裏介紹另一種使用事件總線的解決方案,是國外一個安卓大牛寫的。中間用到了Square開源的EventBus類庫http://square.github.io/otto/。首先自定義一個AsyncTask的子類,在onPostExecute方法中,把返回結果拋給事件總線,代碼以下:

@Override 
    protected String doInBackground(Void... params) {
        Random random = new Random();
        final long sleep = random.nextInt(10);
        try {
            Thread.sleep(10 * 6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Slept for " + sleep + " seconds";
    }

    @Override
    protected void onPostExecute(String result) {
        MyBus.getInstance().post(new AsyncTaskResultEvent(result));
    }

在Activity的onCreate中註冊這個事件總線,這樣異步線程的消息就會被otta分發到當前註冊的activity,這個時候返回結果就在當前activity的onAsyncTaskResult中了,代碼以下:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.otto_layout);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                new MyAsyncTask().execute();
            }
        });

        MyBus.getInstance().register(this);
    }

    @Override
    protected void onDestroy() {
        MyBus.getInstance().unregister(this);
        super.onDestroy();
    }

    @Subscribe
    public void onAsyncTaskResult(AsyncTaskResultEvent event) {
        Toast.makeText(this, event.getResult(), Toast.LENGTH_LONG).show();
    }

我的覺的這個方法至關好,固然更簡單的你也能夠不用otta這個庫,本身單獨的用接口回調的方式估計也能實現,你們能夠試試。

##如何監控AsyncTask?

不過,AsyncTask雖然很好用,可是問題也很多,須要注意的地方也不少。萬一又出現問題了怎麼辦?客戶反饋一個問題,開發人員第一反應是:「這不可能啊,我這裏測試沒問題的!」可是,老闆讓你解決問題,客戶的環境沒法復現、操做步驟沒法重複,這個時候開發就犯難了......總之,監控這個事情是很是重要的。

以應用性能管理(APM)領軍企業OneAPM爲例,其提供了新一代應用性能管理軟件和服務,可以幫助企業用戶和開發者輕鬆實現,緩慢的程序代碼和SQL語句的實時抓取。OneAPM推出了針對移動端的應用性能監控產品Mobile Insight,用戶能夠訪問OneAPM官方網站,下載移動端監控SDK,測試下AsyncTask。


本文做者系OneAPM工程師編譯整理。OneAPM是中國基礎軟件領域的新興領軍企業。專一於提供下一代應用性能管理軟件和服務,幫助企業用戶和開發者輕鬆實現:緩慢的程序代碼和SQL語句的實時抓取。想閱讀更多技術文章,請訪問OneAPM官方技術博客

相關文章
相關標籤/搜索