【Android開發進階系列】多線程專題

1 概述

? ? ? ? 在操作系統中,線程是操作系統調度的最小單元,同時線程又是一種受限的系統資源,即線程不可能無限制地產生,并且線程的創建和銷毀都會有相應的開銷。當系統中存在大量的線程時,系統會通過會時間片輪轉的方式調度每個線程,因此線程不可能做到絕對的并行。

? ? ? ? 如果在一個進程中頻繁地創建和銷毀線程,顯然不是高效的做法。正確的做法是采用線程池,一個線程池中會緩存一定數量的線程,通過線程池就可以避免因為頻繁創建和銷毀線程所帶來的系統開銷。

? ? ? ? 關于線程池的理解,參考Java線程池詳解,Android同樣適用。

????????Android中常見線程使用方式有以下幾種:

????1) Activity.runOnUiThread(Runnable)

????2) View.post(Runnable);View.postDelay(Runnable, long)

????3) Handler

????4) AsyncTask


2 Activity.runOnUiThread方式

????????利用Activity.runOnUiThread(Runnable)把更新ui的代碼創建在Runnable中,然后在需要更新ui時,把這個Runnable對象傳給Activity.runOnUiThread(Runnable)。

????????Runnable對像就能在ui程序中被調用。如果當前線程是UI線程,那么行動是立即執行。如果當前線程不是UI線程,操作是發布到事件隊列的UI線程。

