(轉(zhuǎn)載)Android開(kāi)發(fā)者:你真的會(huì)用AsyncTask嗎考传?

導(dǎo)讀】在Android應(yīng)用開(kāi)發(fā)的過(guò)程中,我們需要時(shí)刻注意保證應(yīng)用程序的穩(wěn)定和UI操作響應(yīng)及時(shí)证鸥,因?yàn)椴环€(wěn)定或響應(yīng)緩慢的應(yīng)用將給應(yīng)用帶來(lái)不好的印象僚楞, 嚴(yán)重的用戶卸載你的APP,這樣你的努力就沒(méi)有體現(xiàn)的價(jià)值了敌土。本文試圖從AsnycTask的作用說(shuō)起镜硕,進(jìn)一步的講解一下內(nèi)部的實(shí)現(xiàn)機(jī)制。如果有一些開(kāi)發(fā)經(jīng)驗(yàn)的人返干, 讀完之后應(yīng)該對(duì)使用AsnycTask過(guò)程中的一些問(wèn)題豁然開(kāi)朗兴枯,開(kāi)發(fā)經(jīng)驗(yàn)不豐富的也可以從中找到使用過(guò)程中的注意點(diǎn)。

為何引入AsnyncTask矩欠?

在Android程序開(kāi)始運(yùn)行的時(shí)候會(huì)單獨(dú)啟動(dòng)一個(gè)進(jìn)程财剖,默認(rèn)情況下所有這個(gè)程序操作都在這個(gè)進(jìn)程中進(jìn)行。一個(gè)Android程序默認(rèn)情況下只有一個(gè)進(jìn)程癌淮,但是一個(gè)進(jìn)程卻是可以有許線程的躺坟。

在這些線程中,有一個(gè)線程叫做UI線程乳蓄,也叫做Main Thread咪橙,除了Main Thread之外的線程都可稱為Worker Thread。Main Thread主要負(fù)責(zé)控制UI頁(yè)面的顯示、更新美侦、交互等产舞。 因此所有在UI線程中的操作要求越短越好,只有這樣用戶才會(huì)覺(jué)得操作比較流暢菠剩。一個(gè)比較好的做法是把一些比較耗時(shí)的操作易猫,例如網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)操作具壮、 復(fù)雜計(jì)算等邏輯都封裝到單獨(dú)的線程准颓,這樣就可以避免阻塞主線程。為此棺妓,有人寫(xiě)了如下的代碼:

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

} else {

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

}

}catch (IOException e){

e.printStackTrace();

}

}

}).start();

}

運(yùn)行涧郊,不出所料贯被,異常信息如下:

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

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

也就是說(shuō),Android的程序運(yùn)行時(shí)是多線程的鞋囊,為了更方便的處理子線程和UI線程的交互止后,引入了AsyncTask。

AsnyncTask內(nèi)部機(jī)制

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

1溜腐、與主線的交互译株,它內(nèi)部實(shí)例化了一個(gè)靜態(tài)的自定義類InternalHandler,這個(gè)類是繼承自Handler的挺益,在這個(gè)自定義類中綁定了一個(gè)叫做AsyncTaskResult的對(duì)象歉糜,每次子線程需要通知主線程,就調(diào)用sendToTarget發(fā)送消息給handler望众。然后在handler的handleMessage中AsyncTaskResult根據(jù)消息的類型不同(例如MESSAGEPOSTPROGRESS會(huì)更新進(jìn)度條匪补,MESSAGEPOSTCANCEL取消任務(wù))而做不同的操作伞辛,值得一提的是,這些操作都是在UI線程進(jìn)行的叉袍,意味著始锚,從子線程一旦需要和UI線程交互,內(nèi)部自動(dòng)調(diào)用了handler對(duì)象把消息放在了主線程了喳逛。源碼地址

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)度棵里,雖然可以新建多個(gè)AsyncTask的子類的實(shí)例润文,但是AsyncTask的內(nèi)部Handler和ThreadPoolExecutor都是static的, 這么定義的變量屬于類的殿怜,是進(jìn)程范圍內(nèi)共享的典蝌,所以AsyncTask控制著進(jìn)程范圍內(nèi)所有的子類實(shí)例,而且該類的所有實(shí)例都共用一個(gè)線程池和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;

