1. 程式人生 > >四. 執行緒管理之Android中的多執行緒

四. 執行緒管理之Android中的多執行緒

不忘初心 砥礪前行, Tomorrow Is Another Day !

相關文章

本文概要:

  1. AsyncTask
  2. HandlerThread與IntentService

在閱讀本文之前,需要對Handler訊息機制有所瞭解才能深入理解,如果對Handler瞭解還不夠深入,可以先閱讀這篇文章-Android訊息機制Handler

一. AsyncTask

Android提供的一個非同步類,它封裝了handler和執行緒池,從而簡化了更新UI的問題.

1.1 基本使用

AsyncTask是一個抽象的泛型類提供三個泛型引數分別為 Params , Progress, Result.

  • Params : 輸入的引數型別
  • Progress : 執行任務的進度
  • Result : 返回結果的型別
public abstract class AsyncTask<Params, Progress, Result> 
複製程式碼

提供了4個核心方法:

  1. onPreExecute :主執行緒中執行,任務開始前.

  2. Result doInBackground(Params... params) : 子執行緒執行,執行任務時.

    • 當我們呼叫publishProgress(Progress... values)更新任務進度時,會回撥onProgressUpdate方法.
    • 返回計算結果Result給onPostExecute方法.
  3. onProgressUpdate(Progress... values) : 主執行緒中執行,任務的執行進度更新時.

  4. onPostExecute(Result result) :主執行緒中執行,任務執行完畢時.

使用示例

public class AsyncTaskActivity extends AppCompatActivity implements View.OnClickListener {
    //...省略部分程式碼

    class MyAsyncTask extends AsyncTask<String, Integer, Robot> {


        @Override
        protected void onPreExecute
() { mProgressDialog = new ProgressDialog(AsyncTaskActivity.this); mProgressDialog.setMessage("正在載入"); mProgressDialog.setMax(10); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mProgressDialog.show(); } @Override protected Robot doInBackground(String... strings) { Robot robot = null; if (strings != null && strings.length > 0) { for (int i = 0; i < 11; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } publishProgress(i); if (isCancelled()) { break; } else if (i == 10) {{ robot = new Robot("i", strings[0]); } } } else { throw new IllegalArgumentException("please set the params"); } return robot; } @Override protected void onProgressUpdate(Integer... values) { if (values != null && values.length > 0) { mProgressDialog.setProgress(values[0]); } } @Override protected void onPostExecute(Robot robot) { mProgressDialog.dismiss(); tvName.setText(robot == null ? "引數不詳" : robot.getName()); } @Override protected void onCancelled() { tvName.setText("任務被取消"); } } } 複製程式碼

首先在onPreExecute初始化了ProgressDialog控制元件,接著通過doInBackground模擬執行耗時的機器人構造流程.在構造流程中呼叫了publishProgress去更新執行任務的進度.最後當任務執行完後在onPostExecute更新了機器人名,這時如果點選了取消任務,那麼onCancelled則會呼叫.

關於AsyncTask的使用比較簡單就到這裡點到為止.我們具體來看它實現的原理.

1.2 基本原理

原始碼版本基於Android 8.0

1. 我們首先從AsyncTask的構造開始,看具體做了哪些操作.

對應原始碼

public AsyncTask() {
        this((Looper) null);
}

public AsyncTask(@Nullable Looper callbackLooper) {
        //1.初始化Handler
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        //2.初始化WorkerRunnable,實際是一個實現Callable介面的類.
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                //...省略具體實現程式碼,後面再分析.
                return result;
            }
        };

        //3.初始化FutureTask封裝了WorkerRunnable.
        //在run方法中呼叫了mWorker的Call方法.
        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                //...省略具體實現程式碼,後面再分析.
            }
        };
    }
複製程式碼

這裡主要乾了三個初始化工作.

  1. 初始化Handler,繫結主執行緒的Looper.
  2. 初始化WorkerRunnable,實際是一個實現Callable介面的類.
    • (如果對Callable還比較陌生,可以看前面的文章介紹.)
  3. 初始化FutureTask封裝了WorkerRunnable.
    • 這裡簡單複習下FutureTask,它既實現了Runnable介面,又實現了Future介面.所以它既可以實現執行執行緒,又可以獲取執行緒執行完後的返回值.(如果對FutureTask還比較陌生,可以看前面的文章介紹.)
