一.AsyncTask分析
AsyncTask本質是上只是一個框架,內部封裝了Thread+Handler機制,不需要控制線程和Handler,可以輕松實現后臺計算,發布進度和結果到主線程。需要注意的是
1.The task can be executed only once
2.The task instance must be created on the UI thread
3.execute must be invoked on the UI thread
1.AsyncTask用例如下:
* private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
* protected Long doInBackground(URL... urls) {
* int count = urls.length;
* long totalSize = 0;
* for (int i = 0; i < count; i++) {
* totalSize += Downloader.downloadFile(urls[i]);
* publishProgress((int) ((i / (float) count) * 100));
* // Escape early if cancel() is called
* if (isCancelled()) break;
* }
* return totalSize;
* }
*
* protected void onProgressUpdate(Integer... progress) {
* setProgressPercent(progress[0]);
* }
*
* protected void onPostExecute(Long result) {
* showDialog("Downloaded " + result + " bytes");
* }
* }
new DownloadFilesTask().execute(url1, url2, url3);
AsyncTask內部流程如下:
When an asynchronous task is executed, the task goes through 4 steps:
- onPreExecute, invoked on the UI thread before the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.
- doInBackground, invoked on the background thread immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate step.
- onProgressUpdate, invoked on the UI thread after a call to publishProgress. The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.
- onPostExecute, invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.
基本如下操作:AsyncTask框架有三個泛型三個,分別是輸入的多個參數、處理中的進度、處理后的結果;excute操作時, onPreExecute在主線程做執行前操作,doInBackground做后臺操作,在操作的同時發布Progress進度,回調在onProgressUpdate中,這是主線程,doInBackground return的結果回調在onPostExecute中。實例化的AsyncTask通過execute
2.在基于這樣的大致框架下,我們如何自行實現以上功能?
本質上就是在execute時,起一個工作線程,執行后臺計算。工作線程執行前,調用onPreExecute;通過主線程looper起一個Handler,用于主線程與工作線程的通信。執行在publishProgress方法中handler.sendmessage,在handler.handleMessage調用onProgressUpdate,在工作線程結束后handler.sendmessage,計算結果放在obj屬性,handler.handleMessage調用onPostExecute即可。
3.源碼分析
AsyncTask框架內處理后臺任務,是要起線程的。其內部線程創建是通過線程池的,即單例的線程池,在第一次AsyncTask類加載時,便會初始化(static方法塊);在初始化類時,具體走構造器時,會初始化Handler與一個FutureTask(為什么要用FutureTask執行異步任務,是為了獲得異步計算的結果,而且FutureTask也是一個可取消的異步計算,Runable或者繼承Thread做不到這些特性)
這里拓展一下,談一下線程創建
Java線程創建本身都是通過實例化Thread去做的,至于如何實例化Thead有以下幾種方式(如何實現run內部邏輯代碼)
- 繼承Thread類實現多線程
* class PrimeThread extends Thread {
* long minPrime;
* PrimeThread(long minPrime) {
* this.minPrime = minPrime;
* }
*
* public void run() {
* // compute primes larger than minPrime
* . . .
* }
* }
* PrimeThread p = new PrimeThread(143);
* p.start();
- 實現Runnable()接口實現多線程,而后同樣覆寫run()
* class PrimeRun implements Runnable {
* long minPrime;
* PrimeRun(long minPrime) {
* this.minPrime = minPrime;
* }
*
* public void run() {
* // compute primes larger than minPrime
* . . .
* }
* }
* PrimeRun p = new PrimeRun(143);
* new Thread(p).start();
- 實現Callable接口(本質也是Runnable方式,不同的是有后臺計算的返回值)
* class PrimeRun implements Callable<String> {
*
* public String call() throws Exception{
* // compute primes larger than minPrime
* . . .
* return "demo";
* }
* }
* PrimeRun callable = new PrimeRun();
* new Thread(new FutureTask<>(callable)).start();
FutureTask is a cancellable asynchronous computation,即FutureTask是一個可取消的異步計算,本身實現了Runnable、Future接口,實現了Runnable的run方法(有了線程異步的能力),實現了Future接口的get,cancel,isCancelled,isDone方法(有了可取消的異步計算結果的能力)
AsyncTask有三個狀態如下:
/**
* Indicates the current status of the task. Each status will be set only once
* during the lifetime of a task.
*/
public enum Status {
/**
* Indicates that the task has not been executed yet.
*/
PENDING,
/**
* Indicates that the task is running.
*/
RUNNING,
/**
* Indicates that {@link AsyncTask#onPostExecute} has finished.
*/
FINISHED,
}
如備注所說,這個狀態的功能就是為了讓每個任務只執行一次。其中PENDING指的是任務還未被執行,RUNNING指的是任務執行運行中,FINISHED指的是onPostExecute方法已結束
返回到AsyncTask構造器講解,源碼如下:
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(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);
}
}
};
}
這里初始化了FutureTask,異步執行的是doInBackground,現將mTaskInvoked愿子變量置為true,代表任務已被調用。doInBackground結束后,send一個MESSAGE_POST_RESULT的Message,handler接受到消息,執行finish方法,任務結束后執行postResultIfNotInvoked,該方法會檢查mTaskInvoked是否為false,即任務未被調用的話,也會send一個MESSAGE_POST_RESULT的Message
finish方法源碼如下:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
isCancelled的原子變量為true的情況要么是用戶調用cancle方法,要么doInBackground發生異常。finsh方法判斷AsyncTask是否可取消,如果可取消,調用onCancelled,不能取消則執行onPostExecute。總是將AsyncTask的狀態置為Finished。
以上都初始化好了之后,當用戶調用excute方法,excute方法會調用executeOnExecutor,不僅要傳入params可變參數,還有傳入執行器,為什么要傳入執行器呢?其實AsyncTask本質是一個線程,不過是加了Handler的線程,AsyncTask內部維護了一個線程池,當用戶起了多個AsyncTask,這個時候,就要考慮AsyncTask內部線程該如何調用;系統默認的是串行執行器,源碼如下:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
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);
}
}
}
由源碼可知,系統默認對于多個AsyncTask采用了串行執行器,即一個線程執行完之后執行雙端隊列的下一個線程
executeOnExecutor源碼如下:
@MainThread
public final AsyncTask<Params, Progress, Result> 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;
}
在excute執行方法內可知,AsyncTask為了只執行一個Task,通過狀態判斷,只要狀態執行中,或者已結束,就拋出異常。之后將狀態置為RUNNING狀態,在調用onPreExecute,將可變參數傳給workrunbale對象中,之后通過執行器執行在構造器時已初始化的futuretask的線程。
4.重談AsyncTask.cancle方法
AsyncTask類cancle方法,除了將原子變量mCancelled置為true,還執行了FutureTask的cancle方法,FutureTask的cancle源碼如下:
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
U.compareAndSwapInt(this, STATE, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
其實canle方法最終調用了線程的interrupt方法;線程的interrupt()方法,根據字面意思,很容易將該方法理解為中斷線程。其實Thread.interrupt()并不會中斷線程的運行,它的作用僅僅是為線程設定一個狀態而已,即標明線程是中斷狀態,這樣線程的調度機制或我們的代碼邏輯就可以通過判斷這個狀態做一些處理,比如sleep()方法會拋出異常,或是我們根據isInterrupted()方法判斷線程是否處于中斷狀態,然后做相關的邏輯處理。
由以上分析可知,當用戶調用AsyncTask的cancle方法時,onBackgroud所做的耗時任務,只要正在進行中,依然還會進行。所以onBackgroud內還要判斷AsyncTask.isCancle方法
AsyncTask內部是用Thread+Handler實現,那與HandlerThred甚是相似,它與AsyncTask有什么不同呢?
5.與HandlerThread區別
HandlerThread本身繼承自Thread的類,這個衍生類是方便子線程創建Handler,用于子線程與主線程之間,子線程與子線程之間的通信。
使用如下:
/**
* 步驟①:創建HandlerThread實例對象
* 傳入參數 = 線程名字,作用 = 標記該線程
*/
mHandlerThread = new HandlerThread("handlerThread");
/**
* 步驟②:啟動線程
*/
mHandlerThread.start();
/**
* 步驟③:創建工作線程Handler & 復寫handleMessage()
* 作用:關聯HandlerThread的Looper對象、實現消息處理操作 & 與其他線程進行通信
* 注:消息處理操作(HandlerMessage())的執行線程 = mHandlerThread所創建的工作線程中執行
*/
workHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg)
{
});
上面使用可知HandlerThread框架免去了Looper的初始化,與loop操作。
其run方法如下:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
在Handler構造器可以傳入目標線程不為空的Looper, 其中getLooper源碼如下:
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
這里要注意一點時,HandlerThread框架為了避免在獲取Looper時,出現為空的情況,在mLooper的set,與get操作加了同步鎖;
由以上分析可知AsyncTask框架內部Handler機制中子線程是生產者,主線程是消費者。而HandlerThread框架內部Handler機制中目標子線程是消費者,其他線程(可能是主線程)是生產者
三.延展分析IntentService
IntentService源碼實現很簡單,其是Service的衍生類,本身Service組件是運行在主線程中,為了讓Service處理耗時任務,在onCreate起了一個HandlerThread與Handler(解決子線程與主線程的通信),在onStartCommand/onStart生命周期中,Handler將Intent包裹在message的obj屬性里,send消息,通知到子線程中處理耗時任務,任務結束后結束Service。無需多講,源碼如下:
private volatile ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
但是,但是,但是 安卓源碼對IntentService有如下備注:
Android O對應用在后臺運行時可以執行的操作施加了限制,稱為后臺執行限制(Background Execution Limits),這可以大大減少應用的內存使用和耗電量,提高用戶體驗。后臺執行限制分為兩個部分:后臺服務限制(Background Service Limitations)、廣播限制(BroadcastLimitations)。該文章詳解:android O 對Service的限制【Background Execution Limits】對此做了詳解.
對于Android8.0后臺執行限制,官方推薦JobIntentService類,用法如下:
public class SimpleJobIntentService extends JobIntentService {
/**
* Unique job ID for this service.
*/
static final int JOB_ID = 1000;
/**
* Convenience method for enqueuing work in to this service.
*/
static void enqueueWork(Context context, Intent work) {
enqueueWork(context, SimpleJobIntentService.class, JOB_ID, work);
}
@Override
protected void onHandleWork(@NonNull Intent intent) {
// We have received work to do. The system or framework is already
// holding a wake lock for us at this point, so we can just go.
Log.i("SimpleJobIntentService", "Executing work: " + intent);
String label = intent.getStringExtra("label");
if (label == null) {
label = intent.toString();
}
toast("Executing: " + label);
for (int i = 0; i < 5; i++) {
Log.i("SimpleJobIntentService", "Running service " + (i + 1)
+ "/5 @ " + SystemClock.elapsedRealtime());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
Log.i("SimpleJobIntentService", "Completed service @ " + SystemClock.elapsedRealtime());
}
@Override
public void onDestroy() {
super.onDestroy();
toast("All work complete");
}
@SuppressWarnings("deprecation")
final Handler mHandler = new Handler();
// Helper for showing tests
void toast(final CharSequence text) {
mHandler.post(new Runnable() {
@Override public void run() {
Toast.makeText(SimpleJobIntentService.this, text, Toast.LENGTH_SHORT).show();
}
});
}
}
由于Android O的后臺限制,創建后臺服務需要使用JobScheduler來由系統進行調度任務的執行,而使用JobService的方式比較繁瑣,8.0及以上提供了JobIntentService幫助開發者更方便的將任務交給JobScheduler調度,其本質是Service后臺任務在他的OnhandleWork()中進行,子類重寫該方法即可。使用較簡單。