(轉(zhuǎn)載)Android開發(fā)者:你真的會用AsyncTask嗎?

導(dǎo)讀】在Android應(yīng)用開發(fā)的過程中,我們需要時刻注意保證應(yīng)用程序的穩(wěn)定和UI操作響應(yīng)及時,因為不穩(wěn)定或響應(yīng)緩慢的應(yīng)用將給應(yīng)用帶來不好的印象, 嚴重的用戶卸載你的APP,這樣你的努力就沒有體現(xiàn)的價值了。本文試圖從AsnycTask的作用說起,進一步的講解一下內(nèi)部的實現(xiàn)機制。如果有一些開發(fā)經(jīng)驗的人, 讀完之后應(yīng)該對使用AsnycTask過程中的一些問題豁然開朗,開發(fā)經(jīng)驗不豐富的也可以從中找到使用過程中的注意點。

為何引入AsnyncTask?

在Android程序開始運行的時候會單獨啟動一個進程,默認情況下所有這個程序操作都在這個進程中進行。一個Android程序默認情況下只有一個進程,但是一個進程卻是可以有許線程的。

在這些線程中,有一個線程叫做UI線程,也叫做Main Thread,除了Main Thread之外的線程都可稱為Worker Thread。Main Thread主要負責(zé)控制UI頁面的顯示、更新、交互等。 因此所有在UI線程中的操作要求越短越好,只有這樣用戶才會覺得操作比較流暢。一個比較好的做法是把一些比較耗時的操作,例如網(wǎng)絡(luò)請求、數(shù)據(jù)庫操作、 復(fù)雜計算等邏輯都封裝到單獨的線程,這樣就可以避免阻塞主線程。為此,有人寫了如下的代碼:

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("請求返回正常,結(jié)果是:" + result);

} else {

textView.setText("請求返回異常!");

}

}catch (IOException e){

e.printStackTrace();

}

}

}).start();

}

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

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

怎么破?可以在主線程創(chuàng)建Handler對象,把textView.setText地方替換為用handler把返回值發(fā)回到handler所在的線程處理,也就是主線程。 這個處理方法稍顯復(fù)雜,Android為我們考慮到了這個情況,給我們提供了一個輕量級的異步類可以直接繼承AsyncTask,在類中實現(xiàn)異步操作, 并提供接口反饋當前異步執(zhí)行的結(jié)果以及執(zhí)行進度,這些接口中有直接運行在主線程中的,例如onPostExecute,onPreExecute等方法。

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

AsnyncTask內(nèi)部機制

AsyncTask內(nèi)部邏輯主要有二個部分:

1、與主線的交互,它內(nèi)部實例化了一個靜態(tài)的自定義類InternalHandler,這個類是繼承自Handler的,在這個自定義類中綁定了一個叫做AsyncTaskResult的對象,每次子線程需要通知主線程,就調(diào)用sendToTarget發(fā)送消息給handler。然后在handler的handleMessage中AsyncTaskResult根據(jù)消息的類型不同(例如MESSAGEPOSTPROGRESS會更新進度條,MESSAGEPOSTCANCEL取消任務(wù))而做不同的操作,值得一提的是,這些操作都是在UI線程進行的,意味著,從子線程一旦需要和UI線程交互,內(nèi)部自動調(diào)用了handler對象把消息放在了主線程了。源碼地址

mFuture = new FutureTask(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(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(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;

}

}

}

2、AsyncTask內(nèi)部調(diào)度,雖然可以新建多個AsyncTask的子類的實例,但是AsyncTask的內(nèi)部Handler和ThreadPoolExecutor都是static的, 這么定義的變量屬于類的,是進程范圍內(nèi)共享的,所以AsyncTask控制著進程范圍內(nèi)所有的子類實例,而且該類的所有實例都共用一個線程池和Handler。代碼如下:

