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

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

 更新時(shí)間:2023年07月04日 10:15:35   作者:星邪Ara  
這篇文章主要為大家介紹了Android 異步任務(wù)和消息機(jī)制面試題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

1.1 HandlerThread 的使用場(chǎng)景和用法?

HandlerThread 本質(zhì)上是一個(gè)在子線程的handler (HandlerThread=Handler+Thread);

它的使用:

步驟1:創(chuàng)建HandlerThread實(shí)例對(duì)象

HandlerThread mHandlerThread = new HandlerThread("handlerThread");

步驟2:?jiǎn)?dòng)線程

mHandlerThread.start();

步驟3:創(chuàng)建工作線程Handler & 復(fù)寫handleMessage()

Handler workHandler = new Handler(
handlerThread.getLooper() ) {
    @OverRide
    public boolean handleMessage(Message msg) {
        ...//消息處理
        return true;
    }
});

步驟4:使用工作線程Handler向工作線程的消息隊(duì)列發(fā)送消息

Message msg = Message.obtain();
msg.what = 2; //消息的標(biāo)識(shí)
msg.obj = "B"; // 消息的存放
// b. 通過Handler發(fā)送消息到其綁定的消息隊(duì)列
workHandler.sendMessage(msg);

步驟5:結(jié)束線程,即停止線程的消息循環(huán)

mHandlerThread.quit();

優(yōu)勢(shì):

  • 將loop運(yùn)行在子線程中處理,減輕了主線程的壓力,使主線程更流暢
  • 串行執(zhí)行,開啟一個(gè)線程起到多個(gè)線程的作用
  • 有自己的消息隊(duì)列,不會(huì)干擾UI線程

劣勢(shì):

  • 由于每一個(gè)任務(wù)隊(duì)列逐步執(zhí)行,一旦隊(duì)列耗時(shí)過長(zhǎng),消息延時(shí)
  • 對(duì)于IO等操作,線程等待,不能并發(fā)

1.2 IntentService 的應(yīng)用場(chǎng)景和使用姿勢(shì)?

IntentService 是 Service 的子類,默認(rèn)為我們開啟了一個(gè)工作線程,使用這個(gè)工作線程逐一處理所有啟動(dòng)請(qǐng)求,在任務(wù)執(zhí)行完畢后會(huì)自動(dòng)停止服務(wù),使用簡(jiǎn)單,只要實(shí)現(xiàn)一個(gè)方法 onHandleIntent,該方法會(huì)接收每個(gè)啟動(dòng)請(qǐng)求的 Intent,能夠執(zhí)行后臺(tái)工作和耗時(shí)操作??梢詥?dòng)IntentService 多次,而每一個(gè)耗時(shí)操作會(huì)以隊(duì)列的方式在 IntentService 的 onHandlerIntent 回調(diào)方法中執(zhí)行,并且,每一次只會(huì)執(zhí)行一個(gè)工作線程,執(zhí)行完第一個(gè)再執(zhí)行第二個(gè)。并且等待所有消息都執(zhí)行完后才終止服務(wù)。
IntentService 適用于 APP 在不影響當(dāng)前用戶的操作的前提下,在后臺(tái)默默的做一些操作。

IntentService源碼:

  • 通過 HandlerThread 單獨(dú)開啟一個(gè)名為IntentService 的線程
  • 創(chuàng)建一個(gè)名叫 ServiceHandler 的內(nèi)部 Handler
  • 把內(nèi)部Handler與HandlerThread所對(duì)應(yīng)的子線程進(jìn)行綁定
  • 通過 onStartCommand() 傳遞給服務(wù) intent,依次插入到工作隊(duì)列中,并逐個(gè)發(fā)送給 onHandleIntent()
  • 通過 onHandleIntent() 來依次處理所有 Intent 請(qǐng)求對(duì)象所對(duì)應(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); // 這里會(huì)打印false,說明不是主線程
        // 模擬耗時(shí)操作
        download();
    }
    /**
     * 模擬執(zhí)行下載
     */
    private void download() {
        try {
            Thread.sleep(5000);
            Log.i(TAG, "下載完成...");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.3 AsyncTask的優(yōu)點(diǎn)和缺點(diǎn)?

AsyncTask的實(shí)現(xiàn)原理:

  • AsyncTask是一個(gè)抽象類,主要由Handler+2個(gè)線程池構(gòu)成,SERIAL_EXECUTOR是任務(wù)隊(duì)列線程池,用于調(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()方法的時(shí)候,其實(shí)就是調(diào)用SERIAL_EXECUTOR的execute()方法,就是把任務(wù)添加到隊(duì)列的尾部,然后從頭開始取出隊(duì)列中的任務(wù),調(diào)用THREAD_POOL_EXECUTOR的execute()方法依次執(zhí)行,當(dāng)隊(duì)列中沒有任務(wù)時(shí)就停止。
  • AsyncTask只能執(zhí)行一次execute(params)方法,否則會(huì)報(bào)錯(cuò)。但是SERIAL_EXECUTOR和
    THREAD_POOL_EXECUTOR線程池都是靜態(tài)的,所以可以形成隊(duì)列。

Q:AsyncTask只能執(zhí)行一次execute()方法,那么為什么用線程池隊(duì)列管理 ?

因?yàn)镾ERIAL_EXECUTOR和THREAD_POOL_EXECUTOR線程池都是靜態(tài)的,所有的AsyncTask實(shí)例都共享這2個(gè)線程池,因此形成了隊(duì)列。

Q:AsyncTask的onPreExecute()、doInBackground()、onPostExecute()方法的調(diào)用流程?

AsyncTask在創(chuàng)建對(duì)象的時(shí)候,會(huì)在構(gòu)造函數(shù)中創(chuàng)建mWorker(workerRunnable) mFuture(FutureTask)對(duì)象。
mWorker實(shí)現(xiàn)了Callable接口的call()方法,在call()方法中,調(diào)用了doInBackground()方法,并在最后調(diào)用了postResult()方法,也就是通過Handler發(fā)送消息給主線程,在主線程中調(diào)用AsyncTask的finish()方法,決定是調(diào)用onCancelled()還是onPostExecute().

mFuture實(shí)現(xiàn)了Runnable和Future接口,在創(chuàng)建對(duì)象時(shí),初始化成員變量mWorker,在run()方法中,調(diào)用mWorker的call()方法。

當(dāng)asyncTask執(zhí)行execute()方法的時(shí)候,會(huì)先調(diào)用onPreExecute()方法,然后調(diào)用SERIAL_EXECUTOR的execute(mFuture),把任務(wù)加入到隊(duì)列的尾部等待執(zhí)行。執(zhí)行的時(shí)候調(diào)用THREAD_POOL_EXECUTOR的execute(mFuture).

1.4 談?wù)勀銓?duì) Activity.runOnUiThread 的理解?