public?class?TestActivity?extends?Activity{

? ?Button?btn;

? ?@Override

? ?protected?void?onCreate(BundlesavedInstanceState) {

? ? ?//?TODO?Auto-generated method stub

? ? ?super.onCreate(savedInstanceState);

? ? ?setContentView(R.layout.handler_msg);

? ? ?btn?= (Button) findViewById(R.id.button1);

? ? ?btn.setOnClickListener(new?OnClickListener(){

? ? ????@Override

? ? ? ? public?void?onClick(View view) {

? ? ? ? ? ?//?TODO?Auto-generated method stub

? ? ? ? ? ?new?Thread(new?Runnable(){

? ? ? ? ? ? ?@Override

? ? ? ? ? ? ?public?void?run() {

? ??????????????//?TODO?Auto-generatedmethod stub

? ??????????????//模擬耗時的操作。

????????????????try?{

? ????????????????Thread.sleep(10000);

????????????????}?catch?(InterruptedException e) {

? ????????????????//?TODO?Auto-generated catch block

? ????????????????e.printStackTrace();

????????????????}


????????????????// 更新主線程UI

????????????????TestActivity.this.runOnUiThread(new?Runnable(){

? ????????????????@Override

? ????????????????public?void?run() {

????????????????????//?TODO?Auto-generated method stub

????????????????????btn.setText("更新完畢!");

????? ????????????}

??? ????????????});

? ????????????}

????????????}).start();

? ? ? ? }

? ? });

}


3 View.post()/postDelayed()方式

????????我們知道,Handler有postDelayed()/post()等API,在UI線程中,通過默認構造方法newHandler(),會創建一個與當前線程的Looper綁定的Handler對象,UI線程的消息循環是由框架層打開(Looper.loop()),APP開發者無需關注。維護一個掛在UI線程的Handler成員變量用以發消息/處理消息,是慣常的代碼風格。

????????當然,還有另外一類API:View.postDelayed()/post()。Android官方文檔介紹這類API也是向UI線程發消息,Runnable執行在UI線程中。與Handler.postDelayed()/post()一樣,View.postDelayed()/post()的API Level是1,是非常古老的API。

public boolean post(Runnable action){

????final AttachInfo attachInfo = mAttachInfo;

????if(attachInfo !=?null){

????????return attachInfo.mHandler.post(action);

????}


????// Assume that post will succeed later

????ViewRootImpl.getRunQueue().post(action);

????return true;

}

????????將任務交由attachInfo中的Handler處理,保證在UI線程執行。從本質上說,它還是依賴于以Handler、Looper、MessageQueue、Message為基礎的異步消息處理機制。相對于新建Handler進行處理更加便捷。因為attachInfo中的Handler其實是由該View的ViewRootImpl提供的,所以post方法相當于把個事件添加到了UI 事件列中。下面舉一個常用的例子,比如在onCreate方法中獲取某個view的寬高,而直接View#getWidth獲取到的值是0。要知道View顯示到界面上需要經歷onMeasure、onLayout和onDraw三個過程,而View的寬高是在onLayout階段才能最終確定的,而在Activity#onCreate中并不能保證View已經執行到了onLayout方法,也就是說Activity的聲明周期與View的繪制流程并不是一一綁定。那為什么調用post方法就能起作用呢?首先MessageQueue是按順序處理消息的,而在setContentView()后隊列中會包含一條詢問是否完成布局的消息,而我們的任務通過View#post方法被添加到隊列尾部,保證了在layout結束以后才執行。

4 Handler+Thread方式

????????Android主線程包含一個消息隊列(MessageQueue),該消息隊列里面可以存入一系列的Message或Runnable對象。通過一個Handler你可以往這個消息隊列發送Message或者Runnable對象,并且處理這些對象。每次你新創建一個Handle對象,它會綁定于創建它的線程(也就是UI線程)以及該線程的消息隊列,從這時起,這個handler就會開始把Message或Runnable對象傳遞到消息隊列中,并在它們出隊列的時候執行它們。????

Handler Thread原理圖

????????Handler可以把一個Message對象或者Runnable對象壓入到消息隊列中,進而在UI線程中獲取Message或者執行Runnable對象,Handler把壓入消息隊列有兩類方式,Post和sendMessage:

????Post方式:

????????Post允許把一個Runnable對象入隊到消息隊列中。它的方法有:

post(Runnable)/postAtTime(Runnable,long)/postDelayed(Runnable,long)

????????對于Handler的Post方式來說,它會傳遞一個Runnable對象到消息隊列中,在這個Runnable對象中,重寫run()方法。一般在這個run()方法中寫入需要在UI線程上的操作。

handler post用法

????sendMessage:

????????sendMessage允許把一個包含消息數據的Message對象壓入到消息隊列中。它的方法有:sendEmptyMessage(int)/sendMessage(Message)/sendMessageAtTime(Message, long)/sendMessageDelayed(Message, long)

????????Handler如果使用sendMessage的方式把消息入隊到消息隊列中,需要傳遞一個Message對象,而在Handler中,需要重寫handleMessage()方法,用于獲取工作線程傳遞過來的消息,此方法運行在UI線程上。Message是一個final類,所以不可被繼承。

handler定義
handler sendMessage用法

????優缺點:

????????1. Handler用法簡單明了,可以將多個異步任務更新UI的代碼放在一起,清晰明了;

????????2.處理單個異步任務代碼略顯多;

????適用范圍

????????1. 多個異步任務的更新UI;

5 AsyncTask

5.1 AsyncTask簡介

??? ????AsyncTask是一個抽象類,它是由Android封裝的一個輕量級異步類(輕量體現在使用方便、代碼簡潔),它可以在線程池中執行后臺任務,然后把執行的進度和最終結果傳遞給主線程并在主線程中更新UI。

??? ????AsyncTask的內部封裝了兩個線程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler)。

??? ????其中SerialExecutor線程池用于任務的排隊,讓需要執行的多個耗時任務,按順序排列THREAD_POOL_EXECUTOR線程池才真正地執行任務InternalHandler用于從工作線程切換到主線程

5.1.1 AsyncTask的泛型參數

????????AsyncTask的類聲明如下:

public abstract class AsyncTask

??? AsyncTask是一個抽象泛型類。

??? 其中,三個泛型類型參數的含義如下:

??????? Params開始異步任務執行時傳入的參數類型;