2. 當我們例項化一個AsyncTask後,就會呼叫execute方法執行任務.接著看原始碼.

對應原始碼

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
}

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();//執行onPreExecute

        mWorker.mParams = params;//將輸入引數封裝到WorkRunnable
        exec.execute(mFuture);//執行任務.
        
        return this;
}
複製程式碼

在excute方法中呼叫了executeOnExecutor方法,對這個流程做個小結.

  1. 檢查狀態.
  2. FutureTask持有一個WorkRunnable,WorkRunnable持有傳入的引數.
  3. 最後利用執行緒池,去執行futureTask任務,這裡可以將FutureTask看成相當於Runnable的作用.
3. 執行緒池的處理流程.

在executeOnExecutor(sDefaultExecutor, params)方法中用到了sDefaultExecutor執行緒池.我們接著看執行緒池的實現.

對應原始碼

   /**
     * 用於任務的排隊的執行緒池
     */
    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            //1. 插入到任務佇列
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();//呼叫futureTask的run方法
                    } finally {//任務執行完畢,繼續執行下一個任務.
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {//無任務,執行下一個.
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {//不斷從佇列中取任務
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
    
     /**
     * 執行任務的執行緒池THREAD_POOL_EXECUTOR
     */
    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;
     private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }
    
複製程式碼

這裡涉及到兩個執行緒池,也是AsyncTask的核心之處.

  • SerialExecutor : 用於任務的排隊.
    1. 插入到任務佇列.
    2. 無任務或者任務執行完後,不斷從佇列中取新任務到另一執行緒池中執行.
  • threadPoolExecutor : 用於真正執行任務.
    • CORE_POOL_SIZE :核心執行緒數與CPU核有關
    • MAXIMUM_POOL_SIZE : 最大執行緒數與CPU核有關
    • KEEP_ALIVE_SECONDS, TimeUnit.SECONDS :超時機制為30秒
      • 超時機制也作用於核心執行緒.
    • sPoolWorkQueue : 阻塞佇列

正因為SerialExecutor的存在,從上面可以看出3.0之後是序列執行,所以不會有併發問題(執行飽和策略).

4. 通過Call方法最後我們來看執行任務後是如何回撥給主執行緒的.

在futureTask的run方法會回撥WorkerRunnable的call方法.這裡再回顧一下上一篇FutureTask的run方法原始碼.

對應原始碼

public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    //回撥callable的Call方法,獲取非同步任務返回值.
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            //...省略部分程式碼
        }
    }
複製程式碼

這裡繼續回到最開始初始化WorkerRunnable時程式碼.

    
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
    
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    /**
                      * 1.回撥doInBackground方法 
                      */
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    
                    postResult(result);
                }
                return result;
            }
        };

      
     /**
      * 2.通過handler將result傳遞出去 
      */
     private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
    
   
    /**
      * 3. 處理訊息
      */
    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @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
                    //呼叫finish
                    result.mTask.finish(result.mData[0]);
                    break;
                //...省略部分程式碼
            }
        }
    }
    
    
    private void finish(Result result) {
        if (isCancelled()) {
            //回撥已取消
            onCancelled(result);
        } else {
            //回撥onPostExecute
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
    
複製程式碼

Call方法流程小結.

  1. 回撥doInBackground方法.
  2. 傳送訊息,通過handler將result傳遞出去.
  3. 處理訊息,回撥onCancelled或onPostExecute.

最後給出一張簡略版的AsyncTask的工作流程圖


asyntask工作流程簡略圖

到這裡AsyncTask的實現原理基本分析完成了,至於之前提到的3.0之前是並行的,3.0之後是序列的,如果想要實現並行可以採用如下方式.

//方式一
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,params);

//方式二
executeOnExecutor(自定義的執行緒池,params);
複製程式碼

對於3.0以下是並行的效果,瞭解即可.這裡就不演示了.因為現在APP基本上5.0以下都不適配了,我們又何必螳臂當車,節約寶貴的時間看未來趨勢的知識.

二. HandlerThread與IntentService

2.1 HandlerThread

是一個訊息迴圈的執行緒,這樣就可以在該執行緒中使用Handler了.

對應原始碼

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();//建立訊息佇列
        synchronized (this) {
            mLooper = Looper.myLooper();
            //Looper初始化完成,喚醒因此條件阻塞的執行緒
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();//開啟訊息迴圈
        mTid = -1;
    }
    
    
public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        //執行緒已經啟動,等待looper初始化完成
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    
複製程式碼

與普通執行緒相比,它的特點.

  • 普通執行緒是在run方法執行耗時任務
  • 它可以在此執行緒中使用handler來執行耗時任務,由於是無限迴圈,不再使用時,也需呼叫quit或者quitSafely來終止執行緒的執行.
  • 具體使用場景見IntentService。

對應原始碼

2.2 IntentService

是一個Service,封裝了handlerThread與Handler.

正因為它是一個Service,所以不會容易被系統殺死.具有以下特點.

特點:

  • 相對於服務,能夠在服務中執行耗時任務.
  • 相對於執行緒,優先順序比單純的執行緒高.
  • 執行完後會自動停止.
基本使用
public class MyIntentService extends IntentService {
    private static final String TAG = "MyIntentService";

    public MyIntentService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String taskName = intent.getStringExtra("taskName");
        Log.d(TAG, "taskName: " + taskName);
        SystemClock.sleep(2500);
        if ("org.jason.taskOne".equals(taskName)){
            Log.d(TAG, "do task: "+taskName);
        }

    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: ");
        super.onDestroy();
    }
}

