探尋Android的線程問題
什么是線程?
線程或者線程執(zhí)行本質(zhì)上就是一串命令(也是程序代碼),然后我們把它發(fā)送給操作系統(tǒng)執(zhí)行。
Multithreaded_process
一般來說,我們的CPU在任何時候一個核只能處理一個線程。多核處理器(目前大多數(shù)Android設備已經(jīng)都是多核)顧名思義,就是可以同時處理多線程(通俗地講就是可以同時處理多件事)。
多核處理與單核多任務處理的實質(zhì)
上面我說的是一般情況,并不是所有的描述都是一定正確的。因為單核也可以用多任務模擬出多線程。
每個運行在線程中的任務都可以分解成多條指令,而且這些指令不用同時執(zhí)行。所以,單核設備可以首先切換到線程1去執(zhí)行指令1A,然后切換到線程2去執(zhí)行指令2A,接著返回到線程1再去執(zhí)行1B、1C、1D,然后繼續(xù)切換到線程2,執(zhí)行2B、2C等等,以此類推。
這個線程之間的切換十分迅速,以至于在單核的設備中也會發(fā)生。幾乎所有的線程都在相同的時間內(nèi)進行任務處理。其實,這都是因為速度太快造成的假象,就像電影《黑客帝國》里的特工Brown一樣,可以變幻出很多的頭和手。
接下來我們來看一些代碼。
Java核心里的線程
在Java中,如果要想做平行任務處理的話,會在Runnable里面執(zhí)行你的代碼??梢岳^承Thread類,或者實現(xiàn)Runnable接口:
// Version 1
public class IAmAThread extends Thread {
public IAmAThread() {
super("IAmAThread");
}
@Override
public void run() {
// your code (sequence of instructions)
}
}
// to execute this sequence of instructions in a separate thread.
new IAmAThread().start();
// Version 2
public class IAmARunnable implements Runnable {
@Override
public void run() {
// your code (sequence of instructions)
}
}
// to execute this sequence of instructions in a separate thread.
IAmARunnable myRunnable = new IAmARunnable();
new Thread(myRunnable).start();
這兩個方法基本上是一樣的。第一個版本是創(chuàng)建一個Thread類,第二個版本是需要創(chuàng)建一個Runnable對象,然后也需要一個Thread類來調(diào)用它。
第二個版是通常建議使用的方法。這也是一個很大的主題了,超過了本文的范圍,以后會再做討論。
Android上的線程
無論何時啟動APP,所有的組件都會運行在一個單獨的線程中(默認的)——叫做主線程。這個線程主要用于處理UI的操作并為視圖組件和小部件分發(fā)事件等,因此主線程也被稱作UI線程。
如果你在UI線程中運行一個耗時操作,那么UI就會被鎖住,直到這個耗時操作結(jié)束。對于用戶體驗來說,這是非常糟糕的!這也就是為什么我們要理解Android上的線程機制了。理解這些機制就可以把一些復雜的工作移動到其它的線程中去執(zhí)行。如果你在UI線程中運行一個耗時的任務,那么很有可能會發(fā)生ANR(應用無響應),這樣用戶就會很快地結(jié)束掉你的APP。
Android和Java一樣,它支持使用Java里面的Thread類來進行一步任務處理。所以可以輕松地像上面Java的例子一樣來使用Android上的線程,不過那好像還是有點困難。
為什么在Android上使用標準Java的線程會困難呢?
其實平行任務處理沒有想象中的那么簡單,你必須在多線程中保證并發(fā),就像偉大的Tim Bray說的那樣:ordinary humans can't do concurrency at scale (or really at all) …
特別對于Android來說,以下這些功能就略顯臃腫:
異步對于UI線程來說是一個主要的PITA(如果你需要在后臺線程中向主線程更新界面,那么你就會用到)。 如果屏幕方向或者屏幕配置改變的話,就會出現(xiàn)一些更加奇怪的現(xiàn)象。因為改變屏幕方向,會引起Activity重建(所以后臺線程就需要去改變被銷毀的Activity的狀態(tài)了,而如果后臺線程不是在UI線程之上的話,那情況會更加復雜,原因如條件1)。 對于線程池來說,沒有默認的處理方式。 取消線程操作需要自定義代碼實現(xiàn)。
那么在Android上怎么進行任務并發(fā)處理呢?
你可能聽過一些Android上一些常見的名詞:
1、Handler
這就是我們今天要討論的詳細主題。
2、AsyncTask
使用AsyncTask是在Android上操作線程最簡單的方式,也是最容易出錯的方式。
3、IntentService
這種方式需要寫更多的代碼,但是這是把耗時任務移動到后臺的很好的方式,也是我最喜歡的方式。配上使用一個EventBus機制的框架如Otto,這樣的話實現(xiàn)IntentService就非常簡單了。
4、Loader
關(guān)于處理異步任務,還有很多事情需要做,比如從數(shù)據(jù)庫或者內(nèi)容提供者那里處理一些數(shù)據(jù)。
5、Service
如果你曾經(jīng)使用過Service的話,你應該知道這里會有一點誤區(qū),其中一個常見的誤解就是服務是運行在后臺線程的。其實不是!看似運行在后臺是因為它們不與UI組件關(guān)聯(lián),但是它們(默認)是運行在UI線程上的……所以默認運行在UI線程上,甚至在上面沒有UI部件。
如果想要把服務運行在后臺線程中,那么必須自定義一個線程,然后把操作代碼都運行在那個線程中(與上面提到的方法很類似)。事實上你應該使用IntentService實現(xiàn),但是這不是本文討論的主題。
Android上的Handler
以下是從Android developer documentation for Handlers:中摘選的一段話:
> A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread/message queue of the thread that is creating it — from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.
為了更好地了解這個概念,也許你需要去看看什么是Message Queues。
消息隊列
在線程里基本都有一個叫做“消息隊列”的東西,它負責線程間通信。這是一種設計模式,所有控制指令或者內(nèi)容在線程間傳遞。
消息隊列如同它的名字那樣,對于線程來說,它就是一個指令隊列。這里我們還可以做一些更酷的事:
定時消息和線程在某個時間點才執(zhí)行。 需要在另一個線程中去添加入隊動作,而不是在本線程中。
注意:這里說的“消息”和Runnable對象、指令隊列的概念是一樣的。
回到Android上的Handler……如果你仔細閱讀的話,可以看到文檔是這樣說的:
> A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue.
所以Handler可以讓你給線程隊列發(fā)消息:
> Each Handler instance is associated with a single thread and that thread's message queue.
一個Handler對象只能和一個線程關(guān)聯(lián):
> When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it
所以一個Handler到底和哪個線程關(guān)聯(lián)呢?就是創(chuàng)造它的線程。
> — from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.、
在我們了解這些知識后,請繼續(xù)看……
小貼士:這里有幾點可能你還不知道。每個線程都和一個Handler類實例綁定,而且可以和別的線程一起運行,相互通信。
還有一個小建議(如果用過AsyncTask的話),AsyncTask內(nèi)部也是使用Handler進行處理的,只是不是運行在UI線程而已,它會提供一個channel來和UI線程通信,使用postExecute方法即可實現(xiàn)。
這還挺酷的,那怎么創(chuàng)建Handler呢?
有兩種方式:
使用默認的構(gòu)造方法:new Handler()。 使用帶參的構(gòu)造方法,參數(shù)是一個Runnable對象或者回調(diào)對象。
Handler里面有什么實用的API嗎?
請記住:
Handler只是簡單往消息隊列中發(fā)送消息而已(或者使用post方式) 它們有更方便的方法可以幫助與UI線程通信。
如果你現(xiàn)在看看Handler的API,可以清楚看到這幾個方法:
post postDelayed postAtTime
代碼示例
這里的代碼都是很基礎(chǔ)的,不過你可以好好看看注釋。
示例1:使用Handler的“post”方法
public class TestActivity extends Activity {
// ...
// all standard stuff
@Override
public void onCreate(Bundle savedInstanceState) {
// ...
// all standard stuff
// we're creating a new handler here
// and we're in the UI Thread (default)
// so this Handler is associated with the UI thread
Handler mHandler = new Handler();
// I want to start doing something really long
// which means I should run the fella in another thread.
// I do that by sending a message - in the form of another runnable object
// But first, I'm going to create a Runnable object or a message for this
Runnable mRunnableOnSeparateThread = new Runnable() {
@Override
public void run () {
// do some long operation
longOperation();
// After mRunnableOnSeparateThread is done with it's job,
// I need to tell the user that i'm done
// which means I need to send a message back to the UI thread
// who do we know that's associated with the UI thread?
mHandler.post(new Runnable(){
@Override
public void run(){
// do some UI related thing
// like update a progress bar or TextView
// ....
}
});
}
};
// Cool but I've not executed the mRunnableOnSeparateThread yet
// I've only defined the message to be sent
// When I execute it though, I want it to be in a different thread
// that was the whole point.
new Thread(mRunnableOnSeparateThread).start();
}
}
如果根本就沒有Handler對象,回調(diào)post方法會比較難辦。
示例2:使用postDelayed方法
近期本站新介紹的特性中,我每次都要模擬EditText的自動完成功能,每次文字改變后都會觸發(fā)一個API的調(diào)用,從服務器中檢索數(shù)據(jù)。
我想減少APP調(diào)用API的次數(shù),所以決定使用Handler的postDelayed方法來實現(xiàn)這個功能。
本例不針對平行處理,只是關(guān)于Handler給消息隊列發(fā)送消息還有安排消息在未來的某一點執(zhí)行等。
// the below code is inside a TextWatcher
// which implements the onTextChanged method
// I've simplified it to only highlight the parts we're
// interested in
private long lastChange = 0;
@Override
public void onTextChanged(final CharSequence chars,
int start, int before, int count) {
// The handler is spawned from the UI thread
new Handler().postDelayed(
// argument 1 for postDelated = message to be sent
new Runnable() {
@Override
public void run() {
if (noChangeInText_InTheLastFewSeconds()) {
searchAndPopulateListView(chars.toString());
// logic
}
}
},
// argument 2 for postDelated = delay before execution
300);
lastChange = System.currentTimeMillis();
}
private boolean noChangeInText_InTheLastFewSeconds() {
return System.currentTimeMillis() - lastChange >= 300
}
最后我就把“postAtTime”這個方法作為聯(lián)系留給讀者們了,掌握Handler了嗎?如果是的話,那么可以盡情使用線程了。
1. Android進程
在了解Android線程之前得先了解一下Android的進程。當一個程序第一次啟動的時候,Android會啟動一個LINUX進程和一個主線程。默認的情況下,所有該程序的組件都將在該進程和線程中運行。同時,Android會為每個應用程序分配一個單獨的LINUX用戶。Android會盡量保留一個正在運行進程,只在內(nèi)存資源出現(xiàn)不足時,Android會嘗試停止一些進程從而釋放足夠的資源給其他新的進程使用, 也能保證用戶正在訪問的當前進程有足夠的資源去及時地響應用戶的事件。Android會根據(jù)進程中運行的組件類別以及組件的狀態(tài)來判斷該進程的重要性,Android會首先停止那些不重要的進程。按照重要性從高到低一共有五個級別:
前臺進程
前臺進程是用戶當前正在使用的進程。只有一些前臺進程可以在任何時候都存在。他們是最后一個被結(jié)束的,當內(nèi)存低到根本連他們都不能運行的時候。一般來說, 在這種情況下,設備會進行內(nèi)存調(diào)度,中止一些前臺進程來保持對用戶交互的響應。 可見進程
可見進程不包含前臺的組件但是會在屏幕上顯示一個可見的進程是的重要程度很高,除非前臺進程需要獲取它的資源,不然不會被中止。 服務進程
運行著一個通過startService() 方法啟動的service,這個service不屬于上面提到的2種更高重要性的。service所在的進程雖然對用戶不是直接可見的,但是他們執(zhí)行了用戶非常關(guān)注的任務(比如播放mp3,從網(wǎng)絡下載數(shù)據(jù))。只要前臺進程和可見進程有足夠的內(nèi)存,系統(tǒng)不會回收他們。 后臺進程
運行著一個對用戶不可見的activity(調(diào)用過 onStop() 方法).這些進程對用戶體驗沒有直接的影響,可以在服務進程、可見進程、前臺進 程需要內(nèi)存的時候回收。通常,系統(tǒng)中會有很多不可見進程在運行,他們被保存在LRU (least recently used) 列表中,以便內(nèi)存不足的時候被第一時間回收。如果一個activity正 確的執(zhí)行了它的生命周期,關(guān)閉這個進程對于用戶體驗沒有太大的影響。 空進程
未運行任何程序組件。運行這些進程的唯一原因是作為一個緩存,縮短下次程序需要重新使用的啟動時間。系統(tǒng)經(jīng)常中止這些進程,這樣可以調(diào)節(jié)程序緩存和系統(tǒng)緩存的平衡。
Android 對進程的重要性評級的時候,選取它最高的級別。另外,當被另外的一個進程依賴的時候,某個進程的級別可能會增高。一個為其他進程服務的進程永遠不會比被服務的進程重要級低。因為服務進程比后臺activity進程重要級高,因此一個要進行耗時工作的activity最好啟動一個service來做這個工作,而不是開啟一個子進程――特別是這個操作需要的時間比activity存在的時間還要長的時候。例如,在后臺播放音樂,向網(wǎng)上上傳攝像頭拍到的圖片,使用service可以使進程最少獲取到“服務進程”級別的重要級,而不用考慮activity目前是什么狀態(tài)。broadcast receivers做費時的工作的時候,也應該啟用一個服務而不是開一個線程。
2. 單線程模型
當一個程序第一次啟動時,Android會同時啟動一個對應的主線程(Main Thread),主線程主要負責處理與UI相關(guān)的事件,如用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事件,并把相關(guān)的事件分發(fā)到對應的組件進行處理。所以主線程通常又被叫做UI線程。在開發(fā)Android應用時必須遵守單線程模型的原則: Android UI操作并不是線程安全的并且這些操作必須在UI線程中執(zhí)行。
2.1 子線程更新UI
Android的UI是單線程(Single-threaded)的。為了避免拖住GUI,一些較費時的對象應該交給獨立的線程去執(zhí)行。如果幕后的線程來執(zhí)行UI對象,Android就會發(fā)出錯誤訊息CalledFromWrongThreadException。以后遇到這樣的異常拋出時就要知道怎么回事了!
2.2 Message Queue
在單線程模型下,為了解決類似的問題,Android設計了一個Message Queue(消息隊列), 線程間可以通過該Message Queue并結(jié)合Handler和Looper組件進行信息交換。下面將對它們進行分別介紹:
1. Message
Message消息,理解為線程間交流的信息,處理數(shù)據(jù)后臺線程需要更新UI,則發(fā)送Message內(nèi)含一些數(shù)據(jù)給UI線程。
2. Handler
Handler處理者,是Message的主要處理者,負責Message的發(fā)送,Message內(nèi)容的執(zhí)行處理。后臺線程就是通過傳進來的Handler對象引用來sendMessage(Message)。而使用Handler,需要implement 該類的handleMessage(Message)方法,它是處理這些Message的操作內(nèi)容,例如Update UI。通常需要子類化Handler來實現(xiàn)handleMessage方法。
3. Message Queue
Message Queue消息隊列,用來存放通過Handler發(fā)布的消息,按照先進先出執(zhí)行。
每個message queue都會有一個對應的Handler。Handler會向message queue通過兩種方法發(fā)送消息:sendMessage或post。這兩種消息都會插在message queue隊尾并按先進先出執(zhí)行。但通過這兩種方法發(fā)送的消息執(zhí)行的方式略有不同:通過sendMessage發(fā)送的是一個message對象,會被Handler的handleMessage()函數(shù)處理;而通過post方法發(fā)送的是一個runnable對象,則會自己執(zhí)行。
4. Looper
Looper是每條線程里的Message Queue的管家。Android沒有Global的Message Queue,而Android會自動替主線程(UI線程)建立Message Queue,但在子線程里并沒有建立Message Queue。所以調(diào)用Looper.getMainLooper()得到的主線程的Looper不為NULL,但調(diào)用Looper.myLooper()得到當前線程的Looper就有可能為NULL。
對于子線程使用Looper,API Doc提供了正確的使用方法:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare(); //創(chuàng)建本線程的Looper并創(chuàng)建一個MessageQueue
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop(); //開始運行Looper,監(jiān)聽Message Queue
}
}
這個Message機制的大概流程:
1. 在Looper.loop()方法運行開始后,循環(huán)地按照接收順序取出Message Queue里面的非NULL的Message。
2. 一開始Message Queue里面的Message都是NULL的。當Handler.sendMessage(Message)到Message Queue,該函數(shù)里面設置了那個Message對象的target屬性是當前的Handler對象。隨后Looper取出了那個Message,則調(diào)用該Message的target指向的Hander的dispatchMessage函數(shù)對Message進行處理。
在dispatchMessage方法里,如何處理Message則由用戶指定,三個判斷,優(yōu)先級從高到低:
1) Message里面的Callback,一個實現(xiàn)了Runnable接口的對象,其中run函數(shù)做處理工作;
2) Handler里面的mCallback指向的一個實現(xiàn)了Callback接口的對象,由其handleMessage進行處理;
3) 處理消息Handler對象對應的類繼承并實現(xiàn)了其中handleMessage函數(shù),通過這個實現(xiàn)的handleMessage函數(shù)處理消息。
由此可見,我們實現(xiàn)的handleMessage方法是優(yōu)先級最低的!
3. Handler處理完該Message (update UI) 后,Looper則設置該Message為NULL,以便回收!
在網(wǎng)上有很多文章講述主線程和其他子線程如何交互,傳送信息,最終誰來執(zhí)行處理信息之類的,個人理解是最簡單的方法——判斷Handler對象里面的Looper對象是屬于哪條線程的,則由該線程來執(zhí)行!
1. 當Handler對象的構(gòu)造函數(shù)的參數(shù)為空,則為當前所在線程的Looper;
2.Looper.getMainLooper()得到的是主線程的Looper對象,Looper.myLooper()得到的是當前線程的Looper對象。
現(xiàn)在來看一個例子,模擬從網(wǎng)絡獲取數(shù)據(jù),加載到ListView的過程:
public class ListProgressDemo extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listprogress);
((Button) findViewById(R.id.load_Handler)).setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
data = null;
data = new ArrayList<string>();
adapter = null;
showDialog(PROGRESS_DIALOG);
new ProgressThread(handler, data).start();
}
});
}
@Override
protected Dialog onCreateDialog(int id) {
switch(id) {
case PROGRESS_DIALOG:
return ProgressDialog.show(this, "",
"Loading. Please wait...", true);
default: return null;
}
}
private class ProgressThread extends Thread {
private Handler handler;
private ArrayList<string> data;
public ProgressThread(Handler handler, ArrayList<string> data) {
this.handler = handler;
this.data = data;
}
@Override
public void run() {
for (int i=0; i<8; i++) {
data.add("ListItem"); //后臺數(shù)據(jù)處理
try {
Thread.sleep(100);
}catch(InterruptedException e) {
Message msg = handler.obtainMessage();
Bundle b = new Bundle();
b.putInt("state", STATE_ERROR);
msg.setData(b);
handler.sendMessage(msg);
}
}
Message msg = handler.obtainMessage();
Bundle b = new Bundle();
b.putInt("state", STATE_FINISH);
msg.setData(b);
handler.sendMessage(msg);
}
}
// 此處甚至可以不需要設置Looper,因為Handler默認就使用當前線程的Looper
private final Handler handler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message msg) { // 處理Message,更新ListView
int state = msg.getData().getInt("state");
switch(state){
case STATE_FINISH:
dismissDialog(PROGRESS_DIALOG);
Toast.makeText(getApplicationContext(),
"加載完成!",
Toast.LENGTH_LONG)
.show();
adapter = new ArrayAdapter<string>(getApplicationContext(),
android.R.layout.simple_list_item_1,
data );
setListAdapter(adapter);
break;
case STATE_ERROR:
dismissDialog(PROGRESS_DIALOG);
Toast.makeText(getApplicationContext(),
"處理過程發(fā)生錯誤!",
Toast.LENGTH_LONG)
.show();
adapter = new ArrayAdapter<string>(getApplicationContext(),
android.R.layout.simple_list_item_1,
data );
setListAdapter(adapter);
break;
default:
}
}
};
private ArrayAdapter<string> adapter;
private ArrayList<string> data;
private static final int PROGRESS_DIALOG = 1;
private static final int STATE_FINISH = 1;
private static final int STATE_ERROR = -1;
}
這個例子,我自己寫完后覺得還是有點亂,要稍微整理才能看明白線程間交互的過程以及數(shù)據(jù)的前后變化。隨后了解到AsyncTask類,相應修改后就很容易明白了!
2.3 AsyncTask
AsyncTask版:
((Button) findViewById(R.id.load_AsyncTask)).setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
data = null;
data = new ArrayList<string>();
adapter = null;
//顯示ProgressDialog放到AsyncTask.onPreExecute()里
//showDialog(PROGRESS_DIALOG);
new ProgressTask().execute(data);
}
});
private class ProgressTask extends AsyncTask, Void, Integer> {
/* 該方法將在執(zhí)行實際的后臺操作前被UI thread調(diào)用??梢栽谠摲椒ㄖ凶鲆恍蕚涔ぷ鳎缭诮缑嫔巷@示一個進度條。*/
@Override
protected void onPreExecute() {
// 先顯示ProgressDialog
showDialog(PROGRESS_DIALOG);
}
/* 執(zhí)行那些很耗時的后臺計算工作。可以調(diào)用publishProgress方法來更新實時的任務進度。 */
@Override
protected Integer doInBackground(ArrayList<string>... datas) {
ArrayList<string> data = datas[0];
for (int i=0; i<8; i++) {
data.add("ListItem");
}
return STATE_FINISH;
}
/* 在doInBackground 執(zhí)行完成后,onPostExecute 方法將被UI thread調(diào)用,
* 后臺的計算結(jié)果將通過該方法傳遞到UI thread.
*/
@Override
protected void onPostExecute(Integer result) {
int state = result.intValue();
switch(state){
case STATE_FINISH:
dismissDialog(PROGRESS_DIALOG);
Toast.makeText(getApplicationContext(),
"加載完成!",
Toast.LENGTH_LONG)
.show();
adapter = new ArrayAdapter<string>(getApplicationContext(),
android.R.layout.simple_list_item_1,
data );
setListAdapter(adapter);
break;
case STATE_ERROR:
dismissDialog(PROGRESS_DIALOG);
Toast.makeText(getApplicationContext(),
"處理過程發(fā)生錯誤!",
Toast.LENGTH_LONG)
.show();
adapter = new ArrayAdapter<string>(getApplicationContext(),
android.R.layout.simple_list_item_1,
data );
setListAdapter(adapter);
break;
default:
}
}
Android另外提供了一個工具類:AsyncTask。它使得UI thread的使用變得異常簡單。它使創(chuàng)建需要與用戶界面交互的長時間運行的任務變得更簡單,不需要借助線程和Handler即可實現(xiàn)。
1) 子類化AsyncTask
2) 實現(xiàn)AsyncTask中定義的下面一個或幾個方法
onPreExecute() 開始執(zhí)行前的準備工作;
doInBackground(Params...) 開始執(zhí)行后臺處理,可以調(diào)用publishProgress方法來更新實時的任務進度;
onProgressUpdate(Progress...) 在publishProgress方法被調(diào)用后,UI thread將調(diào)用這個方法從而在界面上展示任務的進展情況,例如通過一個進度條進行展示。
onPostExecute(Result) 執(zhí)行完成后的操作,傳送結(jié)果給UI 線程。
這4個方法都不能手動調(diào)用。而且除了doInBackground(Params...)方法,其余3個方法都是被UI線程所調(diào)用的,所以要求:
1) AsyncTask的實例必須在UI thread中創(chuàng)建;
2) AsyncTask.execute方法必須在UI thread中調(diào)用;
同時要注意:該task只能被執(zhí)行一次,否則多次調(diào)用時將會出現(xiàn)異常。而且是不能手動停止的,這一點要注意,看是否符合你的需求!
在使用過程中,發(fā)現(xiàn)AsyncTask的構(gòu)造函數(shù)的參數(shù)設置需要看明白:AsyncTask
Params對應doInBackground(Params...)的參數(shù)類型。而new AsyncTask().execute(Params... params),就是傳進來的Params數(shù)據(jù),你可以execute(data)來傳送一個數(shù)據(jù),或者execute(data1, data2, data3)這樣多個數(shù)據(jù)。
Progress對應onProgressUpdate(Progress...)的參數(shù)類型;
Result對應onPostExecute(Result)的參數(shù)類型。
當以上的參數(shù)類型都不需要指明某個時,則使用Void,注意不是void。不明白的可以參考上面的例子,或者API Doc里面的例子。
相關(guān)文章
Android根據(jù)不同身份配置APP對應的不同模塊方法
今天小編就為大家分享一篇Android根據(jù)不同身份配置APP對應的不同模塊方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07
Android中實現(xiàn)延時執(zhí)行操作的方法小結(jié)
在Android開發(fā)中我們可能會有延時執(zhí)行某個操作的需求,這篇文章主要介紹了Android中實現(xiàn)延時執(zhí)行操作的幾種方法,需要的朋友可以參考下2018-10-10
Android 取消藍牙配對框?qū)崿F(xiàn)自動配對功能
這篇文章主要介紹了Android 取消藍牙配對框?qū)崿F(xiàn)自動配對功能,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-02-02
Android下拉刷新控件SwipeRefreshLayout源碼解析
這篇文章主要為大家詳細解析Android下拉刷新控件SwipeRefreshLayout源碼,感興趣的小伙伴們可以參考一下2016-07-07
Android 中 android.view.WindowLeaked的解決辦法
這篇文章主要介紹了Android 中 android.view.WindowLeaked的解決辦法的相關(guān)資料,需要的朋友可以參考下2017-05-05
利用Jetpack?Compose復刻游戲Flappy?Bird
Flappy?Bird是13年紅極一時的小游戲,其簡單有趣的玩法和變態(tài)的難度形成了強烈反差,引發(fā)全球玩家競相把玩!本文將通過Jetpack?Compose復刻這一游戲,感興趣的小伙伴可以了解一下2022-02-02