??????? Progress異步任務執行過程中,返回下載進度值的類型;

??????? Result異步任務執行完成后,返回的結果類型;

? ??? ??如果AsyncTask確定不需要傳遞具體參數,那么這三個泛型參數可以用Void來代替。

? ? ? 有了這三個參數類型之后,也就控制了這個AsyncTask子類各個階段的返回類型,如果有不同業務,我們就需要再另寫一個AsyncTask的子類進行處理。

5.1.2 AsyncTask的核心方法

** onPreExecute()**

? ? ? ? 這個方法會在后臺任務開始執行之間調用,在主線程執行。用于進行一些界面上的初始化操作,比如顯示一個進度條對話框等。

doInBackground(Params...)

? ? ? ? 這個方法中的所有代碼都會在子線程中運行,我們應該在這里去處理所有的耗時任務。

? ? ? ? 任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress...)方法來完成。

onProgressUpdate(Progress...)

? ? ? ? 當在后臺任務中調用了publishProgress(Progress...)方法后,這個方法就很快會被調用,方法中攜帶的參數就是在后臺任務中傳遞過來的。在這個方法中可以對UI進行操作,在主線程中進行,利用參數中的數值就可以對界面元素進行相應的更新。

onPostExecute(Result)

? ? ? ? 當doInBackground(Params...)執行完畢并通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作為參數傳遞到此方法中,可以利用返回的數據來進行一些UI操作,在主線程中進行,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。

? ? ? ? 上面幾個方法的調用順序:

onPreExecute() -->doInBackground() --> publishProgress() --> onProgressUpdate() -->onPostExecute()

? ? ? ? 如果不需要執行更新進度則為onPreExecute() -->doInBackground() --> onPostExecute(),

? ? ? ? 除了上面四個方法,AsyncTask還提供了onCancelled()方法,它同樣在主線程中執行,當異步任務取消時,onCancelled()會被調用,這個時候onPostExecute()則不會被調用,但是要注意的是,AsyncTask中的cancel()方法并不是真正去取消任務,只是設置這個任務為取消狀態,我們需要在doInBackground()判斷終止任務。就好比想要終止一個線程,調用interrupt()方法,只是進行標記為中斷,需要在線程內部進行標記判斷然后中斷線程。

5.1.3 AsyncTask的簡單使用

class DownloadTask extends AsyncTask {?

????@Override?

????protected void onPreExecute() {?

????????progressDialog.show();?

????}?


????@Override?

????protected Boolean doInBackground(Void... params) {?

????????try {?

????????????while (true) {?

????????????????int downloadPercent = doDownload();?

????????????????publishProgress(downloadPercent);?

????????????????if (downloadPercent >= 100) {?

????????????????????break;?

????????????????}?

????????????}?

????????} catch (Exception e) {?

????????????return false;?

????????}?

????????return true;?

????}?


????@Override?

????protected void onProgressUpdate(Integer... values) {?

????????progressDialog.setMessage("當前下載進度:" + values[0] + "%");?

????}?


????@Override?

????protected void onPostExecute(Boolean result) {?

????????progressDialog.dismiss();?

????????if (result) {?

????????????Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();?

????????} else {?

????????????Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();?

????????}?

????}?

}?

? ? ? ? 這里我們模擬了一個下載任務,在doInBackground()方法中去執行具體的下載邏輯,在onProgressUpdate()方法中顯示當前的下載進度,在onPostExecute()方法中來提示任務的執行結果。如果想要啟動這個任務,只需要簡單地調用以下代碼即可:

??? new DownloadTask().execute();?

5.2 使用AsyncTask的注意事項

????① 異步任務的實例必須在UI線程中創建,即AsyncTask對象必須在UI線程中創建。

????② execute(Params... params)方法必須在UI線程中調用。

????③ 不要手動調用onPreExecute(),doInBackground(Params...

params),onProgressUpdate(Progress... values),onPostExecute(Result result)這幾個方法。