private void doIntentService() {
        //連續開三個服務測試
        Intent intent = new Intent(this, MyIntentService.class);
        intent.putExtra("taskName", "org.jason.taskOne");
        startService(intent);
        intent.putExtra("taskName", "org.jason.taskTw0");
        startService(intent);
        intent.putExtra("taskName", "org.jason.taskThree");
        startService(intent);
}

//呼叫輸出
12-27 14:34:01.338 D/MyIntentService: taskName: org.jason.taskOne
12-27 14:34:03.839 D/MyIntentService: do task: org.jason.taskOne
12-27 14:34:03.840 D/MyIntentService: taskName: org.jason.taskTw0
12-27 14:34:06.341 D/MyIntentService: taskName: org.jason.taskThree
12-27 14:34:08.841 D/MyIntentService: onDestroy: 

複製程式碼

從上面日誌除了可以看出它的特點外,還能發現任務都是按順序依次執行的.這與它內部的hanlder處理訊息有關,因為handler的looper就是按順序處理訊息的,接著我們去看是如何實現的.

基本原理

IntentService是一個繼承Service的抽象類.既然是Service我們就按照Service的生命週期來分析.

1. 首先看OnCreate方法流程

對應原始碼

public abstract class IntentService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        //1.初始化一個HandlerThread
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        //2.初始化一個Handler,繫結HandlerThread的Looper.這樣就能使用handler,給HandlerThread執行緒發訊息了.
        //(也就是說綁定了在哪個執行緒的looper,那麼傳送的訊息就在哪個執行緒處理)
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
}    
複製程式碼
2. 接著OnStart方法流程

onStartCommand裡面呼叫了onStart方法,這裡直接看此方法.

對應原始碼

 @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        //1.利用handler傳送訊息,訊息內容就是我們傳入的intent以及服務id標識.
        mServiceHandler.sendMessage(msg);
    }
    
        
 private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        
        //2.處理訊息,
        //注意:此handler綁定了HandlerThread的looper,所以是在子執行緒處理訊息.
        @Override
        public void handleMessage(Message msg) {
            //a.回撥onHandleIntent
            onHandleIntent((Intent)msg.obj);
            //b.停止服務
            stopSelf(msg.arg1);
        }
}    
複製程式碼
3.最後看onDestroy方法
@Override
    public void onDestroy() {
        //最後退出looper,這樣訊息佇列才能退出,最終執行緒才會銷燬.不然一直處於阻塞等待狀態.
        mServiceLooper.quit();
    }
複製程式碼

前面講解HandlerThread時也有提過,當不使用時,需呼叫quit或者quitSafely來終止執行緒的執行.可以看出系統原始碼也是有這一步,所以當我們自定義一個具有訊息迴圈的執行緒一定記得退出,這是良好的程式設計習慣.

關於HandlerThread以及它的應用IntentService就介紹到這裡了.

由於本人技術有限,如有錯誤的地方,麻煩大家給我提出來,本人不勝感激,大家一起學習進步.

參考連結: