轉(zhuǎn)載請注明出處:http://www.reibang.com/p/295a24564237
1 簡介##
在Android中,我們需要將一些耗時的操作放到子線程中漂彤,如果放在UI線程會造成主線程阻塞,出現(xiàn)ANR友驮。在子線程中處理耗時操作旨涝,需要進行更新UI蹬屹,一般的做法是通過Handler發(fā)送消息到UI線程進行處理,相對來說使用Handler的代碼比較臃腫白华。對于Handler的知識可以看這兩篇文章:
[Android Handler的基本使用]
[深入理解Handler哩治、Looper、Messagequeue]
Android 提供了一個異步任務(wù)類AsyncTask衬鱼,使創(chuàng)建異步任務(wù)业筏、更新UI變得更加簡單,不再需要編寫任務(wù)線程和Handler實例即可完成相同的任務(wù)鸟赫。
2 AsyncTask##
AsyncTask 允許在UI線程上執(zhí)行后臺操作和發(fā)布結(jié)果蒜胖,而不必操作Thread和Handler。
它是圍繞Thread和Handler設(shè)計的一個輔助類抛蚤,主要應(yīng)用于短操作(最多幾秒)台谢。如果想要運行長時間的任務(wù),強烈建議使用Executor岁经、ThreadPoolExecutor和FuterTask朋沮。
AsyncTask:一個在后臺線程上運行,而結(jié)果在UI線程上發(fā)布的任務(wù)缀壤。異步任務(wù)必須被繼承使用樊拓,子類至少覆蓋一個方法(doInBackground)。異步任務(wù)用到了3個泛型類型:
- Params:執(zhí)行任務(wù)時發(fā)送給任務(wù)的參數(shù)類型塘慕。
- Progress:后臺任務(wù)執(zhí)行期間筋夏,發(fā)布的進度、百分比图呢。
- Result:后臺計算的結(jié)果類型条篷。
異步任務(wù)中,并不是所有的泛型類型都要被使用蛤织,如果某個類型不需要使用赴叹,只需要使用Void類型就可以。如:
private class MyTask extends AsyncTask<Void, Void, Void>{....}指蚜。
2.1 任務(wù)執(zhí)行步驟
執(zhí)行一個異步任務(wù)需要經(jīng)歷4個步驟:
- onPreExecute():在UI線程上調(diào)用execute方法后乞巧,執(zhí)行onPreExecute(運行在UI線程上)。此步驟通常用于設(shè)置任務(wù)姚炕,例如在用戶界面顯示進度條摊欠。
- doInBackground(Params...):執(zhí)行完onPreExecute后丢烘,立即在后臺線程運行柱宦,此步驟可能需要長時間的后臺計算些椒。execute()參數(shù)也被傳到這步。計算結(jié)果必須由這步驟返回掸刊,并返回到最后一步免糕。這一步驟也可以使用publishprogress發(fā)布任務(wù)的進度,進度會被發(fā)送到onProgressUpdata忧侧。
- onProgressUpdate(Progress...):調(diào)用publishprogress后石窑,執(zhí)行該步驟(運行在UI線程),執(zhí)行時間是不確定的蚓炬。這個方法用于在后臺計算仍在執(zhí)行時在用戶界面顯示進度松逊。例如:用一個進度條顯示下載視頻的百分比。
- onPostExecute(Result):后臺計算(doInBackground)完成時肯夏,在UI線程運行該方法经宏。后臺計算結(jié)果作為一個參數(shù)傳遞到這步。
2.2 取消任務(wù)
一個任務(wù)可以隨時取消驯击,通過調(diào)用cancel(boolean),調(diào)用此方法將導(dǎo)致隨后使用iscancelled()返回ture烁兰。調(diào)用此方法后,在doInBackground執(zhí)行完后不再調(diào)用onPostExecute(Result)徊都,而是調(diào)用oncancelled(Result)沪斟。
2.3 線程規(guī)則
要想使AsyncTask類能正常工作,必須遵循一些線程規(guī)則:
- AsyncTask類必須在UI線程中加載暇矫。
- 任務(wù)實例必須在UI線程中創(chuàng)建主之。
- execute(Params...)必須在UI線程中調(diào)用。
- 不要手動調(diào)用onPreExecute()李根、doInBackground(Params...)杀餐、onProgressUpdate(Progress...)、onPostExecute(Result)朱巨。
- 一個任務(wù)實例只能執(zhí)行一次史翘,(多次執(zhí)行會拋出異常)。
2.4 執(zhí)行順序
在1.6之前冀续,AsyncTask是串行執(zhí)行任務(wù)的琼讽;
1.6的時候AsyncTask開始采用線程池里處理并行任務(wù);
但是從3.0開始洪唐,為了避免AsyncTask所帶來的并發(fā)錯誤钻蹬,AsyncTask又采用一個線程來串行執(zhí)行任務(wù)。
3 案例##
如下是通過使用AsyncTask下載一個段視頻的列子凭需,下載成功或失敗后設(shè)置按鈕內(nèi)容问欠,并在下載過程中更新下載進度肝匆。
先看下效果圖。
package cn.vn.asynctaskdemo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
public class MainActivity extends Activity implements OnClickListener {
private final static String TAG = "MainActivity";
public static final String VIDEO_DOWNLOAD_FOLDER = Environment
.getExternalStorageDirectory().toString();
private Button mExecuteBtn;
private ProgressBar mProgressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mExecuteBtn = (Button) findViewById(R.id.btn_execute);
mExecuteBtn.setOnClickListener(this);
mProgressBar = (ProgressBar) findViewById(R.id.progress);
}
@Override
public void onClick(View view) {
mProgressBar.setProgress(0);
//下載視頻
new DownloadTask().execute("http://120.25.226.186:32812/resources/videos/minion_05.mp4");
}
public class DownloadTask extends AsyncTask<String, Double, Boolean>{
@Override
protected void onPreExecute() {
Log.i(TAG,"onPreExecute ");
}
@Override
protected Boolean doInBackground(String... params) {
Log.i(TAG,"doInBackground params[0]="+params[0]);
HttpURLConnection conn = null;
try{
URL url = new URL(params[0]);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
conn.setRequestProperty("Connection", "Keep-Alive");
}catch(Exception e){
e.printStackTrace();
return false;
} //獲取文件長度
long videoTotalLength = conn.getContentLength();
File file = new File(VIDEO_DOWNLOAD_FOLDER, "test.mp4");
try { //創(chuàng)建File對象顺献,文件路徑為/sdcard/test.mp4
InputStream in = conn.getInputStream();
if (in == null)
return false;
//如果文件已經(jīng)存在旗国,并且第二個參數(shù)為true,則向原先的文件追加數(shù)據(jù)注整,否則重新寫能曾。
FileOutputStream os = new FileOutputStream(file, false);
byte[] buffer = new byte[4096];
int len = 0;
int current = 0;
while ((len = in.read(buffer)) != -1) {
current += len;
//更新進度
publishProgress((double) current,
(double) videoTotalLength);
os.write(buffer, 0, len);
}
os.close();
in.close();
} catch (Exception e) {
if (file.exists())
file.delete();
e.printStackTrace();
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Double... values) {
//設(shè)置ProgressBar進度
mProgressBar.setProgress((int) (values[0]/values[1]*100));
}
@Override
protected void onPostExecute(Boolean result) {
if(result){
Log.i(TAG,"下載文件成功");
mExecuteBtn.setText("下載成功");
}else{
Log.i(TAG,"下載文件失敗");
mExecuteBtn.setText("下載失敗");
}
}
}
}
該demo比較簡單,只有一個Activity——>MainActivity肿轨。該界面有一個Button和一個ProgressBar寿冕。點擊Button開始執(zhí)行異步任務(wù),下載網(wǎng)上視頻椒袍,并保存到本地文件中驼唱。打印信息如下:
06-23 11:14:50.890 I/MainActivity(28417): onPreExecute
06-23 11:14:50.890 I/MainActivity(28417): doInBackground params[0]=http://120.25.226.186:32812/resources/videos/minion_01.mp4
06-23 11:15:33.562 I/MainActivity(28417): 下載文件成功
第37行:創(chuàng)建DownloadTask實例,調(diào)用其execute方法驹暑,開始執(zhí)行任務(wù)玫恳。
開始執(zhí)行任務(wù),先走onPreExecute方法岗钩,然后進入doInBackground纽窟,這里面用來進行耗時操作,這里未在主線程運行兼吓,調(diào)用publishProgress來更新下載進度臂港,會調(diào)用onProgressUpdate。
任務(wù)下載完成(失敗或成功)视搏,走onPostExecute方法审孽。下一篇在分析下AsyncTask的源碼。
歡迎大家關(guān)注浑娜、評論佑力、點贊。
你們的支持是我堅持的動力筋遭。