android多執行緒-AsyncTaskyi(一)
今天分析android的非同步執行緒類HandlerThread與IntentService,它們都是android系統獨有的執行緒類,而android中還有另一個比較重要的非同步執行緒類AsyncTask。本文我們就來分析AsyncTask。
- AsyncTask的常規使用分析以及案例實現
- AsyncTask在不同android版本的下的差異
- AsyncTask的工作原理流程
一、AsyncTask的常規使用分析以及案例實現
AsyncTask是一種輕量級的非同步任務類,它可以線上程池中執行後臺任務,然後會把執行的進度和最終結果傳遞給主執行緒並更新UI。AsyncTask本身是一個抽象類它提供了Params、Progress、Result 三個泛型引數,其類宣告如下:
public abstract class AsyncTask<Params, Progress, Result> {
由類宣告可以看出AsyncTask抽象類確實定義了三種泛型型別 Params,Progress和Result,它們分別含義如下:
- Params :啟動任務執行的輸入引數,如HTTP請求的URL
- Progress : 後臺任務執行的百分比
- Result :後臺執行任務最終返回的結果型別
如果AsyncTask不需要傳遞具體引數,那麼這三個泛型引數可以使用Void代替。我們現在建立一個類繼承自AsyncTask如下:
package com.zejian.handlerlooper;
import android.graphics.Bitmap;
import android.os.AsyncTask;
/**
* Created by zejian
* Time 16/9/4.
* Description:
*/
public class DownLoadAsyncTask extends AsyncTask<String,Integer,Bitmap> {
/**
* onPreExecute是可以選擇性覆寫的方法
* 在主執行緒中執行,在非同步任務執行之前,該方法將會被呼叫
* 一般用來在執行後臺任務前對UI做一些標記和準備工作,
* 如在介面上顯示一個進度條。
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 抽象方法必須覆寫,執行非同步任務的方法
* @param params
* @return
*/
@Override
protected Bitmap doInBackground(String... params) {
return null;
}
/**
* onProgressUpdate是可以選擇性覆寫的方法
* 在主執行緒中執行,當後臺任務的執行進度發生改變時,
* 當然我們必須在doInBackground方法中呼叫publishProgress()
* 來設定進度變化的值
* @param values
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
/**
* onPostExecute是可以選擇性覆寫的方法
* 在主執行緒中執行,在非同步任務執行完成後,此方法會被呼叫
* 一般用於更新UI或其他必須在主執行緒執行的操作,傳遞引數bitmap為
* doInBackground方法中的返回值
* @param bitmap
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
}
/**
* onCancelled是可以選擇性覆寫的方法
* 在主執行緒中,當非同步任務被取消時,該方法將被呼叫,
* 要注意的是這個時onPostExecute將不會被執行
*/
@Override
protected void onCancelled() {
super.onCancelled();
}
}
如程式碼所示,我們建立一個繼承自AsyncTask的非同步執行緒類,在泛型引數方面,傳遞String型別(Url) , Integer型別(顯示進度),Bitmap型別作為返回值。接著重寫了抽象方法doInBackground(),以及覆寫了onPreExecute()、onProgressUpdate()、onPostExecute()、onCancelled()等方法,它們的主要含義如下:
- (1)onPreExecute(), 該方法在主執行緒中執行,將在execute(Params… params)被呼叫後執行,一般用來做一些UI的準備工作,如在介面上顯示一個進度條。
- (2)doInBackground(Params…params), 抽象方法,必須實現,該方法線上程池中執行,用於執行非同步任務,將在onPreExecute方法執行後執行。其引數是一個可變型別,表示非同步任務的輸入引數,在該方法中還可通過publishProgress(Progress… values)來更新實時的任務進度,而publishProgress方法則會呼叫onProgressUpdate方法。此外doInBackground方法會將計算的返回結果傳遞給onPostExecute方法。
- (3)onProgressUpdate(Progress…),在主執行緒中執行,該方法在publishProgress(Progress… values)方法被呼叫後執行,一般用於更新UI進度,如更新進度條的當前進度。
- (4)onPostExecute(Result), 在主執行緒中執行,在doInBackground 執行完成後,onPostExecute 方法將被UI執行緒呼叫,doInBackground 方法的返回值將作為此方法的引數傳遞到UI執行緒中,並執行一些UI相關的操作,如更新UI檢視。
- (5)onCancelled(),在主執行緒中執行,當非同步任務被取消時,該方法將被呼叫,要注意的是這個時onPostExecute將不會被執行。
我們這裡再強調一下它們的執行順序,onPreExecute方法先執行,接著是doInBackground方法,在doInBackground中如果呼叫了publishProgress方法,那麼onProgressUpdate方法將會被執行,最後doInBackground方法執行後完後,onPostExecute方法將被執行。說了這麼多,我們還沒說如何啟動AsyncTask呢,其實可以通過execute方法啟動非同步執行緒,其方法宣告如下:
public final AsyncTask<Params, Progress, Result> execute(Params... params)
該方法是一個final方法,引數型別是可變型別,實際上這裡傳遞的引數和doInBackground(Params…params)方法中的引數是一樣的,該方法最終返回一個AsyncTask的例項物件,可以使用該物件進行其他操作,比如結束執行緒之類的。啟動範例如下:
new DownLoadAsyncTask().execute(url1,url2,url3);
當然除了以上介紹的內容外,我們在使用AsyncTask時還必須遵守一些規則,以避免不必要的麻煩。
- (1) AsyncTask的例項必須在主執行緒(UI執行緒)中建立 ,execute方法也必須在主執行緒中呼叫
- (2) 不要在程式中直接的呼叫onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)這幾個方法
- (3) 不能在doInBackground(Params… params)中更新UI
- (5) 一個AsyncTask物件只能被執行一次,也就是execute方法只能呼叫一次,否則多次呼叫時將會丟擲異常
到此,AsyncTask的常規方法說明和使用以及注意事項全部介紹完了,下面我們來看一個下載案例,該案例是去下載一張大圖,並實現下載實時進度。先來看看AsynTaskActivity.java的實現:
package com.zejian.handlerlooper;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.PowerManager;
import android.widget.Toast;
import com.zejian.handlerlooper.util.LogUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Created by zejian
* Time 16/9/4.
* Description:
*/
public class DownLoadAsyncTask extends AsyncTask<String, Integer, String> {
private PowerManager.WakeLock mWakeLock;
private int ValueProgress=100;
private Context context;
public DownLoadAsyncTask(Context context){
this.context=context;
}
/**
* sync method which download file
* @param params
* @return
*/
@Override
protected String doInBackground(String... params) {
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
try {
URL url = new URL(params[0]);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
// expect HTTP 200 OK, so we don't mistakenly save error report
// instead of the file
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return "Server returned HTTP " + connection.getResponseCode()
+ " " + connection.getResponseMessage();
}
// this will be useful to display download percentage
// might be -1: server did not report the length
int fileLength = connection.getContentLength();
// download the file
input = connection.getInputStream();
//create output
output = new FileOutputStream(getSDCardDir());
byte data[] = new byte[4096];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
// allow canceling with back button
if (isCancelled()) {
input.close();
return null;
}
total += count;
// publishing the progress....
if (fileLength > 0) // only if total length is known
publishProgress((int) (total * 100 / fileLength));
//
Thread.sleep(100);
output.write(data, 0, count);
}
} catch (Exception e) {
return e.toString();
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
} catch (IOException ignored) {
}
if (connection != null)
connection.disconnect();
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
// take CPU lock to prevent CPU from going off if the user
// presses the power button during download
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
mWakeLock.acquire();
//Display progressBar
// progressBar.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(String values) {
super.onPostExecute(values);
mWakeLock.release();
if (values != null)
LogUtils.e("Download error: "+values);
else {
Toast.makeText(context, "File downloaded", Toast.LENGTH_SHORT).show();
}
}
/**
* set progressBar
* @param values
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// progressBar.setmProgress(values[0]);
//update progressBar
if(updateUI!=null){
updateUI.UpdateProgressBar(values[0]);
}
}
/**
* get SD card path
* @return
*/
public File getSDCardDir(){
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
// 建立一個資料夾物件,賦值為外部儲存器的目錄
String dirName = Environment.getExternalStorageDirectory()+"/MyDownload/";
File f = new File(dirName);
if(!f.exists()){
f.mkdir();
}
File downloadFile=new File(f,"new.jpg");
return downloadFile;
}
else{
LogUtils.e("NO SD Card!");
return null;
}
}
public UpdateUI updateUI;
public interface UpdateUI{
void UpdateProgressBar(Integer values);
}
public void setUpdateUIInterface(UpdateUI updateUI){
this.updateUI=updateUI;
}
}
簡單說明一下程式碼,在onPreExecute方法中,可以做了一些準備工作,如顯示進度圈,這裡為了演示方便,進度圈在常態下就是顯示的,同時,我們還鎖定了CPU,防止下載中斷,而在doInBackground方法中,通過HttpURLConnection物件去下載圖片,然後再通過int fileLength =connection.getContentLength();
程式碼獲取整個下載圖片的大小並使用publishProgress((int) (total * 100 / fileLength));
更新進度,進而呼叫onProgressUpdate方法更新進度條。最後在onPostExecute方法中釋放CPU鎖,並通知是否下載成功。接著看看Activity的實現:
activity_download.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:customView="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zejian.handlerlooper.util.LoadProgressBarWithNum
android:id="@+id/progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
customView:progress_radius="100dp"
android:layout_centerInParent="true"
customView:progress_strokeWidth="40dp"
customView:progress_text_size="35sp"
customView:progress_text_visibility="visible"
customView:progress_value="0"
/>
<Button
android:id="@+id/downloadBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start download"
android:layout_centerHorizontal="true"
android:layout_below="@id/progressbar"
android:layout_marginTop="40dp"
/>
</RelativeLayout>
AsynTaskActivity.java
package com.zejian.handlerlooper;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.Button;
import com.zejian.handlerlooper.util.LoadProgressBarWithNum;
import com.zejian.handlerlooper.util.LogUtils;
/**
* Created by zejian
* Time 16/9/4.
* Description:AsynTaskActivity
*/
public class AsynTaskActivity extends Activity implements DownLoadAsyncTask.UpdateUI {
private static int WRITE_EXTERNAL_STORAGE_REQUEST_CODE=0x11;
private static String DOWNLOAD_FILE_JPG_URL="http://img2.3lian.com/2014/f6/173/d/51.jpg";
private LoadProgressBarWithNum progressBar;
private Button downloadBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download);
progressBar= (LoadProgressBarWithNum) findViewById(R.id.progressbar);
downloadBtn= (Button) findViewById(R.id.downloadBtn);
//create DownLoadAsyncTask
final DownLoadAsyncTask downLoadAsyncTask= new DownLoadAsyncTask(AsynTaskActivity.this);
//set Interface
downLoadAsyncTask.setUpdateUIInterface(this);
//start download
downloadBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//execute
downLoadAsyncTask.execute(DOWNLOAD_FILE_JPG_URL);
}
});
//android 6.0 許可權申請
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//android 6.0 API 必須申請WRITE_EXTERNAL_STORAGE許可權
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
WRITE_EXTERNAL_STORAGE_REQUEST_CODE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
doNext(requestCode,grantResults);
}
private void doNext(int requestCode, int[] grantResults) {
if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted
LogUtils.e("Permission Granted");
} else {
// Permission Denied
LogUtils.e("Permission Denied");
}
}
}
/**
* update progressBar
* @param values
*/
@Override
public void UpdateProgressBar(Integer values) {
progressBar.setmProgress(values);;
}
}
在AsynTaskActivity中實現了更新UI的介面DownLoadAsyncTask.UpdateUI,用於更新主執行緒的progressBar的進度,由於使用的測試版本是android6.0,涉及到外部SD卡讀取許可權的申請,所以在程式碼中對SD卡許可權進行了特殊處理(這點不深究,不明白可以google一下),LoadProgressBarWithNum是一個自定義的進度條控制元件。ok~,最後看看我們的執行結果:
效果符合預期,通過這個案例,相信我們對AsyncTask的使用已相當清晰了。基本使用到此,然後再來聊聊AsyncTask在不同android版本中的差異。
二、AsyncTask在不同android版本的下的差異
這裡我們主要區分一下android3.0前後版本的差異,在android 3.0之前,AsyncTask處理任務時預設採用的是執行緒池裡並行處理任務的方式,而在android 3.0之後 ,為了避免AsyncTask處理任務時所帶來的併發錯誤,AsyncTask則採用了單執行緒序列執行任務。但是這並不意味著android 3.0之後只能執行序列任務,我們仍然可以採用AsyncTask的executeOnExecutor方法來並行執行任務。接下來,編寫一個案例,分別在android 2.3.3 和 android 6.0上執行,然後列印輸出日誌。程式碼如下:
package com.zejian.handlerlooper;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.zejian.handlerlooper.util.LogUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by zejian
* Time 16/9/5.
* Description:
*/
public class ActivityAsyncTaskDiff extends Activity {
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_diff);
btn= (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AysnTaskDiff("AysnTaskDiff-1").execute("");
new AysnTaskDiff("AysnTaskDiff-2").execute("");
new AysnTaskDiff("AysnTaskDiff-3").execute("");
new AysnTaskDiff("AysnTaskDiff-4").execute("");
new AysnTaskDiff("AysnTaskDiff-5").execute("");
}
});
}
private static class AysnTaskDiff extends AsyncTask<String ,Integer ,String>{
private String name;
public AysnTaskDiff(String name){
super();
this.name=name;
}
@Override
protected String doInBackground(String... params) {
try {
Thread.sleep(2000);
}catch (Exception ex){
ex.printStackTrace();
}
return name;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
LogUtils.e(s+" execute 執行完成時間:"+df.format(new Date()));
}
}
}
案例程式碼比較簡單,不過多分析,我們直接看在android 2.3.3 和 android 6.0上執行的結果,其中android 2.3.3上執行Log列印如下:
在 android 6.0上執行Log列印如下:
從列印log可以看出AsyncTask在android 2.3.3上確實是並行執行任務的,而在 android 6.0上則是序列執行任務。那麼瞭解這點有什麼用呢?其實以前我也只是知道這回事而已,不過最近在SDK開發中遇到了AsyncTask的開發問題,產生問題的場景是這樣的,我們團隊在SDK中使用了AsyncTask作為網路請求類,因為現在大部分系統都是在Android 3.0以上的系統執行的,所以預設就是序列執行,一開始SDK在海外版往外提供也沒有出現什麼問題,直到後面我們提供國內一個publisher海外版本時,問題就出現了,該publisher接入我們的SDK後,他們的應用網路載入速度變得十分慢,後來他們一直沒排查出啥問題,我們這邊也在懵逼中……直到我們雙方都找到一個點,那就是publisher的應用和我們的SDK使用的都是AsyncTask作為網路請求,那麼問題就來,我們SDK是在在Application啟動時觸發網路的,而他們的應用也是啟動Activity時去訪問網路,所以SDK比應用先載入網路資料,但是!!!AsyncTask預設是序列執行的,所以!!只有等我們的SDK網路載入完成後,他們應用才開始載入網路資料,這就造成應用的網路載入延遲十分嚴重了。後面我們SDK在內部把AsyncTask改為並行任務後問題也就解決了(當然這也是SDK的一個BUG,考慮欠佳)。在Android 3.0之後我們可以通過下面程式碼讓AsyncTask執行並行任務,其AsyncTask.THREAD_POOL_EXECUTOR為AsyncTask的內部執行緒池。
new AysnTaskDiff("AysnTaskDiff-5").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
第一個引數傳遞是執行緒池,一般使用AsyncTask內部提供的執行緒池即可(也可以自己建立),第二個引數,就是最終會傳遞給doInBackground方法的可變引數,這裡不傳,所以直接給了空白符。執行效果就不再演示了,大家可以自行測試一下。
到此AsyncTask在不同android版本中的差異也分析的差不多了。
本文轉自:http://blog.csdn.net/javazejian/article/details/52462830如有侵權,請聯絡刪除。