? ? ④ 不能在doInBackground(Params... params)中更改UI組件的信息。

????⑤ 一個任務實例只能執行一次,如果執行第二次將會拋出異常。


5.3 AsyncTask的源碼分析

??? 先從初始化一個AsyncTask時,調用的構造函數開始分析。

public AsyncTask() {

??????? mWorker = new WorkerRunnable() {

??????????? public Result call() throws Exception {

??????????????? mTaskInvoked.set(true);

??????????????? Result result = null;

??????????????? try {

???? ???????????????Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

??????????????????? //noinspection unchecked

??????????????????? result = doInBackground(mParams);

??????????????????? Binder.flushPendingCommands();

??????????????? } catch (Throwable tr) {

??????????????????? mCancelled.set(true);

??????????????????? throw tr;

??????????????? } finally {

??????????????????? postResult(result);

??????????????? }

??????????????? return result;

??????????? }

??????? };


??????? mFuture = new FutureTask(mWorker) {

??????????? @Override

??????????? protected void done() {

??????????????? try {

??????????????????? postResultIfNotInvoked(get());

??????????????? } catch (InterruptedException e) {

??????????????????? android.util.Log.w(LOG_TAG, e);

??????????????? } catch (ExecutionException e) {

??????????????????? throw new RuntimeException("An error occurred while executing doInBackground()",

??????????????????????????? e.getCause());

??????????????? } catch (CancellationException e) {

???????? ???????????postResultIfNotInvoked(null);

??????????????? }

??????????? }

??????? };

??? }

? ? ? ? 這段代碼雖然看起來有點長,但實際上并沒有任何具體的邏輯會得到執行,只是初始化了兩個變量,mWorker和mFuture,并在初始化mFuture的時候將mWorker作為參數傳入。mWorker是一個Callable對象,mFuture是一個FutureTask對象,這兩個變量會暫時保存在內存中,稍后才會用到它們。 FutureTask實現了Runnable接口,關于這部分內容可以看這篇文章

? ? ? ? mWorker中的call()方法執行了耗時操作,即result = doInBackground(mParams);,然后把執行得到的結果通過postResult(result);,傳遞給內部的Handler跳轉到主線程中。在這里這是實例化了兩個變量,并沒有開啟執行任務。

? ? ? ??那么mFuture對象是怎么加載到線程池中,進行執行的呢?

? ? ? ? 接著如果想要啟動某一個任務,就需要調用該任務的execute()方法,因此現在我們來看一看execute()方法的源碼,如下所示:

public final AsyncTask execute(Params... params) {

??????? return executeOnExecutor(sDefaultExecutor, params);

??? }


????????調用了executeOnExecutor()方法,具體執行邏輯在這個方法里面:

? public final AsyncTask 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;

??? }

????????可以 看出,先執行了onPreExecute()方法,然后具體執行耗時任務是在exec.execute(mFuture),把構造函數中實例化的mFuture傳遞進去了。

? ??exec具體是什么?

????????從上面可以看出具體是sDefaultExecutor,再追溯看到是SerialExecutor類,具體源碼如下:

private static class SerialExecutor implements Executor {

??????? final ArrayDeque mTasks = new ArrayDeque();

??????? 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);

??????????? }

??????? }

??? }

????????終于追溯到了調用了SerialExecutor 類的execute方法。SerialExecutor 是個靜態內部類,是所有實例化的AsyncTask對象公有的,SerialExecutor 內部維持了一個隊列,通過鎖使得該隊列保證AsyncTask中的任務是串行執行的,即多個任務需要一個個加到該隊列中,然后執行完隊列頭部的再執行下一個,以此類推。

????????在這個方法中,有兩個主要步驟。

????① 向隊列中加入一個新的任務,即之前實例化后的mFuture對象。

????② 調用 scheduleNext()方法,調用THREAD_POOL_EXECUTOR執行隊列頭部的任務。


? ??????由此可見SerialExecutor 類僅僅為了保持任務執行是串行的,實際執行交給了THREAD_POOL_EXECUTOR

THREAD_POOL_EXECUTOR又是什么?

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

threadPoolExecutor.allowCoreThreadTimeOut(true);

THREAD_POOL_EXECUTOR = threadPoolExecutor;

????????實際是個線程池,開啟了一定數量的核心線程和工作線程。然后調用線程池的execute()方法。執行具體的耗時任務,即開頭構造函數中mWorker中call()方法的內容。先執行完doInBackground()方法,又執行postResult()方法,下面看該方法的具體內容:

private Result postResult(Result result) {

??????? @SuppressWarnings("unchecked")

??????? Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,

??????????????? new AsyncTaskResult(this, result));

??????? message.sendToTarget();

??????? return result;

}


????????該方法向Handler對象發送了一個消息,下面具體看AsyncTask中實例化的Hanlder對象的源碼:

private static class InternalHandler extends Handler {

??????? public InternalHandler() {

??????????? super(Looper.getMainLooper());

??????? }


??????? @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;

??????????? }

??????? }

??? }


????????在InternalHandler 中,如果收到的消息是MESSAGE_POST_RESULT,即執行完了doInBackground()方法并傳遞結果,那么就調用finish()方法。

private void finish(Result result) {

??????? if (isCancelled()) {

????????? ??onCancelled(result);

??????? } else {

??????????? onPostExecute(result);

??????? }

??????? mStatus = Status.FINISHED;

??? }


????????如果任務已經取消了,回調onCancelled()方法,否則回調 onPostExecute()方法。

????????如果收到的消息是MESSAGE_POST_PROGRESS,回調onProgressUpdate()方法,更新進度。

? ??????InternalHandler是一個靜態類,為了能夠將執行環境切換到主線程,因此這個類必須在主線程中進行加載。所以變相要求AsyncTask的類必須在主線程中進行加載。

????????到此為止,從任務執行的開始到結束都從源碼分析完了。

5.3.1 AsyncTask的串行和并行

????????從上述源碼分析中分析得到,默認情況下AsyncTask的執行效果是串行的,因為有了SerialExecutor類來維持保證隊列的串行。如果想使用并行執行任務,那么可以直接跳過SerialExecutor類,使用executeOnExecutor()來執行任務。

5.3.2 AsyncTask使用不當的后果

????1)生命周期

????????AsyncTask不與任何組件綁定生命周期,所以在Activity/或者Fragment中創建執行AsyncTask時,最好在Activity/Fragment的onDestory()調用 cancel(boolean);

????2) 內存泄漏

????????如果AsyncTask被聲明為Activity的非靜態的內部類,那么AsyncTask會保留一個對創建了AsyncTask的Activity的引用。如果Activity已經被銷毀,AsyncTask的后臺線程還在執行,它將繼續在內存里保留這個引用,導致Activity無法被回收,引起內存泄露。

????3) 結果丟失

????????屏幕旋轉或Activity在后臺被系統殺掉等情況會導致Activity的重新創建,之前運行的AsyncTask(非靜態的內部類)會持有一個之前Activity的引用,這個引用已經無效,這時調用onPostExecute()再去更新界面將不再生效。

6 參考鏈接

Android中的幾種多線程實現

https://www.cnblogs.com/chendu123/p/6081301.html


Android多線程的四種方式

http://mobile.51cto.com/android-537207.htm


runOnUiThread更新主線程

https://www.cnblogs.com/wanqieddy/p/4153203.html


View.postDelayed()/post()原理與使用(1)

https://www.2cto.com/kf/201708/667701.html


不可不知的開發技巧之View.Post()

http://www.lxweimin.com/p/b1d5e31e2011


Android中的線程狀態之AsyncTask詳解

http://www.lxweimin.com/p/817a34a5f200

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容