一般是用來將一個(gè)Runnable綁定到主線程,在runOnUiThread源碼里面會(huì)判斷當(dāng)前Runnable是否是主線程,如果是直接run,如果不是,通過一個(gè)默認(rèn)的空構(gòu)造函數(shù)Handler將Runnable post 到looper里面,創(chuàng)建構(gòu)造函數(shù)Handler,會(huì)默認(rèn)綁定一個(gè)主線程的looper對(duì)象

1.5 子線程能否更新UI?為什么?

子線程是不能直接更新UI的注意這句話,是不能直接更新,不是不能更新(極端情況
下可更新)

繪制過程要保持同步(否則頁面不流暢),而我們的主線程負(fù)責(zé)繪制UI,極端情況就是,在Activity的onResume(含)之前的生命周期中子線程都可以進(jìn)行更新UI,也就是 onCreate,onStart和onResume,此時(shí)主線程的繪制還沒開始。

1.6 談?wù)?Handler 機(jī)制和原理?

首先在UI線程我們創(chuàng)建了一個(gè)Handler實(shí)例對(duì)象,無論是匿名內(nèi)部類還是自定義類生成的Handler實(shí)例對(duì)象,我們都需要對(duì)handleMessage方法進(jìn)行重寫,在handleMessage方法中我們可以通過參數(shù)msg來寫接受消息過后UI線程的邏輯處理,接著我們創(chuàng)建子線程,在子線程中需要更新UI的時(shí)候,新建一個(gè)Message對(duì)象,并且將消息的數(shù)據(jù)記錄在這個(gè)消息對(duì)象Message的內(nèi)部,比如arg1,arg2,obj等,然后通過前面的Handler實(shí)例對(duì)象調(diào)用sendMessge方法把這個(gè)Message實(shí)例對(duì)象發(fā)送出去,之后這個(gè)消息會(huì)被存放于MessageQueue中等待被處理,此時(shí)MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出來,通過回調(diào)dispatchMessage方法將消息傳遞給Handler的handleMessage方法,最終前面提到的消息會(huì)被Looper從MessageQueue中取出來傳遞給handleMessage方法。

1.7 為什么在子線程中創(chuàng)建Handler會(huì)拋異常?

不能在還沒有調(diào)用 Looper.prepare() 方法的線程中創(chuàng)建Handler。

因?yàn)閽伋霎惓5牡胤剑趍Looper 對(duì)象為null的時(shí)候,會(huì)拋出異常。說明這里的Looper.myLooper();的返回值是null。 只有調(diào)用了Looper.prepare()方法,才會(huì)構(gòu)造一個(gè)Looper對(duì)象并在 ThreadLocal 存儲(chǔ)當(dāng)前線程的Looper 對(duì)象。

這樣在調(diào)用 Looper.myLooper() 時(shí),獲取的結(jié)果就不會(huì)為null。

1.8 試從源碼角度分析Handler的post和sendMessage方法的區(qū)別和應(yīng)用場(chǎng)景?

