android異步任務(wù)設(shè)計(jì)思詳解(AsyncTask)
這里說有設(shè)計(jì)思想是我根據(jù)查看Android源代碼提煉出來的代碼邏輯,所以不會(huì)跟Google工程師的原始設(shè)計(jì)思想100%符合(也有可能是0%),但是本文一定可以幫助你理解AsyncTask,也可能有一些你以前沒有發(fā)現(xiàn)的內(nèi)容。
大家都知道,Android的主線程(又叫UI線程,線程ID為1)有一些限制策略,使得主線程有些事做不了,比如訪問網(wǎng)絡(luò)就不允許,否則就是報(bào),但在2.3之后的版本,你可以通過添加以下代碼更改其限制策略,從而強(qiáng)制使得主線程可以訪問網(wǎng)絡(luò):
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
不過StrictMode是一個(gè)開發(fā)工具主要用于偵測主線程中的磁盤和網(wǎng)絡(luò)訪問,而不是讓你做這“壞”事,其實(shí)Android這樣限制是有好處的,強(qiáng)制讓開發(fā)者重視用戶體驗(yàn),一個(gè)反面例子是Windows,主線程里什么都可以做,一些很懶的開發(fā)者就把所有任務(wù)放到主線程里,使得線程經(jīng)常好卡,比如編輯器UE或Notepad++打開了一個(gè)網(wǎng)絡(luò)上(如samba服務(wù)器)的文件,如果突然網(wǎng)絡(luò)中斷了,那你的整個(gè)編輯器都卡住,要等好久才會(huì)有反應(yīng),不過我不確定那是不是因?yàn)橹骶€程里訪問了網(wǎng)絡(luò),不過Windows經(jīng)常因?yàn)檫@個(gè)原因卡。還有一個(gè)正面例子是iOS,極其注意響應(yīng)速度,所以當(dāng)有用戶輸入事件時(shí),其內(nèi)核都有相應(yīng)的調(diào)度,從而優(yōu)先響應(yīng)用戶操作。
還是回到正題,就是因?yàn)橹骶€程的這些限制使開發(fā)者不得不寫多個(gè)線程,當(dāng)然,你也可以不用AsyncTask,不過你不用也避免不了多線程,如果你不用,就是可能要用Handler和Thread了,我想很多人初學(xué)的時(shí)候就是那么干的,包括我,因?yàn)槟菚r(shí)很有可能還沒有發(fā)現(xiàn)有這個(gè)類,于是就經(jīng)常寫Handler和Thread的了,寫著寫著就發(fā)現(xiàn)有一些代碼是相同的,你寫的Handler和Thread匿名類肯定是重復(fù)代碼,如下:
[final Handler handler = new Handler() {
public void handleMessage(Message msg) {
System.out.println("The hard have done!");
// ... front end code
}
};
new Thread() {
public void run() {
doHardWork();
handler.sendEmptyMessage(0);
}
private void doHardWork() {
// ... back end code
}
}.start();
你可能想到要復(fù)用這些代碼,當(dāng)然,你可以通過Copy的方式來復(fù)用這段代碼,用的時(shí)候只要在省略號處寫入你的代碼就可以了,但更好的復(fù)用是將其用一個(gè)類封裝起來,好吧,那我們就簡單的封裝一下吧,于是,就變成了這樣:
public class HandlerAndThread {
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
System.out.println("The hard have done!");
//...
}
};
public void doInBackground() {
new Thread() {
public void run() {
doHardWork();
handler.sendEmptyMessage(0);
}
private void doHardWork() {
// ...
}
};
}
}
這樣好像還不行,因?yàn)闊o法告訴后臺線程做什么事,做完了也不知道通知,要復(fù)用還是得Copy代碼,我們可以加兩個(gè)方法,一個(gè)在前臺調(diào)用一個(gè)在后臺調(diào)用,只要定義一個(gè)新類就可以實(shí)現(xiàn)復(fù)用,于是代碼變成這樣:
public class HandlerAndThread {
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
System.out.println("The hard have done!");
runInFrontend(); // added
}
};
public void doInBackground() {
new Thread() {
public void run() {
doHardWork();
handler.sendEmptyMessage(0);
}
private void doHardWork() {
runInBackend(); //added
}
};
}
//added
protected void runInBackend() {
}
//added
protected void runInFrontend() {
}
}
一個(gè)可復(fù)用的類就出爐了,我們寫一個(gè)子類,并用一個(gè)Activity來調(diào)用一下吧:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new SubHandlerAndThread().doInBackground();
}
class SubHandlerAndThread extends HandlerAndThread {
protected void runInBackend() {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
protected void runInFrontend() {
System.out.println("Task has been done");
}
}
}
這樣是不是比直接寫Thread和Handler簡潔了許多呢,這里我是用sleep來模似長時(shí)間事務(wù)的,如果在真實(shí)的環(huán)境中,我們可能是要下載,如果是下載,我們可能希望傳入個(gè)下載地址的參數(shù)到后臺線程,來讓他按我們的需要下載,我們給加doInBackground方法加一個(gè)參數(shù),于是HandlerAndThread類的代碼就變成這樣:
public class HandlerAndThread {
...
public void doInBackground(final String url) { // added url
new Thread() {
public void run() {
doHardWork();
handler.sendEmptyMessage(0);
}
private void doHardWork() {
runInBackend(url); // added url
}
};
}
protected void runInBackend(String url) { // added url
}
...
}
而調(diào)用類的代碼變成這樣:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String url = "http://path/to/file";
new SubHandlerAndThread().doInBackground(url); //added url
}
class SubHandlerAndThread extends HandlerAndThread {
@Override
protected void runInBackend(String url) { // added url
System.out.println("Start download from url:" + url);
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected void runInFrontend() {
System.out.println("finish download");
}
}
}
假如是下一個(gè)文件呢,我們是不是加一個(gè)進(jìn)度更新的方法呢,于是又變成這樣:
public class HandlerAndThread {
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) { // added
case 0:
runInFrontend();
break;
case 1:
runInFrontendProgress(msg.arg1);
break;
}
}
};
...
final protected void publishProgress(int progress) { // added
handler.obtainMessage(1, progress, 0);
}
protected void runInFrontendProgress(int progress) { // added
}
}
public class MainActivity extends Activity {
...
class SubHandlerAndThread extends HandlerAndThread {
@Override
protected void runInBackend(String url) {
System.out.println("Start download from url:" + url);
for (int i = 0; i < 10; ++i) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrack();
}
publishProgress(i*10); // added
}
}
...
@Override
protected void runInFrontendProgress(int progress) { // added
System.out.println("Progress: " + progress);
}
}
}
你可能已經(jīng)沒有耐心一版一版的進(jìn)化了,那我就跳躍一下吧,一次多加幾條需要:一、我們下載完了可能要得到文件的路徑,所以我們給runInFrontend方法加一個(gè)輸入?yún)?shù)filePath表示路徑;二、把子類必須實(shí)現(xiàn)的方法改成抽象方法,類也改成抽象方法;我把代碼中的一些方法名改一下,使其更好理解,把doInBackground改為execute,把runInFrontend改為onPostExecute,把runInFrontendProgress改為onProgressUpdate。最終版如下:
public abstract class HandlerAndThread {
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
onPostExecute((String) msg.obj);
break;
case 1:
onProgressUpdate(msg.arg1);
break;
}
}
};
public void doInBackground(final String url) {
new Thread() {
public void run() {
String result = runInBackend(url);
handler.obtainMessage(0, result);
}
};
}
final protected void publishProgress(int progress) {
handler.obtainMessage(1, progress, 0);
}
abstract protected String runInBackend(String url);
protected void onPostExecute(String filePath) { }
protected void onProgressUpdate(int progress) { }
}
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String url = "http://path/to/file";
new SubHandlerAndThread().doInBackground(url);
}
class SubHandlerAndThread extends HandlerAndThread {
@Override
protected String runInBackend(String url) {
System.out.println("Start download from url:" + url);
for (int i = 0; i < 10; ++i) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
publishProgress(i*10);
}
return "/path/to/file";
}
@Override
protected void onPostExecute(String filePath) {
System.out.println("Download finished");
}
@Override
protected void onProgressUpdate(int progress) {
System.out.println("Progress: " + progress);
}
}
}
這是不是跟Android的AsyncTask很像呢,我想Google就是由于這種需求做出這個(gè)類的,Android官網(wǎng)是這樣描述AsyncTask的:
This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
意思是這個(gè)類使得:不使用Thread和Handler,就可以在后臺執(zhí)行操作然后在發(fā)布結(jié)果到UI線程。其實(shí)他內(nèi)部的實(shí)現(xiàn)就是封裝了Thread和Handler,所以你就不必要直接用這兩個(gè)低層類了,但他的目的也是代碼復(fù)用,他的實(shí)現(xiàn)跟我們上面寫的類也差不多。主要有這幾點(diǎn)不同:一、AsyncTask使用了線程池而不是單個(gè)線程去執(zhí)行后臺任務(wù),該線程池是整個(gè)進(jìn)程共用的,是因?yàn)樗木€程池對象是一個(gè)靜態(tài)成員變量,這一點(diǎn)很多人搞錯(cuò),誤以為AsyncTask越來創(chuàng)建的線程越多,這不是絕對正確的,因?yàn)榫€程池會(huì)根據(jù)負(fù)載動(dòng)態(tài)調(diào)整的,而且有最大值和空閑超時(shí),AsyncTask的配置是最小5,最大128,空閑超時(shí)1秒,當(dāng)然你也可以配置成線程數(shù)根據(jù)任務(wù)數(shù)線程遞增,關(guān)于線程池,可參考這里,后續(xù)我會(huì)在博客寫文章討論Java線程池;二、AsyncTask的輸入和輸出參數(shù)使用了泛型;三、AsyncTask支持中斷當(dāng)前任務(wù)。
現(xiàn)在知道了AsyncTask的設(shè)計(jì)思想了吧,是不是很簡單呢,所以建議童鞋們?nèi)タ匆幌滤脑创a,反正我寫代碼時(shí)有查看源碼的習(xí)慣,因?yàn)槲視?huì)好奇它是如何實(shí)現(xiàn)的,看源碼有很多好處,如可以學(xué)到好的API設(shè)計(jì)思想,軟件架構(gòu)。
相關(guān)文章
TextInputLayout輸入框控件的懸浮標(biāo)簽
這篇文章主要為大家詳細(xì)介紹了TextInputLayout輸入框控件的懸浮標(biāo)簽,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12androidstudio3.0使用butterknife報(bào)錯(cuò)解決的解決方法
這篇文章主要介紹了androidstudio3.0使用butterknife報(bào)錯(cuò)解決的解決方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-01-01Android開發(fā)之OkHttpUtils的具體使用方法
這篇文章主要介紹了Android開發(fā)之OkHttpUtils的具體使用方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-08-08Android實(shí)現(xiàn)收到新短信后自動(dòng)發(fā)郵件功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)收到新短信后自動(dòng)發(fā)郵件功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-052014值得推薦的10個(gè)移動(dòng) Web 應(yīng)用程序開發(fā)框架
今天這篇文章向大家推薦10大優(yōu)秀的移動(dòng) Web 開發(fā)框架,幫助開發(fā)者更加高效的開發(fā)移動(dòng)Web應(yīng)用。2014-08-08