從代碼還可以看出骏掀,默認(rèn)核心線程池的大小是5,緩存任務(wù)隊(duì)列是10柱告。意味著截驮,如果線程池的線程數(shù)量小于5,這個(gè)時(shí)候新添加一個(gè)異步任務(wù)則會(huì)新建一個(gè)線程际度; 如果線程池的數(shù)量大于等于5葵袭,這個(gè)時(shí)候新建一個(gè)異步任務(wù)這個(gè)任務(wù)會(huì)被放入緩存隊(duì)列中等待執(zhí)行。限制一個(gè)APP內(nèi)AsyncTask并發(fā)的線程的數(shù)量看似是有必要的乖菱, 但也帶來(lái)了一個(gè)問(wèn)題坡锡,假如有人就是需要同時(shí)運(yùn)行10個(gè)而不是5個(gè),或者不對(duì)線程的多少做限制窒所,例如有些APP的瀑布流頁(yè)面中的N多圖片的加載鹉勒。

另一方面,同時(shí)運(yùn)行的任務(wù)多吵取,線程也就多禽额,如果這些任務(wù)是去訪問(wèn)網(wǎng)絡(luò)的,會(huì)導(dǎo)致短時(shí)間內(nèi)手機(jī)那可憐的帶寬被占完了海渊,這樣總體的表現(xiàn)是誰(shuí)都很難很快加載完全绵疲, 因?yàn)樗麄兪歉?jìng)爭(zhēng)關(guān)系。所以臣疑,把選擇權(quán)交給開(kāi)發(fā)者吧盔憨。

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

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

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

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

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

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

運(yùn)行中可以隨時(shí)調(diào)用cancel(boolean)方法取消任務(wù)社证,如果成功調(diào)用isCancelled()會(huì)返回true逼龟,并且不會(huì)執(zhí)行onPostExecute() 方法了,取而代之的是調(diào)用 onCancelled() 方法追葡。而且從源碼看腺律,如果這個(gè)任務(wù)已經(jīng)執(zhí)行了這個(gè)時(shí)候調(diào)用cancel是不會(huì)真正的把task結(jié)束,而是繼續(xù)執(zhí)行宜肉,只不過(guò)改變的是執(zhí)行之后的回調(diào)方法是onPostExecute還是onCancelled匀钧。

AsnyncTask和Activity OnConfiguration

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

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

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

這里介紹另外一種使用事件總線的解決方案,是國(guó)外一個(gè)安卓大牛寫(xiě)的愚屁。中間用到了Square開(kāi)源的EventBus類庫(kù)http://square.github.io/otto/谆甜。 首先自定義一個(gè)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中注冊(cè)這個(gè)事件總線谆棺,這樣異步線程的消息就會(huì)被otta分發(fā)到當(dāng)前注冊(cè)的activity栽燕,這個(gè)時(shí)候返回結(jié)果就在當(dāng)前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();

}

個(gè)人覺(jué)的這個(gè)方法相當(dāng)好改淑,當(dāng)然更簡(jiǎn)單的你也可以不用otta這個(gè)庫(kù)碍岔,自己?jiǎn)为?dú)的用接口回調(diào)的方式估計(jì)也能實(shí)現(xiàn),大家可以試試朵夏。

本文系OneAPM工程師原創(chuàng)蔼啦。想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問(wèn)OneAPM官方技術(shù)博客仰猖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捏肢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子饥侵,更是在濱河造成了極大的恐慌鸵赫,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躏升,死亡現(xiàn)場(chǎng)離奇詭異辩棒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)膨疏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)一睁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人佃却,你說(shuō)我怎么就攤上這事者吁。” “怎么了双霍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵砚偶,是天一觀的道長(zhǎng)批销。 經(jīng)常有香客問(wèn)我,道長(zhǎng)染坯,這世上最難降的妖魔是什么均芽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮单鹿,結(jié)果婚禮上掀宋,老公的妹妹穿的比我還像新娘。我一直安慰自己仲锄,他們只是感情好劲妙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著儒喊,像睡著了一般镣奋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怀愧,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天侨颈,我揣著相機(jī)與錄音,去河邊找鬼芯义。 笑死哈垢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扛拨。 我是一名探鬼主播耘分,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼绑警!你這毒婦竟也來(lái)了求泰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤待秃,失蹤者是張志新(化名)和其女友劉穎拜秧,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體章郁,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枉氮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暖庄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聊替。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖培廓,靈堂內(nèi)的尸體忽然破棺而出惹悄,到底是詐尸還是另有隱情,我是刑警寧澤肩钠,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布泣港,位于F島的核電站暂殖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏当纱。R本人自食惡果不足惜呛每,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坡氯。 院中可真熱鬧晨横,春花似錦、人聲如沸箫柳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)悯恍。三九已至库糠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涮毫,已是汗流浹背曼玩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窒百,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓豫尽,卻偏偏與公主長(zhǎng)得像篙梢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子美旧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容