public abstract class AsyncTask {

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 sWorkQueue =

new LinkedBlockingQueue(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,緩存任務(wù)隊列是10。意味著,如果線程池的線程數(shù)量小于5,這個時候新添加一個異步任務(wù)則會新建一個線程; 如果線程池的數(shù)量大于等于5,這個時候新建一個異步任務(wù)這個任務(wù)會被放入緩存隊列中等待執(zhí)行。限制一個APP內(nèi)AsyncTask并發(fā)的線程的數(shù)量看似是有必要的, 但也帶來了一個問題,假如有人就是需要同時運行10個而不是5個,或者不對線程的多少做限制,例如有些APP的瀑布流頁面中的N多圖片的加載。

另一方面,同時運行的任務(wù)多,線程也就多,如果這些任務(wù)是去訪問網(wǎng)絡(luò)的,會導(dǎo)致短時間內(nèi)手機那可憐的帶寬被占完了,這樣總體的表現(xiàn)是誰都很難很快加載完全, 因為他們是競爭關(guān)系。所以,把選擇權(quán)交給開發(fā)者吧。

事實上,大概從Android從3.0開始,每次新建異步任務(wù)的時候AsnycTask內(nèi)部默認規(guī)則是按提交的先后順序每次只運行一個異步任務(wù)。當然了你也可以自己指定自己的線程池。

可以看出,AsyncTask使用過程中需要注意的地方不少

由于Handler需要和主線程交互,而Handler又是內(nèi)置于AsnycTask中的,所以,AsyncTask的創(chuàng)建必須在主線程。

AsyncTaskResult的doInBackground(mParams)方法執(zhí)行異步任務(wù)運行在子線程中,其他方法運行在主線程中,可以操作UI組件。

不要手動的去調(diào)用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法,這些都是由Android系統(tǒng)自動調(diào)用的

一個任務(wù)AsyncTask任務(wù)只能被執(zhí)行一次。

運行中可以隨時調(diào)用cancel(boolean)方法取消任務(wù),如果成功調(diào)用isCancelled()會返回true,并且不會執(zhí)行onPostExecute() 方法了,取而代之的是調(diào)用 onCancelled() 方法。而且從源碼看,如果這個任務(wù)已經(jīng)執(zhí)行了這個時候調(diào)用cancel是不會真正的把task結(jié)束,而是繼續(xù)執(zhí)行,只不過改變的是執(zhí)行之后的回調(diào)方法是onPostExecute還是onCancelled。

AsnyncTask和Activity OnConfiguration

上面提到了那么多的注意點,還有其他需要注意的嗎?當然有!我們開發(fā)App過程中使用AsyncTask請求網(wǎng)絡(luò)數(shù)據(jù)的時候,一般都是習(xí)慣在onPreExecute顯示進度條, 在數(shù)據(jù)請求完成之后的onPostExecute關(guān)閉進度條。這樣做看似完美,但是如果您的App沒有明確指定屏幕方向和configChanges時,當用戶旋轉(zhuǎn)屏幕的時候Activity就會重新啟動, 而這個時候您的異步加載數(shù)據(jù)的線程可能正在請求網(wǎng)絡(luò)。當一個新的Activity被重新創(chuàng)建之后,可能由重新啟動了一個新的任務(wù)去請求網(wǎng)絡(luò),這樣之前的一個異步任務(wù)不經(jīng)意間就泄露了, 假設(shè)你還在onPostExecute寫了一些其他邏輯,這個時候就會發(fā)生意想不到異常。

一般簡單的數(shù)據(jù)類型的,對付configChanges我們很好處理,我們直接可以通過onSaveInstanceState()和onRestoreInstanceState()進行保存與恢復(fù)。 Android會在銷毀你的Activity之前調(diào)用onSaveInstanceState()方法,于是,你可以在此方法中存儲關(guān)于應(yīng)用狀態(tài)的數(shù)據(jù)。然后你可以在onCreate()或onRestoreInstanceState()方法中恢復(fù)。

但是,對于AsyncTask怎么辦?問題產(chǎn)生的根源在于Activity銷毀重新創(chuàng)建的過程中AsyncTask和之前的Activity失聯(lián),最終導(dǎo)致一些問題。 那么解決問題的思路也可以朝著這個方向發(fā)展。Android官方文檔也有一些解決問題的線索。

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

@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分發(fā)到當前注冊的activity,這個時候返回結(jié)果就在當前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這個庫,自己單獨的用接口回調(diào)的方式估計也能實現(xiàn),大家可以試試。

本文系OneAPM工程師原創(chuàng)。想閱讀更多技術(shù)文章,請訪問OneAPM官方技術(shù)博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容