Android中的線程形態
本節將對Android中的線程形態做一個全面的介紹,除了傳統的Thread以外,還包含了AsyncTask,HandlerThread以及IntentService,這三者的底層實現也是線程,但是他們具有特殊的表現形式,同時在使用上也各有優缺點,下面從源碼的角度來分析AsyncTask的執行過程。
一、AsyncTask
AsyncTask是一種輕量級的異步任務類,它可以在線程池中執行後臺任務,然後把執行的進度和最終結果傳遞給主線程並在主線程中更新UI。從實現上來說,AsyncTask封裝了Thread和Handler,通過AsyncTask我們更加方便的執行後臺任務以及在主線程中訪問UI,但是AsyncTask並不適合進行特別耗時的後臺任務,對於特別耗時的任務來說,建議使用線程池。
AsyncTask是一個抽象的泛型類,他提供了Params,Progress和Result這三個泛型參數,其中Params表示參數的類型,Progress表示後臺任務的執行進度,Result表示後臺任務的返回結果的類型,如果AsyncTask確實不需要傳遞具體的參數,那麼這三個泛型可以用Void來代替。AsyncTask這個類的聲明如下所示。
public abstract class AsyncTask<Params, Progress, Result>
AsyncTask提供了4個核心方法,它們的含義如下所示。
- onPreExecute()
在主線程執行,在異步任務執行之前被調用,一般可以用於做一些準備工作。
- doInBackground(Params… params)
在線程池中執行,此方法用於執行異步任務,params參數表示異步任務的輸入參數。在此方法中可以通過publishProgress方法來更新任務的進度,publishProgress會調用onProgressUpdate方法。此外此方法需要返回計算結果給onPostExecute方法。
- onProgressUpdate(Progress… values)
在主線程執行,當後臺任務的執行進度發生改變時此方法會被調用。
- onPostExecute(Result result)
在主線程中執行,在異步任務結束之後,此方法被調用,其中result參數是後臺任務的返回值,即DoInBackground的返回值。
上面幾個方法,onPreExecute先執行,接着是doInBackground,最後纔是onPostExecute。除了上述四個方法以外,AsyncTask還提供了一個onCancelled()方法,它同樣在主線程中執行,當異步任務被取消時,onCancelled就會被調用,這個時候onPostExecute就不會被調用。下面提供一個典型的示例(這個示例就是源碼中的)
class DownloadTask extends AsyncTask<URL,Integer,Long>{
@Override
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < totalSize; i++) {
totalSize += DownLoad.downloadFile(urls[i]);
publicProgress((int)((i / (float)count)*100));
if(isCancelled()){
break;
}
}
return totalSize;
}
@Override
protected void onProgressUpdate(Integer... values) {
setProgressPercent(values[0]);
}
@Override
protected void onPostExecute(Long aLong) {
showDialog(aLong);
}
}
AsyncTask在使用的過程中也有一些條件限制的,主要有如下幾點:
- 1、AsyncTask的類必須在主線程加載,也就意味着第一次訪問AsyncTask必須發生在主線程,當然這個過程在Android4.1之後已經被系統自動完成了,在Android5.0的源碼中,可以看到ActivityThread的main方法,它會調用AsyncTask的init方法,這就滿足了AsyncTask的類必須在主線程中加載這個條件了。
- 2、AsyncTask的對象必須在主線程創建。
- 3、execute方法必須在UI線程調用。
- 4、不要再程序中直接使用onPreExecute、onPostExecute、DoInBackground和onProgressUpdate方法。
- 5、一個AsyncTask對象只能執行一次,即只能調用一次execute方法,否則會運行時異常。
- 6、在Android1.6之前,AsyncTask是串行執行任務的,Android1.6之後開始採用線程池並行處理,但是從Android3.0開始,爲了避免AsyncTask所帶來的併發錯誤,AsyncTask又採用一個線程來串行任務。儘管如此,在Android3.0以及以後的版本中,我們也可以通過AsyncTask的executteOnExecutor方法來並行的執行任務。
二、AsyncTask的工作原理
爲了分析AsyncTask的工作原理,我們從它的execute方法開始分析
android.os.AsyncTask#execute
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
//private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
return executeOnExecutor(sDefaultExecutor, params);
}
android.os.AsyncTask#executeOnExecutor
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;
}
在上面的代碼中,sDefaultExecutor實際上是一個串行的線程池,一個進程中所有的AsyncTask全部在這個串行的的線程池中排隊執行,這個排隊過程後面再進行分析。在executeOnExecutor方法中,AsyncTask的onPreExecute方法最先執行,然後線程池開始執行。下面分析下線程池的執行過程,如下所示。
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);
}
}
}
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
在mWorker的call方法裏,首先將mTaskInvoked設置爲true,表示當前任務已經被調用過了,然後執行AsyncTask的doInBackgroud方法,接着將其返回值傳遞給postResult方法,它的實現如下所示。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
private Handler getHandler() {
return mHandler;
}
private final Handler mHandler;
在下面代碼賦值
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());//創建實例
}
return sHandler;
}
}
postResult方法會通過sHandler發送給一個MESSAGE_POST_RESULT的消息,這個sHandler的定義如下所示。
private static class InternalHandler extends Handler {
@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;
}
}
}
可以發現,sHandler是一個靜態的Handler對象,爲了能夠將執行環境切換到主線程,這就是要求sHandler對象必須在主線程中創建。由於靜態成員會在加載類的時候進行初始化,因此就變相要求AsyncTask的類必須在主線程加載,否則同一個進程中的AsyncTask都將無法正常工作。sHandler收到MESSAGE_POST_RESULT這個消息後會調用AsyncTask的finish方法,如下所示。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
AsyncTask的finish方法的邏輯比較簡單,如果AsyncTask被取消執行了,那麼就會調用onCancelled方法,否則就會調用onPostExecute方法,可以看到doInBackground的返回結果會傳遞給onPostExecute方法,到這裏AsyncTack的整個工作過程就完畢了。
三、HandlerThread
HandlerThread繼承了Thread,它是一種可以使用Handler的Thread,它的實現也很簡單,就是在run方法中通過Looper.prepare()來創建消息隊列,並通過Looper.loop()來開啓消息循環,這樣在實際的使用中就允許在HandlerThread中創建Handler了,HandlerThread的run如下:
比如我們可以使用一個HandlerThread,將我們的業務切換到子線程執行(和主線程的邏輯剛好相反)。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
四、IntentService
@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);
}
當IntentService被第一次啓動時,它的onCreate會被調用,onCreate方法會創建一個HandlerThread,然後使用它的Looper來構造一個Handler對象mServiceHandler,這樣通過mServiceHandler發送的消息最終都會在HandlerThread中執行,從這個角度來看,IntentService也可以用於執行後臺任務。每次啓動onstartCommon就會調用一次,IntentService在onStartCommon中處理每個後臺任務的intent。下面看一下onStartCommon方法是如何處理外界的intent的,onStartCommon調用了onStart,onStart方法實現如下所示。
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
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);
}
}
public class LocalIntentService extends IntentService {
private static final String TAG = "LocalIntentService";
public LocalIntentService() {
super(TAG);
}
@Override
protected void onHandleIntent( Intent intent) {
String action = intent.getStringExtra("task_action");
Log.i(TAG,"receiver action:" + action);
SystemClock.sleep(3000);
if("com.lgl.test.ACTION".equals(action)){
Log.i(TAG,"handler action:" + action);
}
}
@Override
public void onDestroy() {
Log.i(TAG,"onDestroy");
super.onDestroy();
}
}
五、Android中的線程池
線程池有如下優點。
- 1、重用線程池中的線程,避免因爲線程的創建和銷燬所帶來的性能開銷。
- 2、能有效控制線程池的最大併發數,避免大量的線程之間因互相搶佔系統資源而導致的阻塞現象。
- 3、能夠對線程進行簡單的管理,並提供定時執行以及指定間隔循環執行等功能。
Android中的線程池的概念來源於Java中的Executor,Executor是一個接口,真正的線程池實現爲ThreadPoolExecutor。ThreadPoolExecutor提供了一系列參數來配置線程池,通過不同的參數可以創建不同的線程池,從線程池的功能特性上來說,Android的線程池主要分爲4類,可以通過Executors所提供的工廠方法得到的。
1、ThreadPoolExecutor
ThreadPoolExecutor是線程池的真正實現,它的構造方法提供了一系列的參數來配置,構造如下所示。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
ThreadFactory threadFactory) {
- corePoolSize
線程池的核心線程數,默認情況下,核心線程會在線程池中一直存活,即使它們處於閒置狀態。如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置爲true,那麼閒置的核心線程在等待新任務到來時會有超時策略,這個間隔由keepAliveTime指定,當等待時間超過keepAliveTime所指的時長後,核心線程就會被終止。
- maximumPoolSize
線程池所容納最大的線程數,當活動線程數達到這個數值後,後續的新任務將會被阻塞。
- keepAliveTime
非核心線程閒置時的超時時長,超過這個時長,非核心線程就會被回收。當ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置爲true時,keepAliveTime同樣會作用於核心線程。
- unit
用於指定keepAliveTime參數的時間單位,這是一個枚舉,常用的有TimeUnit,毫秒,秒以及分鐘等。
- workQueue
線程池的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個參數中。
- threadFactory
線程工廠,爲線程池提供創建新線程的功能,ThreadFactory是一個接口,它只有一個方法,Thread new
Thread(Runnable r)。
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingDeque<Runnable>(128);
public static final Executor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
2、線程池的分類
常見的4中不同功能特性的線程池
- FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
- CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- ScheduleThreadPool
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
- SingleTheardExecutor
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
下面演示下系統預置的4中線程池的典型用法。
Runnable common = new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
}
};
ExecutorService fixThreadPool = Executors.newFixedThreadPool(4);
fixThreadPool.execute(common);
ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
cacheThreadPool.execute(common);
ScheduledExecutorService scheduleThreadPool = Executors.newScheduledThreadPool(4);
scheduleThreadPool.schedule(common,2000,TimeUnit.MICROSECONDS);
scheduleThreadPool.scheduleAtFixedRate(common,10,1000,TimeUnit.MICROSECONDS);
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(common);
總結:線程和線程池都介紹完成了。平時開發中可以封裝一個線程池工具類。這樣避免隨意的new Thread導致一些性能開銷或內存泄漏等問題。