handler.post和handler.sendMessage方法最后都會(huì)調(diào)用sendMessageAtTime方法進(jìn)行消息的發(fā)送,但是在post方法中message是通過getPostMessage(Runnable r)這個(gè)方法獲取的message,在這個(gè)方法中有這樣一句代碼m.callback = r ,給message的callback賦值為runnable對(duì)象,而在dispatchMessage這個(gè)方法中對(duì)消息進(jìn)行分發(fā)的時(shí)候,先進(jìn)行了msg.callback != null的判斷,如果不為null,消息是通過handleCallback(msg);這個(gè)方法處理的,在這個(gè)方法中message.callback.run();調(diào)用的是post方法傳遞過來的runnable內(nèi)的run方法處理消息,如果為空,再進(jìn)行handler內(nèi)部的callback判斷mCallback != null,如果handler內(nèi)的callback不為空,執(zhí)行mCallback.handleMessage(msg)這個(gè)處理消息并判斷返回是否為true,如果返回true,消息處理結(jié)束,如果返回false,消息交給handler的handleMessage(msg)處理。

所以區(qū)別就是調(diào)用post方法的消息是在post傳遞的Runnable對(duì)象的run方法中處理,而調(diào)用sendMessage方法需要重寫handleMessage方法或者給handler設(shè)置callback,在callback的handleMessage中處理并返回true。

1.9 Handler中有Loop死循環(huán),為什么沒有阻塞主線程,原理是什么?

主線程掛起

Looper 是一個(gè)死循環(huán), 不斷的讀取MessageQueue中的消息, loop 方法會(huì)調(diào)用 MessageQueue 的 next 方法來獲取新的消息,next 操作是一個(gè)阻塞操作,當(dāng)沒有消息的時(shí)候 next 方法會(huì)一直阻塞, 進(jìn)而導(dǎo)致 loop 一直阻塞,理論上 messageQueue.nativePollOnce 會(huì)讓線程掛起-阻塞-block 住, 但是為什么, 在發(fā)送 delay 10s 的消息, 假設(shè)消息隊(duì)列中, 目前只有這一個(gè)消息;

那么為什么在這 10s 內(nèi), UI是可操作的, 或者列表頁是可滑動(dòng)的, 或者動(dòng)畫還是可以執(zhí)行的?

先不講 nativePollOnce 是怎么實(shí)現(xiàn)的阻塞, 我們還知道, 另外一個(gè) nativeWake, 是實(shí)現(xiàn)線程喚醒的;

那么什么時(shí)候會(huì), 觸發(fā)這個(gè)方法的調(diào)用呢, 就是在有新消息添加進(jìn)來的時(shí)候, 可是并沒有手動(dòng)添加消息啊?

display 每隔16毫秒, 刷新一次屏幕;

SurfaceFlingerVsyncChoreographer 每隔16毫秒, 發(fā)送一個(gè) vSync 信號(hào);

FrameDisplayEventReceiver 收到信號(hào)后, 調(diào)用 onVsync方法, 通過 handler 消息發(fā)送到主線程處理, 所以就會(huì)有消息添加進(jìn)來, UI線程就會(huì)被喚醒;

事實(shí)上, 安卓系統(tǒng), 不止有一個(gè)屏幕刷新的信號(hào), 還有其他的機(jī)制, 比如輸入法和系統(tǒng)廣播, 也會(huì)往主線程的MessageQueue 添加消息;

所以, 可以理解為, 主線程也是隨時(shí)掛起, 隨時(shí)被阻塞的;

系統(tǒng)怎么實(shí)現(xiàn)的阻塞與喚醒

這種機(jī)制是通過pipe(管道)機(jī)制實(shí)現(xiàn)的;

簡(jiǎn)單來說, 管道就是一個(gè)文件在管道的兩端, 分別是兩個(gè)打開文件的, 文件描述符, 這兩個(gè)打開文件描述符, 都是對(duì)應(yīng)同一個(gè)文件, 其中一個(gè)是用來讀的, 別一個(gè)是用來寫的;

一般的使用方式就是, 一個(gè)線程通過讀文件描述符, 來讀管道的內(nèi)容, 當(dāng)管道沒有內(nèi)容時(shí), 這個(gè)線程就會(huì)進(jìn)入等待狀態(tài),
而另外一個(gè)線程, 通過寫文件描述符, 來向管道中寫入內(nèi)容,寫入內(nèi)容的時(shí)候, 如果另一端正有線程, 正在等待管道中的內(nèi)容, 那么這個(gè)線程就會(huì)被喚醒;

這個(gè)等待和喚醒的操作是如何進(jìn)行的呢, 這就要借助 Linux系統(tǒng)中的 epoll 機(jī)制了, Linux 系統(tǒng)中的 epoll 機(jī)制為處理 大批量句柄而作了改進(jìn)的 poll,是 Linux 下多路復(fù)用 IO 接口 select/poll 的增強(qiáng)版本, 它能顯著減少程序, 在大量并發(fā)連接中, 只有少量活躍的情況下的系統(tǒng) CPU 利用率;

即當(dāng)管道中有內(nèi)容可讀時(shí), 就喚醒當(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ù)和消息機(jī)制面試題分析的詳細(xì)內(nèi)容,更多關(guān)于Android 異步任務(wù)消息機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論