Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 第11章 Android的線程和線程池

第11章 Android的線程和線程池

編輯:關於Android編程

線程在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所提供的工廠方法來得到。我們可以先來看一下ThreadPoolExecutor

11.3.1 ThreadPoolExecutor

ThreadPoolExecutor是線程池真正的實現,它的構造方法提供了一系列參數來配置線程池:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue 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 sPoolWorkQueue =
            new LinkedBlockingQueue(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
只有一個核心線程,任務都是在同一個線程中按照順序執行的。它的意義在於統一所有的外界任務到一個線程中,這使得在這些任務之間不需要處理線程同步的問題。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved