Android 異步任務(wù)和消息機制面試題分析
1.1 HandlerThread 的使用場景和用法?
HandlerThread 本質(zhì)上是一個在子線程的handler (HandlerThread=Handler+Thread);
它的使用:
步驟1:創(chuàng)建HandlerThread實例對象
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
步驟2:啟動線程
mHandlerThread.start();
步驟3:創(chuàng)建工作線程Handler & 復(fù)寫handleMessage()
Handler workHandler = new Handler( handlerThread.getLooper() ) { @OverRide public boolean handleMessage(Message msg) { ...//消息處理 return true; } });
步驟4:使用工作線程Handler向工作線程的消息隊列發(fā)送消息
Message msg = Message.obtain(); msg.what = 2; //消息的標(biāo)識 msg.obj = "B"; // 消息的存放 // b. 通過Handler發(fā)送消息到其綁定的消息隊列 workHandler.sendMessage(msg);
步驟5:結(jié)束線程,即停止線程的消息循環(huán)
mHandlerThread.quit();
優(yōu)勢:
- 將loop運行在子線程中處理,減輕了主線程的壓力,使主線程更流暢
- 串行執(zhí)行,開啟一個線程起到多個線程的作用
- 有自己的消息隊列,不會干擾UI線程
劣勢:
- 由于每一個任務(wù)隊列逐步執(zhí)行,一旦隊列耗時過長,消息延時
- 對于IO等操作,線程等待,不能并發(fā)
1.2 IntentService 的應(yīng)用場景和使用姿勢?
IntentService 是 Service 的子類,默認為我們開啟了一個工作線程,使用這個工作線程逐一處理所有啟動請求,在任務(wù)執(zhí)行完畢后會自動停止服務(wù),使用簡單,只要實現(xiàn)一個方法 onHandleIntent,該方法會接收每個啟動請求的 Intent,能夠執(zhí)行后臺工作和耗時操作??梢詥覫ntentService 多次,而每一個耗時操作會以隊列的方式在 IntentService 的 onHandlerIntent 回調(diào)方法中執(zhí)行,并且,每一次只會執(zhí)行一個工作線程,執(zhí)行完第一個再執(zhí)行第二個。并且等待所有消息都執(zhí)行完后才終止服務(wù)。
IntentService 適用于 APP 在不影響當(dāng)前用戶的操作的前提下,在后臺默默的做一些操作。
IntentService源碼:
- 通過 HandlerThread 單獨開啟一個名為IntentService 的線程
- 創(chuàng)建一個名叫 ServiceHandler 的內(nèi)部 Handler
- 把內(nèi)部Handler與HandlerThread所對應(yīng)的子線程進行綁定
- 通過 onStartCommand() 傳遞給服務(wù) intent,依次插入到工作隊列中,并逐個發(fā)送給 onHandleIntent()
- 通過 onHandleIntent() 來依次處理所有 Intent 請求對象所對應(yīng)的任務(wù)
使用示例:
public class MyIntentService extends IntentService { public static final String TAG = "MyIntentService"; public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(@Nullable Intent intent) { boolean isMainThread = Thread.currentThread() == Looper.getMainLooper().getThread(); Log.i(TAG, "is main thread:" + isMainThread); // 這里會打印false,說明不是主線程 // 模擬耗時操作 download(); } /** * 模擬執(zhí)行下載 */ private void download() { try { Thread.sleep(5000); Log.i(TAG, "下載完成..."); } catch (Exception e) { e.printStackTrace(); } } }
1.3 AsyncTask的優(yōu)點和缺點?
AsyncTask的實現(xiàn)原理:
- AsyncTask是一個抽象類,主要由Handler+2個線程池構(gòu)成,SERIAL_EXECUTOR是任務(wù)隊列線程池,用于調(diào)度任務(wù),按順序排列執(zhí)行,THREAD_POOL_EXECUTOR是執(zhí)行線程池,真正執(zhí)行具體的線程任務(wù)。Handler用于工作線程和主線程的異步通信。
- AsyncTask<Params,Progress,Result>,其中Params是doInBackground()方法的參數(shù)類型,Result是doInBackground()方法的返回值類型,Progress是onProgressUpdate()方法的參數(shù)類型。
- 當(dāng)執(zhí)行execute()方法的時候,其實就是調(diào)用SERIAL_EXECUTOR的execute()方法,就是把任務(wù)添加到隊列的尾部,然后從頭開始取出隊列中的任務(wù),調(diào)用THREAD_POOL_EXECUTOR的execute()方法依次執(zhí)行,當(dāng)隊列中沒有任務(wù)時就停止。
- AsyncTask只能執(zhí)行一次execute(params)方法,否則會報錯。但是SERIAL_EXECUTOR和
THREAD_POOL_EXECUTOR線程池都是靜態(tài)的,所以可以形成隊列。
Q:AsyncTask只能執(zhí)行一次execute()方法,那么為什么用線程池隊列管理 ?
因為SERIAL_EXECUTOR和THREAD_POOL_EXECUTOR線程池都是靜態(tài)的,所有的AsyncTask實例都共享這2個線程池,因此形成了隊列。
Q:AsyncTask的onPreExecute()、doInBackground()、onPostExecute()方法的調(diào)用流程?
AsyncTask在創(chuàng)建對象的時候,會在構(gòu)造函數(shù)中創(chuàng)建mWorker(workerRunnable) mFuture(FutureTask)對象。
mWorker實現(xiàn)了Callable接口的call()方法,在call()方法中,調(diào)用了doInBackground()方法,并在最后調(diào)用了postResult()方法,也就是通過Handler發(fā)送消息給主線程,在主線程中調(diào)用AsyncTask的finish()方法,決定是調(diào)用onCancelled()還是onPostExecute().
mFuture實現(xiàn)了Runnable和Future接口,在創(chuàng)建對象時,初始化成員變量mWorker,在run()方法中,調(diào)用mWorker的call()方法。
當(dāng)asyncTask執(zhí)行execute()方法的時候,會先調(diào)用onPreExecute()方法,然后調(diào)用SERIAL_EXECUTOR的execute(mFuture),把任務(wù)加入到隊列的尾部等待執(zhí)行。執(zhí)行的時候調(diào)用THREAD_POOL_EXECUTOR的execute(mFuture).
1.4 談?wù)勀銓?Activity.runOnUiThread 的理解?
一般是用來將一個Runnable綁定到主線程,在runOnUiThread源碼里面會判斷當(dāng)前Runnable是否是主線程,如果是直接run,如果不是,通過一個默認的空構(gòu)造函數(shù)Handler將Runnable post 到looper里面,創(chuàng)建構(gòu)造函數(shù)Handler,會默認綁定一個主線程的looper對象
1.5 子線程能否更新UI?為什么?
子線程是不能直接更新UI的注意這句話,是不能直接更新,不是不能更新(極端情況
下可更新)
繪制過程要保持同步(否則頁面不流暢),而我們的主線程負責(zé)繪制UI,極端情況就是,在Activity的onResume(含)之前的生命周期中子線程都可以進行更新UI,也就是 onCreate,onStart和onResume,此時主線程的繪制還沒開始。
1.6 談?wù)?Handler 機制和原理?
首先在UI線程我們創(chuàng)建了一個Handler實例對象,無論是匿名內(nèi)部類還是自定義類生成的Handler實例對象,我們都需要對handleMessage方法進行重寫,在handleMessage方法中我們可以通過參數(shù)msg來寫接受消息過后UI線程的邏輯處理,接著我們創(chuàng)建子線程,在子線程中需要更新UI的時候,新建一個Message對象,并且將消息的數(shù)據(jù)記錄在這個消息對象Message的內(nèi)部,比如arg1,arg2,obj等,然后通過前面的Handler實例對象調(diào)用sendMessge方法把這個Message實例對象發(fā)送出去,之后這個消息會被存放于MessageQueue中等待被處理,此時MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出來,通過回調(diào)dispatchMessage方法將消息傳遞給Handler的handleMessage方法,最終前面提到的消息會被Looper從MessageQueue中取出來傳遞給handleMessage方法。
1.7 為什么在子線程中創(chuàng)建Handler會拋異常?
不能在還沒有調(diào)用 Looper.prepare() 方法的線程中創(chuàng)建Handler。
因為拋出異常的地方,在mLooper 對象為null的時候,會拋出異常。說明這里的Looper.myLooper();的返回值是null。 只有調(diào)用了Looper.prepare()方法,才會構(gòu)造一個Looper對象并在 ThreadLocal 存儲當(dāng)前線程的Looper 對象。
這樣在調(diào)用 Looper.myLooper() 時,獲取的結(jié)果就不會為null。
1.8 試從源碼角度分析Handler的post和sendMessage方法的區(qū)別和應(yīng)用場景?
handler.post和handler.sendMessage方法最后都會調(diào)用sendMessageAtTime方法進行消息的發(fā)送,但是在post方法中message是通過getPostMessage(Runnable r)這個方法獲取的message,在這個方法中有這樣一句代碼m.callback = r ,給message的callback賦值為runnable對象,而在dispatchMessage這個方法中對消息進行分發(fā)的時候,先進行了msg.callback != null的判斷,如果不為null,消息是通過handleCallback(msg);這個方法處理的,在這個方法中message.callback.run();調(diào)用的是post方法傳遞過來的runnable內(nèi)的run方法處理消息,如果為空,再進行handler內(nèi)部的callback判斷mCallback != null,如果handler內(nèi)的callback不為空,執(zhí)行mCallback.handleMessage(msg)這個處理消息并判斷返回是否為true,如果返回true,消息處理結(jié)束,如果返回false,消息交給handler的handleMessage(msg)處理。
所以區(qū)別就是調(diào)用post方法的消息是在post傳遞的Runnable對象的run方法中處理,而調(diào)用sendMessage方法需要重寫handleMessage方法或者給handler設(shè)置callback,在callback的handleMessage中處理并返回true。
1.9 Handler中有Loop死循環(huán),為什么沒有阻塞主線程,原理是什么?
主線程掛起
Looper 是一個死循環(huán), 不斷的讀取MessageQueue中的消息, loop 方法會調(diào)用 MessageQueue 的 next 方法來獲取新的消息,next 操作是一個阻塞操作,當(dāng)沒有消息的時候 next 方法會一直阻塞, 進而導(dǎo)致 loop 一直阻塞,理論上 messageQueue.nativePollOnce 會讓線程掛起-阻塞-block 住, 但是為什么, 在發(fā)送 delay 10s 的消息, 假設(shè)消息隊列中, 目前只有這一個消息;
那么為什么在這 10s 內(nèi), UI是可操作的, 或者列表頁是可滑動的, 或者動畫還是可以執(zhí)行的?
先不講 nativePollOnce 是怎么實現(xiàn)的阻塞, 我們還知道, 另外一個 nativeWake, 是實現(xiàn)線程喚醒的;
那么什么時候會, 觸發(fā)這個方法的調(diào)用呢, 就是在有新消息添加進來的時候, 可是并沒有手動添加消息啊?
display 每隔16毫秒, 刷新一次屏幕;
SurfaceFlingerVsyncChoreographer 每隔16毫秒, 發(fā)送一個 vSync 信號;
FrameDisplayEventReceiver 收到信號后, 調(diào)用 onVsync方法, 通過 handler 消息發(fā)送到主線程處理, 所以就會有消息添加進來, UI線程就會被喚醒;
事實上, 安卓系統(tǒng), 不止有一個屏幕刷新的信號, 還有其他的機制, 比如輸入法和系統(tǒng)廣播, 也會往主線程的MessageQueue 添加消息;
所以, 可以理解為, 主線程也是隨時掛起, 隨時被阻塞的;
系統(tǒng)怎么實現(xiàn)的阻塞與喚醒
這種機制是通過pipe(管道)機制實現(xiàn)的;
簡單來說, 管道就是一個文件在管道的兩端, 分別是兩個打開文件的, 文件描述符, 這兩個打開文件描述符, 都是對應(yīng)同一個文件, 其中一個是用來讀的, 別一個是用來寫的;
一般的使用方式就是, 一個線程通過讀文件描述符, 來讀管道的內(nèi)容, 當(dāng)管道沒有內(nèi)容時, 這個線程就會進入等待狀態(tài),
而另外一個線程, 通過寫文件描述符, 來向管道中寫入內(nèi)容,寫入內(nèi)容的時候, 如果另一端正有線程, 正在等待管道中的內(nèi)容, 那么這個線程就會被喚醒;
這個等待和喚醒的操作是如何進行的呢, 這就要借助 Linux系統(tǒng)中的 epoll 機制了, Linux 系統(tǒng)中的 epoll 機制為處理 大批量句柄而作了改進的 poll,是 Linux 下多路復(fù)用 IO 接口 select/poll 的增強版本, 它能顯著減少程序, 在大量并發(fā)連接中, 只有少量活躍的情況下的系統(tǒng) CPU 利用率;
即當(dāng)管道中有內(nèi)容可讀時, 就喚醒當(dāng)前正在等待管道中的內(nèi)容的線程;
怎么證明, 線程被掛起了
@Override public void onCreateData(@Nullable Bundle bundle) { new Thread() { @SuppressLint("HandlerLeak") @Override public void run() { super.run(); LogTrack.v("thread.id = " + Thread.currentThread().getId()); Looper.prepare(); Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); LogTrack.v("thread.id = " + Thread.currentThread().getId() + ", what = " + msg.what); } }; LogTrack.w("loop.之前"); // 執(zhí)行了 Looper.loop(); // 執(zhí)行了 LogTrack.w("loop.之后"); // 無法執(zhí)行 } }.start(); }
以上就是Android 異步任務(wù)和消息機制面試題分析的詳細內(nèi)容,更多關(guān)于Android 異步任務(wù)消息機制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)筆記之:返回鍵的復(fù)寫onBackPressed()介紹
本篇文章是對Android中返回鍵的復(fù)寫onBackPressed()進行了詳細的分析介紹,需要的朋友參考下2013-05-05Android?WindowManager深層理解view繪制實現(xiàn)流程
WindowManager是Android中一個重要的Service,是全局且唯一的。WindowManager繼承自ViewManager。WindowManager主要用來管理窗口的一些狀態(tài)、屬性、view增加、刪除、更新、窗口順序、消息收集和處理等2022-11-11詳解Android Activity中的幾種監(jiān)聽器和實現(xiàn)方式
這篇文章主要介紹了Activity中的幾種監(jiān)聽器和實現(xiàn)方式的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-04-04解決Android-RecyclerView列表倒計時錯亂問題
這篇文章主要介紹了解決Android-RecyclerView列表倒計時錯亂問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08Windows下Flutter+Idea環(huán)境搭建及配置
這篇文章介紹了Windows下Flutter+Idea環(huán)境搭建及配置的方法,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-12-12Android時間對話框TimePickerDialog詳解
這篇文章主要為大家詳細介紹了Android時間對話框TimePickerDialog的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02Android Studio實現(xiàn)長方體表面積計算器
這篇文章主要為大家詳細介紹了Android Studio實現(xiàn)長方體表面積計算器,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05