編輯:關於android開發
android的異步任務體系中還有一個非常重要的操作類:AsyncTask,其內部主要使用的是java的線程池和Handler來實現異步任務以及與UI線程的交互。本文主要解析AsyncTask的的使用與源碼。
首先我們來看一下AsyncTask的基本使用:
class MAsyncTask extends AsyncTask {
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.i(TAG, "onPreExecute...(開始執行後台任務之前)");
}
@Override
protected void onPostExecute(Integer i) {
super.onPostExecute(i);
Log.i("TAG", "onPostExecute...(開始執行後台任務之後)");
}
@Override
protected Integer doInBackground(Integer... params) {
Log.i(TAG, "doInBackground...(開始執行後台任務)");
return 0;
}
}
我們定義了自己的MAsyncTask並繼承自AsyncTask;並重寫了其中的是哪個回調方法:onPreExecute(),onPostExecute(),doInBackground();
然後開始調用異步任務:
new MAsyncTask().execute();
好了,下面我們開始分析異步任務的執行過程,首先查看一下異步任務的構造方法:
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
mWorker = new WorkerRunnable() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};
mFuture = new FutureTask(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
咋一看AsyncTask的構造方法代碼量還是比較多的,但是仔細一看其實這裡面只是初始化了兩個成員變量:mWorker和mFuture他們分別是:WorkerRunnable和FutureTask,熟悉java的童鞋應該知道這兩個類其實是java裡面線程池先關的概念。其具體用法大家可以在網上查詢,這裡具體的細節不在表述,重點是對異步任務整體流程的把握。
總結:異步任務的構造方法主要用於初始化線程池先關的成員變量。
接下來我們看一下execute方法:
@MainThread
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
這裡發現該方法中添加一個@MainThread的注解,通過該注解,可以知道我們在執行AsyncTask的execute方法時,只能在主線程中執行,這裡可以實驗一下:
new Thread(new Runnable() {
@Override
public void run() {
Log.i("tag", Thread.currentThread().getId() + "");
new MAsyncTask().execute();
}
}).start();
Log.i("tag", "mainThread:" + Thread.currentThread().getId() + "");
然後執行,但是並沒有什麼區別,程序還是可以正常執行,我的手機的Android系統是Android5.0,具體原因尚未找到,歡迎有知道答案的童鞋可以相互溝通哈。但是這裡需要主要的一個問題是:onPreExecute方法是與開始執行的execute方法是在同一個線程中的,所以如果在子線程中執行execute方法,一定要確保onPreExecute方法不執行刷新UI的方法,否則:
@Override
protected void onPreExecute() {
super.onPreExecute();
title.setText("########");
Log.i(TAG, "onPreExecute...(開始執行後台任務之前)");
}
Process: com.example.aaron.helloworld, PID: 659
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6981)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1034)
at android.view.View.requestLayout(View.java:17704)
at android.view.View.requestLayout(View.java:17704)
at android.view.View.requestLayout(View.java:17704)
at android.view.View.requestLayout(View.java:17704)
at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:380)
at android.view.View.requestLayout(View.java:17704)
at android.widget.TextView.checkForRelayout(TextView.java:7109)
at android.widget.TextView.setText(TextView.java:4082)
at android.widget.TextView.setText(TextView.java:3940)
at android.widget.TextView.setText(TextView.java:3915)
at com.example.aaron.helloworld.MainActivity$MAsyncTask.onPreExecute(MainActivity.java:53)
at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:587)
at android.os.AsyncTask.execute(AsyncTask.java:535)
at com.example.aaron.helloworld.MainActivity$1$1.run(MainActivity.java:40)
at java.lang.Thread.run(Thread.java:818)
若在子線程中執行execute方法,那麼這時候如果在onPreExecute方法中刷新UI,會報錯,即子線程中不能更新UI。
繼續看剛才的execute方法,我們可以發現其內部調用了executeOnExecutor方法:
@MainThread
public final AsyncTask executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
可以看到其具體的內部實現方法裡:首先判斷當前異步任務的狀態,其內部保存異步任務狀態的成員變量mStatus的默認值為Status.PENDING,所以第一次執行的時候並不拋出這兩個異常,那麼什麼時候回進入這個if判斷並拋出異常呢,通過查看源代碼可以知道,當我們執行了execute方法之後,如果再次執行就會進入這裡的if條件判斷並拋出異常,這裡可以嘗試一下:
final MAsyncTask mAsyncTask = new MAsyncTask();
title.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/*MLog.e("you have clicked the title textview!!!");
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivityForResult(intent, 101);*/
new Thread(new Runnable() {
@Override
public void run() {
Log.i("tag", Thread.currentThread().getId() + "");
mAsyncTask
.execute();
}
}).start();
Log.i("tag", "mainThread:" + Thread.currentThread().getId() + "");
}
});
這裡我們可以看到我們定義了一個AsyncTask的對象,並且每次執行點擊事件的回調方法都會執行execute方法,當我們點擊第一次的時候程序正常執行,但是當我們執行第二次的時候,程序就崩潰了。若這時候第一次執行的異步任務尚未執行完成則會拋出異常:
Cannot execute task:the task is already running.
若第一次執行的異步任務已經執行完成,則會拋出異常:
Cannot execute task:the task has already been executed (a task can be executed only once)
繼續往下看,在executeOnExecutor中若沒有進入異常分之,則將當前異步任務的狀態更改為Running,然後回調onPreExecute()方法,這裡可以查看一下onPreExecute方法其實是一個空方法,主要就是為了用於我們的回調實現,同時這裡也說明了onPreExecute()方法是與execute方法的執行在同一線程中。
然後將execute方法的參數賦值給mWorker對象那個,最後執行exec.execute(mFuture)方法,並返回自身。
這裡我們重點看一下exec.execute(mFuture)的具體實現,這裡的exec其實是AsyncTask定義的一個默認的Executor對象:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
那麼,SERIAL_EXECUTOR又是什麼東西呢?
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
繼續查看SerialExecutor的具體實現:
private static class SerialExecutor implements Executor {
final ArrayDeque mTasks = new ArrayDeque();
Runnable mActive;
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
可以發現其繼承Executor類其內部保存著一個Runnable列表,即任務列表,在剛剛的execute方法中執行的exec.execute(mFuture)方法就是執行的這裡的execute方法。
這裡具體看一下execute方法的實現:
1)首先調用的是mTasks的offer方法,即將異步任務保存至任務列表的隊尾
2)判斷mActive對象是不是等於null,第一次運行是null,然後調用scheduleNext()方法
3)在scheduleNext()這個方法中會從隊列的頭部取值,並賦值給mActive對象,然後調用THREAD_POOL_EXECUTOR去執行取出的取出的Runnable對象。
4)在這之後如果再有新的任務被執行時就等待上一個任務執行完畢後才會得到執行,所以說同一時刻只會有一個線程正在執行。
5)這裡的THREAD_POOL_EXECUTOR其實是一個線程池對象。
然後我們看一下執行過程中mWorker的執行邏輯:
mWorker = new WorkerRunnable() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};
可以看到在執行線程池的任務時,我們回調了doInBackground方法,這也就是我們重寫AsyncTask時重寫doInBackground方法是後台線程的原因。
然後在任務執行完畢之後會回調我們的done方法:
mFuture = new FutureTask(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
這裡我們具體看一下postResultIfNotInvoked方法:
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
其內部還是調用了postResult方法:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult(this, result));
message.sendToTarget();
return result;
}
這裡可以看到起調用了內部的Handler對象的sendToTarget方法,發送異步消息
追蹤代碼,可以查看AsyncTask內部定義了一個Handler對象:
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void 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;
}
}
}
可以看到起內部的handleMessage方法,有兩個處理邏輯,分別是:更新進入條和執行完成,這裡的更新進度的方法就是我們重寫AsyncTask方法時重寫的更新進度的方法,這裡的異步任務完成的消息會調用finish方法:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
這裡AsyncTask首先會判斷當前任務是否被取消,若被取消的話則直接執行取消的方法,否則執行onPostExecute方法,也就是我們重寫AsyncTask時需要重寫的異步任務完成時回調的方法。
其實整個異步任務的大概流程就是這樣子的,其中涉及的知識點比較多,這裡總結一下:
異步任務內部使用線程池執行後台任務,使用Handler傳遞消息;
onPreExecute方法主要用於在異步任務執行之前做一些操作,它所在線程與異步任務的execute方法所在的線程一致,這裡若需要更新UI等操作,則execute方法不能再子線程中執行。
通過剛剛的源碼分析可以知道異步任務一般是順序執行的,即一個任務執行完成之後才會執行下一個任務。
doInBackground這個方法所在的進程為任務所執行的進程,在這裡可以進行一些後台操作。
異步任務執行完成之後會通過一系列的調用操作,最終回調我們的onPostExecute方法
異步任務對象不能執行多次,即不能創建一個對象執行多次execute方法。(通過execute方法的源碼可以得知)
所有源碼基於android23,中間有什麼疏漏歡迎指正。
Android中Service的一個Demo例子 Android中Service的一個Demo例子 Service組件是Android系統重要的一部分,網上看了代碼,很簡
Intent(三)向下一個活動傳遞數據,intent傳遞 向下傳遞活動很簡單,可以我采用putExtra()方法的重載,把
Intent屬性詳解三 data、type和extra,intentextra 1 Data 執行時要操作的數據 在目標<data/>標簽中包含了
Android中的Menu,AndroidMenuAndroid中的設置按鈕:長按或點擊菜單鍵 1.長按選項: 布局文件: 1 <LinearLayout x