Android 多線程實現方式

一個優秀的程序員,必須具備兩種能力:學習能力,時間管理能力

該原創文章首發於微信公衆號“字節流動”

Android 多線程實現方式

通常來說,一個應用至少有一個進程,而一個進程至少有一個線程。

線程是 CPU 調度的基本單位,進程是系統資源分配的基本單位。

進程獨享內存資源,一個進程可以看作一個 JVM ,一個進程崩潰後,在保護模式下一般不會對其它進程產生影響。
同一個進程中的線程共享內存資源,一個線程死掉就導致整個進程死掉。

Android 提供了四種常用的多線程實現方式:

  • AsyncTask
  • 異步消息機制
  • IntentService
  • ThreadPoolExcutor

AsyncTask

我們的老朋友 AsyncTask 類,它是封裝好的線程池,操作 UI 線程極其方便。

瞅一眼,AsyncTask 的三個泛型參數:

public abstract class AsyncTask<Params, Progress, Result>

  • params傳入參數類型,即 doInBackground() 方法中的參數類型;
  • Progress ,異步任務執行過程中返回的任務執行進度類型,即 publishProgress() 和onProgressUpdate() 方法中傳入的參數類型;
  • Result ,異步任務執行完返回的結果類型,即 doInBackground() 方法中返回值的類型。

四個回調方法:

  • onPreExecute(),在主線程執行,做一些準備工作。
  • doInBackground(),在線程池中執行,該方法是抽象方法,在此方法中可以調用 publishProgress() 更新任務進度。
  • onProgressUpdate(),在主線程中執行,在 publishProgress() 調用之後被回調,展示任務進度。
  • onPostExecute(),在主線程中執行,異步任務結束後,回調此方法,處理返回結果。

注意

  • 當 AsyncTask 任務被取消時,回調 onCanceled(obj) ,此時 onPostExecute(),不會被調用,AsyncTask 中的 cancel() 方法並不是真正去取消任務,只是設置這個任務爲取消狀態,需要在 doInBackground() 中通過 isCancelled() 判斷終止任務。
  • AsyncTask 必須在主線程中創建實例,execute() 方法也必須在主線程中調用。
  • 每個 AsyncTask 實例只能執行一次 execute() ,多次執行會報錯,如需執行多次,則需創建多個實例。
  • Android 3.0 之後, AsyncTask 對象默認執行多任務是串行執行,即 mAsyncTask.execute() ,併發執行的話需要使用 executeOnExecutor() 。
  • AsyncTask 用的是線程池機制和異步消息機制(基於 ThreadPoolExecutor 和 Handler )。Android 2.3 以前,AsyncTask 線程池容量是 128 ,全局線程池只有 5 個工作線程,如果運用 AsyncTask 對象來執行多個併發異步任務,那麼同一時間最多只能有 5 個線程同時運行,其他線程將被阻塞。Android 3.0 之後 Google 又進行了調整,新增接口 executeOnExecutor() ,允許自定義線程池(那麼核心線程數以及線程容量也可自定義),並提供了 SERIAL_EXECUTOR 和 THREAD_POOL_EXECUTOR 預定義線程池。後來 Google 又做了一些調整(任何事物都不完美),將線程池的容量與 CPU 的核心數聯繫起來,如目前 SDK 25 版本中,預定義的核心線程數量最少有 2 個,最多 4 個,線程池容量範圍 5 ~ 9 。改動如下:
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;

異步消息機制

異步消息機制的三大主角: Handler ,Message 和 Looper 。
Looper 負責創建 MessageQueue 消息對列,然後進入一個無限 for 循環中,不斷地從消息隊列中取消息,如果消息隊列爲空,當前線程阻塞,Handler 負責向消息隊列中發送消息。

Looper

Looper 有兩個重要的方法: prepare() 和 loop()。

  • prepare() , Looper 與當前線程綁定,一個線程只能有一個 Looper 實例和一個 MessageQueue 實例。
public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(true)); 保證 Looper 對象在當前線程唯一
}

// Looper 的構造方法
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();
}
  • loop ,進入一個無限 for 循環體中,不斷地從消息隊列中取消息,然後交給消息的 target 屬性的 dispatchMessage 方法去處理。
public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();


        // 無限循環體,有沒有想過在 UI 線程裏,有這樣一個死循環,爲什麼界面沒卡死??
        // 答案最後揭曉。
        for (;;) { 
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            msg.target.dispatchMessage(msg);

            msg.recycle();
        }
}

Handler

Handler 負責向消息隊列中發送消息。
在 Activity 中我們直接可以 new Handler ,那是因爲在 Activity 的啓動代碼中,已經在當前 UI 線程中調用了 Looper.prepare() 和 Looper.loop() 方法。

在子線程中 new Handler 必須要在當前線程(子線程)中創建好 Looper 對象和消息隊列,代碼如下

    //在子線程中

    Looper.prepare();

    handler = new Handler() {

	    public void handleMessage(Message msg) {
		    //處理消息
	    };
    };

    Looper.loop();   

之後,你拿着這個 Handler 對象就可以在其他線程中,往這個子線程的消息隊列中發消息了。

HandlerThread

