標籤:
線程在Android中是一個很重要的概念,從用途上來說,線程分為主線程和子線程,主線程主要處理的是和介面相關的事情,而子線程則往往用於耗時的操作。由於Android的特性,如果在主線程中執行耗時操作就容易導致程式無法及時響應。除了Thread本身外,Android中可以扮演線程角色的還有很多很多,比如:
AsyncTask,IntentService和HandlerThread。不同形式的線程雖然都是線程,但是它們仍然具有不同的特性和使用情境。
11.1 主線程和子線程
主線程是進程所擁有的線程,在java中預設情況下一個進程只有一個線程,這個線程就是主線程。主線程主要處理介面互動相關的邏輯,因為使用者隨時會和介面發生互動,因此主線程在任何時候都必須具有較高的響應速度,否則就會產生一種介面卡頓的感覺。為了保持較高的響應速度,這就要求主線程中不能執行耗時的操作,這個時候子線程就派上用場了。
11.2 Android中的線程形態
11.2.1 AsyncTask
AsyncTask是一種輕量級的非同步任務類,它可以線上程池中執行背景工作,然後把執行的進度和執行的結果傳遞給主線程並在主線程中更新UI。從實現來說,AsyncTask封裝了Thread和Handler,通過AsyncTask可以更加方便地執行背景工作以及在主線程中訪問UI。
AsyncTask是一個抽象的泛型類,它提供了params、progress和result這三個泛型參數。其中params表示的是參數的類型,progress表示背景工作執行進度的類型,而result則表示背景工作的返回結果的類型。
AsyncTask提供了4個核心的方法,它們的含義就不講了。這裡只是簡單的提一下。
(1)onPreExecute:在主線程中執行,在非同步任務執行之前,此方法會被調用
(2)doInBackground(Params…params):線上程池中執行,此方法用於執行非同步任務,params參數表示的是非同步任務的輸入參數。在此方法中可以通過publishProgress方法來更新任務進度,publishProgress方法會回調onProgressUpdate方法。另外此方法需要返回計算結果給onPostExecute
(3)onProgressUpdate(Progress…values):在主線程中執行,當後天任務的執行進度發生改變時此方法會被調用
(4)onPostExecute(Result result):在非同步任務執行完畢之後會被調用
AsyncTask在使用的過程中還是有一些限制的,主要有以下幾點:
(1)AsyncTask的類必須在主線程中載入,這就意味著第一次訪問AsyncTask必鬚髮生在主線程,當然這個過程在Android4.1及以上版本中已經被系統自動完成了。在ActivityThread的main方法中,它會調用AsyncTask的init方法,這就滿足了AsyncTask的類必須在主線程載入的條件了。
(2)AsyncTask的對象必須在主線程中建立
(3)execute方法必須在UI線程調用
(4)不要在程式中直接調用4個核心方法
(5)一個AsyncTask對象只能執行一次,即只能調用一次execute方法
(6)AsyncTask預設是調用execute方法來串列執行任務的,但是我們仍然可以調用executeOnExecutor方法來並行的執行
11.2.2 AsyncTask的工作原理
它的實現原理是這樣的:
在主線程執行了execute方法。這個方法會調用onPreExecute方法,然後會將params參數進行封裝成FutureTask【繼承自Runnable】交給SerialExecutor【用於任務的排隊】的execute方法去處理,execute方法會把FutureTask對象添加到任務隊列中等待THREAD_POOL_EXECUTOR去執行任務,一旦得到執行官就會調用doInBackGround方法,
1)在這個使用者複寫的方法中如果調用了publishProcess方法,則會通過AsyncTask的內部類InternalHandler執行個體sHandler發送一條MESSAGE_POST_PROGRESS訊息,更新進度,sHandler處理訊息時onProgressUpdate(Progress… values)方法將被調用;
2)執行中如果遇到異常,則發送一條MESSAGE_POST_CANCEL的訊息,取消任務,sHandler處理訊息時onCancelled()方法將被調用;
3 )如果執行成功,則發送一條MESSAGE_POST_RESULT的訊息,顯示結果,sHandler處理訊息時onPostExecute(Result result)方法被調用;
AsyncTask中有兩個線程池(serialExecutor和THREAD_POOL_EXECUTOR)和一個靜態Handler(InternalHandler),其中線程池serialExecutor用於任務的排隊,而線程池THREAD_POOL_EXECUTOR用於真正的任務的執行,Handler(InternalHandler)用於將工作環境切換會主線程中。
繼承自Handler的InternalHandler執行個體sHandler是一個靜態Handler對象,為了能夠將執行環境切換會主線程的話,就必須要求sHandler在主線程中建立。又由於sHandler是靜態,所以會在載入類的時候進行初始化。綜合來看,就會變相的要求AsyncTask類必須在主線程中載入。
11.2.3 HandlerThread
她繼承了Thread,它是一種可以使用Handler的Thread【以為著本身包含了Looper】。它在run方法中通過Looper.prepare()來建立訊息佇列,並通過Looper.loop()來開啟訊息迴圈,這樣在實際的使用中就允許在HandlerThread中建立Handler了。
使用步驟
儘管HandlerThread的文檔比較簡單,但是它的使用並沒有想象的那麼easy。
下面看一下它的使用步驟:
(1)建立一個HandlerThread,即建立了一個包含Looper的線程。
HandlerThread handlerThread = new HandlerThread("leochin.com");handlerThread.start(); //建立HandlerThread後一定要記得start()
(2)擷取HandlerThread的Looper
Looper looper = handlerThread.getLooper();
(3)建立Handler,通過Looper初始化
Handler handler = new Handler(looper);
接下來你就可以通過handler給背景工作執行緒發訊息了。最後注意一下由於HandlerThread的run方法是一個無限迴圈,因此當明確不需要再使用HandlerThread的時候,可以通過它的quit或者quitSafely方法來終止線程的執行,這是一個良好的編程習慣。
11.2.4 IntentService
是一種特殊的service,它繼承了service並且它是一個抽象類別,因此必須建立它的子類才能使用IntentService。IntentService可用於執行後台耗時的任務。
優點:
(1)當任務執行後它會自動停止,
(2)由於具有Service的特性,所以它相比於普通的Thread具有較高的優先順序不容易被後台殺死,
IntentService封裝了HandlerThread和Handler。這一點可以從onCreate方法中看出來:
public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); }
可以明顯的看到,在onCreate方法中建立了HandlerThread 對象,並且開啟了這個線程。通過這個線程拿到Looper對象,再將這個Looper對象作為參數傳到Handler中建立了一個mServiceHandler 對象。每次啟動IntentService的時候,都會調用onStart方法,在這個方法中IntentService僅僅是通過mServiceHandler 發送了一個訊息,這個訊息會在HandlerThread中被處理。下面是onStart的源碼:
public void onStart(Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); }
上述的ServiceHandler是IntentService的一個內部類,它繼承了Handler。如下所示:
private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } }
上面在onStart方法中發送了訊息,最後在mServiceHandler的handleMessage方法中調用了使用者需要覆寫的onHandlerIntent方法進行訊息的處理。當最後一個訊息處理完畢之後就調用stopSelf(msg.arg1)停止服務。
11.3 線程池
提到了線程池就有必要說一下線程池的好處:
(1)重用線程,避免頻繁建立和銷毀線程帶來的效能開銷;
(2)能夠有效控制線程池的最大並發數,避免大量的線程之間因為互相搶佔資源導致的阻塞;
(3)能夠對線程進行簡單的管理;
Android中的線程池的概念來源於java中的Executor,Executor是一個介面,真正的線程池的實現是ThreadPoolExecutor。ThreadPoolExecutor提供了一系列參數來配置。通過不同的配置就可以建立不同的線程池。從線程池的功能特性上來看,Android的線程池主要分為4類,這4類線程池可以通過Executors所提供的Factory 方法來得到。我們可以先來看一下ThreadPoolExecutor
11.3.1 ThreadPoolExecutor
ThreadPoolExecutor是線程池真正的實現,它的構造方法提供了一系列參數來配置線程池:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
(1)corePoolSize:線程池的核心線程數。預設情況下,核心線程會線上程池中一直存活,即使他們處於閒置狀態。但是如果將ThreadPoolExecutor中的allowCoreThreadTimeOut設定為true的話,那麼閑置的核心線程在等待新任務到來時就會有逾時策略。這個時間間隔由keepAliveTime指定。當等待時間超過keepAliveTime指定的時間就核心線程就會被終止。
(2)maximumPoolSize:線程池所能容納的最大線程數,當活動線程數達到這個值後,後續的新任務就會被阻塞。
(3)keepAliveTime:閑置線程的逾時時間長度。
(4)unit:用於指定keepAliveTime參數的時間單位。
(5)workQueue:線程池的任務隊列,通過ThreadPoolExecutor的execute方法提交的Runnable對象都會儲存其中的。
(6)threadFactory:線程工廠,為線程池建立新線程的功能。
ThreadPoolExecutor執行任務時大致是遵循如下規則的:
(1)如果線程池中的線程數量沒有達到核心線程的數量的話,那麼新能加入一個任務就會新建立一個核心線程;
(2)如果線程池中的線程數量達到或者超過核心線程的數量的話,那麼就會將新來的任務添加到等待隊列中;
(3)如果等待隊列已滿,但是線程數量沒有達到最大線程數,就會啟動非核心線程來執行任務;
(4)如果步驟3中的線程數量已經達到線程池規定的最大值,那麼就拒絕執行此任務,並且會拋出異常;
ThreadPoolExecutor的參數配置在AsyncTask中有明顯的體現,下面就是AsyncTask中的線程池的配置情況;
private static final String LOG_TAG = "AsyncTask"; 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); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sThreadFactory);
11.3.2 線程池的分類
1,FixThreadPool
它是一種線程數量固定的線程池,而且全是核心線程。線程處於空閑狀態也不會被回收,除非線程池被關閉了。當所有的線程都處於活動狀態,新任務就會處於等待狀態,直到有閒置線程出來。由於它的顯著特點,這就意味著它能夠更加快的響應外界的請求。另外任務隊列也是沒有大小限制的。
2,CacheThreadPool
是一種線程數量不固定的線程池。它只有非核心線程,並且其最大線程數為Integer.MAX_VALUE。由於這是一個很大的數,所以可以認為最大線程數可以任意大。當線程池中有空閑線程的時候就會用空閑線程來執行新任務,但是空閑線程是有時間限制的【60s】,當沒有空閑線程的話就會建立新的線程來執行新任務。還有值得注意的是,這個線程池的任務隊列其實相當於空集合。也就是說新任務都會立即執行不用等待。這類線程池比較適合執行大量的耗時較少的任務。
3,SchemeThreadPool
它的核心線程數是固定的,但是非核心線程數是無限制的,並且當非核心線程閑置時就會被立刻回收。這類線程池用於執行定時任務和具有固定周期的重複任務。
4,SingleThreadPool
只有一個核心線程,任務都是在同一個線程中按照順序執行的。它的意義在於統一所有的外界任務到一個線程中,這使得在這些任務之間不需要處理線程同步的問題。
第11章 Android的線程和線程池