欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android 異步任務(wù)和消息機制面試題分析

 更新時間:2023年07月04日 10:15:35   作者:星邪Ara  
這篇文章主要為大家介紹了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)文章

最新評論