HandlerThread 可以看作在子線程中創建一個異步消息處理機制的簡化版,HandlerThread 對象自動幫我們在工作線程裏創建 Looper 對象和消息隊列。

使用方法:

mHandlerThread = new HandlerThread("MyHandlerThread");
mHandlerThread.start();

mHandler = new Handler(mHandlerThread.getLooper()){
   
    @Override
    public void handleMessage(Message msg) {
        //處理消息
    }
};

之後你就可以使用 Handler 對象往工作線程中的消息隊列中發消息了。

看一下源碼片段:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

    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;
    }
}

注意:handler 在 UI 線程中初始化的,looper 在一個子線程中執行,我們必須等 mLooper 創建完成之後,才能調用 getLooper ,源碼中是通過 wait 和 notify 解決兩個線程的同步問題。

IntentService

IntentService 可以看成是 Service 和 HandlerThread 的合體。它繼承自 Service ,並可以處理異步請求,其內部有一個 WorkerThread 來處理異步任務,當任務執行完畢後,IntentService 自動停止。

如果多次啓動 IntentService 呢? 看到 HandlerThread ,你就應該想到多次啓動 IntentService ,就是將多個異步任務放到任務隊列裏面,然後在 onHandlerIntent 回調方法中串行執行,執行完畢後自動結束。

下面對源碼進行簡單的解析,IntentService 源碼:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            //onHandleIntent 方法在工作線程中執行,執行完調用 stopSelf() 結束服務。
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * enabled == true 時,如果任務沒有執行完,當前進程就死掉了,那麼系統就會令當前進程重啓。
     * 任務會被重新執行。
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }
    @Override
    public void onCreate() {
        super.onCreate();

        // 上面已經講過,HandlerThread 對象 start 之後,會在工作線程裏創建消息隊列 和 Looper 對象。
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
			
        mServiceLooper = thread.getLooper();
        // 獲得 Looper 對象初始化 Handler 對象。
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        // IntentService 每次啓動都會往工作線程消息隊列中添加消息,不會創建新的線程。
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    // 官方建議 IntentService onStartCommand 方法不應該被重寫,注意該方法會調用 onStart 。
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
    @Override
    public void onDestroy() {  
        //服務停止會清除消息隊列中的消息,除了當前執行的任務外,後續的任務不會被執行。
        mServiceLooper.quit();
    }
    /**
     * 不建議通過 bind 啓動 IntentService ,如果通過 bind 啓動 IntentService ,那麼 onHandlerIntent 方法不會被回調。Activity 與 IntentService 之間的通信一般採用廣播的方式。
     */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
    /**
     * 子類必須要實現,執行具體的異步任務邏輯,由 IntentService 自動回調。
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

IntentService 源碼很容易理解,你也可以就自己的應用場景封裝自己的 IntentService 。

場景

  • 正常情況下,啓動 IntentService ,任務完成,服務停止;
  • 異步任務完成前,停止 IntentService ,服務停止,但任務還會執行完成,完成後,工作線程結束;
  • 多次啓動 IntentService ,任務會被一次串行執行,執行結束後,服務停止;
  • 多次啓動 IntentService ,在所有任務執行結束之前,停止 IntentService ,服務停止,除了當前執行的任務外,後續的任務不會被執行;

ThreadPoolExcutor

圖片來自 Jakob Jenkov 博客

ThreadPool

用來管理一組工作線程,任務隊列( BlockingQueue )中持有的任務等待着被線程池中的空閒線程執行。

常用構造方法:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue
);

  1. corePoolSize 核心線程池容量,即線程池中所維持線程的最低數量。corePoolSize 初始值爲 0 ,當有新任務加入到任務隊列中,新的線程將被創建,這個時候即使線程池中存在空閒線程,只要當前線程數小於 corePoolSize ,那麼新的線程依然被創建。
  2. maximumPoolSize 線程池中所維持線程的最大數量。
  3. keepAliveTime 空閒線程在沒有新任務到來時的存活時間。
  4. unit 參數 keepAliveTime 的時間單位。
  5. workQueue 任務隊列,必須是 BlockingQueue 。

簡單使用

創建 ThreadFactory ,當然也可以自定義。
    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());
        }
    };
創建 ThreadPoolExecutor 。
// 根據 CPU 核心數確定線程池容量。
public static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); 

mThreadPoolExecutor = new ThreadPoolExecutor(
        NUMBER_OF_CORES * 2, 
        NUMBER_OF_CORES * 2 + 1,
        60L,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>(),
        backgroundPriorityThreadFactory
);
執行
mThreadPoolExecutor.execute(new Runnable() { 
    
    @Override  
    public void run() {  
         //do something  
    } 

});

Future future = mThreadPoolExecutor.submit(new Runnable() { 
    
    @Override  
    public void run() {  
         //do something  
    } 

});

//任務可取消
future.cancel(true);

Future<Integer> futureInt = mThreadPoolExecutor.submit(new Callable<Integer>() {
    @override
    public Integer call() throws Exception {
        return 0;
    }

});

//獲取執行結果
futureInt.get();

FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>(){
    @override
    public Integer call() throws Exception {
        return 0;
    }    

});

mThreadPoolExecutor.submit(task);
task.get();

聯繫與交流

微信公衆號
我的公衆號
個人微信
我的微信

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章