成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

Android之消息機(jī)制問(wèn)題

番茄西紅柿 / 3038人閱讀

摘要:通過(guò)向消息池發(fā)送各種消息事件通過(guò)處理相應(yīng)的消息事件。消息泵通過(guò)不斷地從中抽取,按分發(fā)機(jī)制將消息分發(fā)給目標(biāo)處理者。也稱(chēng)之為消息隊(duì)列,特點(diǎn)是先進(jìn)先出,底層實(shí)現(xiàn)是單鏈表數(shù)據(jù)結(jié)構(gòu)。

目錄介紹

6.0.0.1 談?wù)勏C(jī)制Hander作用?有哪些要素?流程是怎樣的?簡(jiǎn)單說(shuō)一下你的看法!

6.0.0.2 為什么一個(gè)線程只有一個(gè)Looper、只有一個(gè)MessageQueue,可以有多個(gè)Handler?

6.0.0.3 可以在子線程直接new一個(gè)Handler嗎?會(huì)出現(xiàn)什么問(wèn)題,如何在子線程中使用handler?

6.0.0.4 說(shuō)一下Handler內(nèi)存泄漏有哪些?造成造成內(nèi)存泄漏原因是什么?如何解決handler造成的內(nèi)存泄漏?

6.0.0.5 Activity如何自動(dòng)綁定Looper?主線程中的Looper死循環(huán)和binder線程中的死循環(huán)有哪些區(qū)別?

6.0.0.6 為什么系統(tǒng)不建議在子線程訪問(wèn)UI,不對(duì)UI控件的訪問(wèn)加上鎖機(jī)制的原因?

6.0.0.7 Looper.loop是一個(gè)死循環(huán),拿不到需要處理的Message就會(huì)阻塞,那在UI線程中為什么不會(huì)導(dǎo)致ANR?

6.0.0.8 Handler.sendMessageDelayed()怎么實(shí)現(xiàn)延遲的?結(jié)合Looper.loop()循環(huán)中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。

6.0.0.9 Message可以如何創(chuàng)建?哪種效果更好,為什么?

6.0.1.0 MessageQueue作用是干什么的?MessageQueue的定義是什么?MessageQueue主要工作原理是怎樣的?

6.0.1.1 子線程更新UI有哪些方式?runOnUiThread如何實(shí)現(xiàn)子線程更新UI?View.post(Runnable r)更新UI?

6.0.1.3 使用Hanlder的postDealy()后消息隊(duì)列會(huì)發(fā)生什么變化?

6.0.1.4 ThreadLocal有什么作用?如何避免UI線程盡量只做跟UI相關(guān)的工作);

6.0.1.5 為什么一個(gè)線程只有一個(gè)Looper、只有一個(gè)MessageQueue,可以有多個(gè)Handler?

好消息

博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開(kāi)發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善……開(kāi)源的文件是markdown格式的!同時(shí)也開(kāi)源了生活博客,從12年起,積累共計(jì)N篇[近100萬(wàn)字,陸續(xù)搬到網(wǎng)上],轉(zhuǎn)載請(qǐng)注明出處,謝謝!

鏈接地址:github.com/yangchong21…

如果覺(jué)得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬(wàn)事起于忽微,量變引起質(zhì)變!

6.0.0.1 談?wù)勏C(jī)制Hander作用?有哪些要素?流程是怎樣的?簡(jiǎn)單說(shuō)一下你的看法!

作用:

跨線程通信。當(dāng)子線程中進(jìn)行耗時(shí)操作后需要更新UI時(shí),通過(guò)Handler將有關(guān)UI的操作切換到主線程中執(zhí)行。

四要素:

Message(消息):需要被傳遞的消息,其中包含了消息ID,消息處理對(duì)象以及處理的數(shù)據(jù)等,由MessageQueue統(tǒng)一列隊(duì),最終由Handler處理。技術(shù)博客大總結(jié)

MessageQueue(消息隊(duì)列):用來(lái)存放Handler發(fā)送過(guò)來(lái)的消息,內(nèi)部通過(guò)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表,等待Looper的抽取。

Handler(處理者):負(fù)責(zé)Message的發(fā)送及處理。通過(guò) Handler.sendMessage() 向消息池發(fā)送各種消息事件;通過(guò) Handler.handleMessage() 處理相應(yīng)的消息事件。

Looper(消息泵):通過(guò)Looper.loop()不斷地從MessageQueue中抽取Message,按分發(fā)機(jī)制將消息分發(fā)給目標(biāo)處理者。

具體流程

Handler.sendMessage()發(fā)送消息時(shí),會(huì)通過(guò)MessageQueue.enqueueMessage()向MessageQueue中添加一條消息;

通過(guò)Looper.loop()開(kāi)啟循環(huán)后,不斷輪詢(xún)調(diào)用MessageQueue.next();

調(diào)用目標(biāo)Handler.dispatchMessage()去傳遞消息,目標(biāo)Handler收到消息后調(diào)用Handler.handlerMessage()處理消息。

6.0.0.2 Handler的post方法和view的post方法有什么區(qū)別?為什么說(shuō)要避免在子線程中手動(dòng)創(chuàng)建looper?

Handler的post方法實(shí)現(xiàn)很簡(jiǎn)單,如下所示

mHandler.post(new Runnable() {
    @Override
    public void run() {

    }
});

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

view的post方法也很簡(jiǎn)單,如下所示

可以發(fā)現(xiàn)其調(diào)用的就是activity中默認(rèn)保存的handler對(duì)象的post方法

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    ViewRootImpl.getRunQueue().post(action);
    return true;
}

public void post(Runnable action) {
    postDelayed(action, 0);
}

public void postDelayed(Runnable action, long delayMillis) {
    final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

    synchronized (this) {
        if (mActions == null) {
            mActions = new HandlerAction[4];
        }
        mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
        mCount++;
    }
}

為什么說(shuō)要避免在子線程中手動(dòng)創(chuàng)建looper?

下面這種使用方式,是非常危險(xiǎn)的一種做法

在子線程中,如果手動(dòng)為其創(chuàng)建Looper,那么在所有的事情完成以后應(yīng)該調(diào)用quit方法來(lái)終止消息循環(huán),否則這個(gè)子線程就會(huì)一直處于等待的狀態(tài),而如果退出Looper以后,這個(gè)線程就會(huì)立刻終止,因此建議不需要的時(shí)候終止Looper。(【 Looper.myLooper().quit(); 】)

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show();
        Looper.loop();
    }
}).start();

6.0.0.3 可以在子線程直接new一個(gè)Handler嗎?會(huì)出現(xiàn)什么問(wèn)題,那該怎么做?

直接在子線程中創(chuàng)建handler,看看會(huì)出現(xiàn)什么情況?博客

運(yùn)行后可以得出在子線程中定義Handler對(duì)象出錯(cuò),難道Handler對(duì)象的定義或者是初始化只能在主線程中?其實(shí)不是這樣的,錯(cuò)誤信息中提示的已經(jīng)很明顯了,在初始化Handler對(duì)象之前需要調(diào)用Looper.prepare()方法。

Handler的工作是依賴(lài)于Looper的,而Looper(與消息隊(duì)列)又是屬于某一個(gè)線程(ThreadLocal是線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類(lèi),通過(guò)它可以在指定線程中存儲(chǔ)數(shù)據(jù),其他線程則無(wú)法獲取到),其他線程不能訪問(wèn)。因此Handler就是間接跟線程是綁定在一起了。因此要使用Handler必須要保證Handler所創(chuàng)建的線程中有Looper對(duì)象并且啟動(dòng)循環(huán)。因?yàn)樽泳€程中默認(rèn)是沒(méi)有Looper的,所以會(huì)報(bào)錯(cuò)。

tv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread() {
            @Override
            public void run() {
                Handler mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        if (msg.what == 1) {
                            Log.i(TAG, "在子線程中定義Handler,接收并處理消息");
                        }
                    }
                };
            }
        }.start();
    }
});

如何正確運(yùn)行。在這里問(wèn)一個(gè)問(wèn)題,在子線程中可以吐司嗎?答案是可以的,只不過(guò)又條件,詳細(xì)可以看這篇文章02.Toast源碼深度分析

這樣程序已經(jīng)不會(huì)報(bào)錯(cuò),那么這說(shuō)明初始化Handler對(duì)象的時(shí)候我們是需要調(diào)用Looper.prepare()的,那么主線程中為什么可以直接初始化Handler呢?難道是主線程創(chuàng)建handler對(duì)象的時(shí)候,會(huì)自動(dòng)調(diào)用Looper.prepare()方法的嗎?博客

tv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Handler mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        if (msg.what == 1) {
                            Log.i(TAG, "在子線程中定義Handler,接收并處理消息");
                        }
                    }
                };
                //獲取Looper對(duì)象
                mLooper = Looper.myLooper();
                Looper.loop();
                //在適當(dāng)?shù)臅r(shí)候退出Looper的消息循環(huán),防止內(nèi)存泄漏
                mLooper.quit();
            }
        }.start();
    }
});

6.0.0.4 說(shuō)一下Handler內(nèi)存泄漏有哪些?造成造成內(nèi)存泄漏原因是什么?如何解決handler造成的內(nèi)存泄漏?

解決Handler內(nèi)存泄露主要2點(diǎn)

有延時(shí)消息,要在Activity銷(xiāo)毀的時(shí)候移除Messages

匿名內(nèi)部類(lèi)導(dǎo)致的泄露改為匿名靜態(tài)內(nèi)部類(lèi),并且對(duì)上下文或者Activity使用弱引用。博客

問(wèn)題代碼

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler();
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.text);        //模擬內(nèi)存泄露
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("yangchong");
            }
        }, 2000);
    }
}

造成內(nèi)存泄漏原因分析

上述代碼通過(guò)內(nèi)部類(lèi)的方式創(chuàng)建mHandler對(duì)象,此時(shí)mHandler會(huì)隱式地持有一個(gè)外部類(lèi)對(duì)象引用這里就是MainActivity,當(dāng)執(zhí)行postDelayed方法時(shí),該方法會(huì)將你的Handler裝入一個(gè)Message,并把這條Message推到MessageQueue中,MessageQueue是在一個(gè)Looper線程中不斷輪詢(xún)處理消息,那么當(dāng)這個(gè)Activity退出時(shí)消息隊(duì)列中還有未處理的消息或者正在處理消息,而消息隊(duì)列中的Message持有mHandler實(shí)例的引用,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無(wú)法及時(shí)回收,引發(fā)內(nèi)存泄漏。

如何解決handler造成的內(nèi)存泄漏

第一種解決辦法

要想避免Handler引起內(nèi)存泄漏問(wèn)題,需要我們?cè)贏ctivity關(guān)閉退出的時(shí)候的移除消息隊(duì)列中所有消息和所有的Runnable。

上述代碼只需在onDestroy()函數(shù)中調(diào)用mHandler.removeCallbacksAndMessages(null);就行了。

@Override
protected void onDestroy() {
    super.onDestroy();
    if(handler!=null){
        handler.removeCallbacksAndMessages(null);
        handler = null;
    }
}

第二種解決方案

使用弱引用解決handler內(nèi)存泄漏問(wèn)題,關(guān)于代碼案例,可以參考我的開(kāi)源項(xiàng)目:github.com/yangchong21…

//自定義handler
public static class HandlerHolder extends Handler {
    WeakReference mListenerWeakReference;
    /**
     * @param listener 收到消息回調(diào)接口
     */
    HandlerHolder(OnReceiveMessageListener listener) {
        mListenerWeakReference = new WeakReference<>(listener);
    }

    @Override
    public void handleMessage(Message msg) {
        if (mListenerWeakReference!=null && mListenerWeakReference.get()!=null){
            mListenerWeakReference.get().handlerMessage(msg);
        }
    }
}

//創(chuàng)建handler對(duì)象
private HandlerHolder handler = new HandlerHolder(new OnReceiveMessageListener() {
    @Override
    public void handlerMessage(Message msg) {
        switch (msg.what){
            case 1:
                TextView textView1 = (TextView) msg.obj;
                showBottomInAnimation(textView1);
                break;
            case 2:
                TextView textView2 = (TextView) msg.obj;
                showBottomOutAnimation(textView2);
                break;
        }
    }
});

//發(fā)送消息
Message message = new Message();
message.what = 1;
message.obj = textView;
handler.sendMessageDelayed(message,time);


即推薦使用靜態(tài)內(nèi)部類(lèi) + WeakReference 這種方式。每次使用前注意判空。

6.0.0.5 Activity如何自動(dòng)綁定Looper?主線程中的Looper死循環(huán)和binder線程中的死循環(huán)有哪些區(qū)別?

主線程如何自動(dòng)調(diào)用Looper.prepare()。那就是ActivityThread,并且在main方法中我們會(huì)看到主線程也是通過(guò)Looper方式來(lái)維持一個(gè)消息循環(huán)。那么這個(gè)死循環(huán)會(huì)不會(huì)導(dǎo)致應(yīng)用卡死,即使不會(huì)的話(huà),它會(huì)慢慢的消耗越來(lái)越多的資源嗎?

對(duì)于線程即是一段可執(zhí)行的代碼,當(dāng)可執(zhí)行代碼執(zhí)行完成后,線程生命周期便該終止了,線程退出。而對(duì)于主線程,我們是絕不希望會(huì)被運(yùn)行一段時(shí)間,自己就退出,那么如何保證能一直存活呢?簡(jiǎn)單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會(huì)被退出。

例如,binder線程也是采用死循環(huán)的方法,通過(guò)循環(huán)方式不同與Binder驅(qū)動(dòng)進(jìn)行讀寫(xiě)操作,當(dāng)然并非簡(jiǎn)單地死循環(huán),無(wú)消息時(shí)會(huì)休眠。但這里可能又引發(fā)了另一個(gè)問(wèn)題,既然是死循環(huán)又如何去處理其他事務(wù)呢?通過(guò)創(chuàng)建新線程的方式。真正會(huì)卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume等操作時(shí)間過(guò)長(zhǎng),會(huì)導(dǎo)致掉幀,甚至發(fā)生ANR,looper.loop本身不會(huì)導(dǎo)致應(yīng)用卡死。

可以看到Looper.prepare()方法在這里調(diào)用,所以在主線程中可以直接初始化Handler了。

//ActivityThread類(lèi)中的main方法中重點(diǎn)代碼
//注意:這里省略了許多代碼
public static void main(String[] args) {
    ……
    //創(chuàng)建Looper和MessageQueue對(duì)象,用于處理主線程的消息
    Looper.prepareMainLooper();
    //創(chuàng)建ActivityThread對(duì)象
    ActivityThread thread = new ActivityThread();
    //建立Binder通道 (創(chuàng)建新線程)
    thread.attach(false);
    ……
    //消息循環(huán)運(yùn)行
    Looper.loop();
    //如果能執(zhí)行下面方法,說(shuō)明應(yīng)用崩潰或者是退出了...
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

并且可以看到還調(diào)用了:Looper.loop()方法,可以知道一個(gè)Handler的標(biāo)準(zhǔn)寫(xiě)法其實(shí)是這樣的

Looper.prepare();
Handler mHandler = new Handler() {
   @Override
   public void handleMessage(Message msg) {
      if (msg.what == 101) {
         Log.i(TAG, "在子線程中定義Handler,并接收到消息");
       }
   }
};
Looper.loop();

6.0.0.6 Looper.prepare()能否調(diào)用多次?調(diào)用多次會(huì)出現(xiàn)什么情況?Looper中用什么存儲(chǔ)消息?

思考:Looper.prepare()能否調(diào)用兩次或者多次

如果運(yùn)行,則會(huì)報(bào)錯(cuò),并提示prepare中的Excetion信息。由此可以得出在每個(gè)線程中Looper.prepare()能且只能調(diào)用一次

//這里L(fēng)ooper.prepare()方法調(diào)用了兩次
Looper.prepare();
Looper.prepare();
Handler mHandler = new Handler() {
   @Override
   public void handleMessage(Message msg) {
       if (msg.what == 1) {
          Log.i(TAG, "在子線程中定義Handler,并接收到消息。。。");
       }
   }
};
Looper.loop();

Looper中用什么存儲(chǔ)消息

先看一下下面得源代碼

可以看到Looper中有一個(gè)ThreadLocal成員變量,熟悉JDK的同學(xué)應(yīng)該知道,當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。

看Looper對(duì)象的構(gòu)造方法,可以看到在其構(gòu)造方法中初始化了一個(gè)MessageQueue對(duì)象。MessageQueue也稱(chēng)之為消息隊(duì)列,特點(diǎn)是先進(jìn)先出,底層實(shí)現(xiàn)是單鏈表數(shù)據(jù)結(jié)構(gòu)。

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

6.0.0.6 為什么系統(tǒng)不建議在子線程訪問(wèn)UI,不對(duì)UI控件的訪問(wèn)加上鎖機(jī)制的原因?

出自《Android藝術(shù)探索》

這是因?yàn)锳ndroid的UI控件不是線程安全的,如果在多線程中并發(fā)訪問(wèn)可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài),那么為什么系統(tǒng)不對(duì)UI控件的訪問(wèn)加上鎖機(jī)制呢?缺點(diǎn)有兩個(gè):

①首先加上鎖機(jī)制會(huì)讓UI訪問(wèn)的邏輯變得復(fù)雜

②鎖機(jī)制會(huì)降低UI訪問(wèn)的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。

所以最簡(jiǎn)單且高效的方法就是采用單線程模型來(lái)處理UI操作。

為什么說(shuō)子線程不能更新UI?

子線程是不能直接更新UI的。Android實(shí)現(xiàn)View更新有兩組方法,分別是invalidate和postInvalidate。前者在UI線程中使用,后者在非UI線程即子線程中使用。換句話(huà)說(shuō),在子線程調(diào)用 invalidate 方法會(huì)導(dǎo)致線程不安全。熟悉View工作原理的人都知道,invalidate 方法會(huì)通知 view 立即重繪,刷新界面。作一個(gè)假設(shè),現(xiàn)在用 invalidate 在子線程中刷新界面,同時(shí)UI線程也在用 invalidate 刷新界面,這樣會(huì)不會(huì)導(dǎo)致界面的刷新不能同步?這就是invalidate不能在子線程中使用的原因。博客

6.0.0.7 Looper.loop是一個(gè)死循環(huán),拿不到需要處理的Message就會(huì)阻塞,那在UI線程中為什么不會(huì)導(dǎo)致ANR?

問(wèn)題描述

在處理消息的時(shí)候使用了Looper.loop()方法,并且在該方法中進(jìn)入了一個(gè)死循環(huán),同時(shí)Looper.loop()方法是在主線程中調(diào)用的,那么為什么沒(méi)有造成阻塞呢?

ActivityThread中main方法

ActivityThread類(lèi)的注釋上可以知道這個(gè)類(lèi)管理著我們平常所說(shuō)的主線程(UI線程)

首先 ActivityThread 并不是一個(gè) Thread,就只是一個(gè) final 類(lèi)而已。我們常說(shuō)的主線程就是從這個(gè)類(lèi)的 main 方法開(kāi)始,main 方法很簡(jiǎn)短

public static final void main(String[] args) {
    ...
    //創(chuàng)建Looper和MessageQueue
    Looper.prepareMainLooper();
    ...
    //輪詢(xún)器開(kāi)始輪詢(xún)
    Looper.loop();
    ...
}

Looper.loop()方法無(wú)限循環(huán)

看看Looper.loop()方法無(wú)限循環(huán)部分的代碼

while (true) {
   //取出消息隊(duì)列的消息,可能會(huì)阻塞
   Message msg = queue.next(); // might block
   ...
   //解析消息,分發(fā)消息
   msg.target.dispatchMessage(msg);
   ...
}

為什么這個(gè)死循環(huán)不會(huì)造成ANR異常呢?

因?yàn)锳ndroid 的是由事件驅(qū)動(dòng)的,looper.loop() 不斷地接收事件、處理事件,每一個(gè)點(diǎn)擊觸摸或者說(shuō)Activity的生命周期都是運(yùn)行在 Looper.loop() 的控制之下,如果它停止了,應(yīng)用也就停止了。只能是某一個(gè)消息或者說(shuō)對(duì)消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。技術(shù)博客大總結(jié)

處理消息handleMessage方法

如下所示

可以看見(jiàn)Activity的生命周期都是依靠主線程的Looper.loop,當(dāng)收到不同Message時(shí)則采用相應(yīng)措施。

如果某個(gè)消息處理時(shí)間過(guò)長(zhǎng),比如你在onCreate(),onResume()里面處理耗時(shí)操作,那么下一次的消息比如用戶(hù)的點(diǎn)擊事件不能處理了,整個(gè)循環(huán)就會(huì)產(chǎn)生卡頓,時(shí)間一長(zhǎng)就成了ANR。

public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
            r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        break;
        case RELAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
            ActivityClientRecord r = (ActivityClientRecord) msg.obj;
            handleRelaunchActivity(r);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        break;
        case PAUSE_ACTIVITY:
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
            handlePauseActivity((IBinder) msg.obj, false, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 2) != 0);
            maybeSnapshot();
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;
        case PAUSE_ACTIVITY_FINISHING:
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
            handlePauseActivity((IBinder) msg.obj, true, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 1) != 0);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;
        ...........
    }
}

loop的循環(huán)消耗性能嗎?

主線程Looper從消息隊(duì)列讀取消息,當(dāng)讀完所有消息時(shí),主線程阻塞。子線程往消息隊(duì)列發(fā)送消息,并且往管道文件寫(xiě)數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù),主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢,再次睡眠。因此loop的循環(huán)并不會(huì)對(duì)CPU性能有過(guò)多的消耗。

簡(jiǎn)單的來(lái)說(shuō):ActivityThread的main方法主要就是做消息循環(huán),一旦退出消息循環(huán),那么你的程序也就可以退出了。

6.0.0.9 Message可以如何創(chuàng)建?哪種效果更好,為什么?runOnUiThread如何實(shí)現(xiàn)子線程更新UI?

創(chuàng)建Message對(duì)象的幾種方式:技術(shù)博客大總結(jié)

Message msg = new Message();

Message msg = Message.obtain();

Message msg = handler1.obtainMessage();

后兩種方法都是從整個(gè)Messge池中返回一個(gè)新的Message實(shí)例,能有效避免重復(fù)Message創(chuàng)建對(duì)象,因此更鼓勵(lì)這種方式創(chuàng)建Message

runOnUiThread如何實(shí)現(xiàn)子線程更新UI

看看源碼,如下所示

如果msg.callback為空的話(huà),會(huì)直接調(diào)用我們的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler對(duì)象是在主線程中創(chuàng)建的,所以handler的handlerMessage方法的執(zhí)行也會(huì)在主線程中。

在runOnUiThread程序首先會(huì)判斷當(dāng)前線程是否是UI線程,如果是就直接運(yùn)行,如果不是則post,這時(shí)其實(shí)質(zhì)還是使用的Handler機(jī)制來(lái)處理線程與UI通訊。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

@Override
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

6.0.1.0 MessageQueue作用是干什么的?MessageQueue的定義是什么?MessageQueue主要工作原理是怎樣的?

MessageQueue作用是干什么的

MessageQueue,主要包含2個(gè)操作:插入和讀取。

讀取操作會(huì)伴隨著刪除操作,插入和讀取對(duì)應(yīng)的方法分別為enqueueMessage和next,其中enqueueMessage的作用是往消息隊(duì)列中插入一條消息,而next的作用是從消息隊(duì)列中取出一條消息并將其從消息隊(duì)列中移除。

雖然MessageQueue叫消息隊(duì)列,但是它的內(nèi)部實(shí)現(xiàn)并不是用的隊(duì)列。

實(shí)際上它是通過(guò)一個(gè)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表,單鏈表在插入和刪除上比較有優(yōu)勢(shì)。

MessageQueue的定義是什么

通過(guò)源碼我們可以知道,MessageQueue維護(hù)了一個(gè)消息列表。Messgae并不是直接添加到MessageQueue中,而是通過(guò)和Looper相關(guān)聯(lián)的Handler來(lái)添加的。在當(dāng)前線程中可以通過(guò)調(diào)用Looper.myQueue()方法來(lái)獲取當(dāng)前線程的MessageQueue。博客

/**
 * Low-level class holding the list of messages to be dispatched by a
 * {@link Looper}.  Messages are not added directly to a MessageQueue,
 * but rather through {@link Handler} objects associated with the Looper.
 * 
 * 

You can retrieve the MessageQueue for the current thread with * {@link Looper#myQueue() Looper.myQueue()}. */ public final class MessageQueue

MessageQueue主要工作原理是怎樣的?

源碼如下所示

在Message的源碼中定義了一個(gè)成員屬性target,其類(lèi)型為Handler。由上面enqueuMessage的源碼,我們可以看到,當(dāng)Message沒(méi)有處理其的Handler或該Message正在被處理的時(shí)候,都不能正常進(jìn)入MessageQueue,這一點(diǎn)也是很容易理解的。當(dāng)線程處于死亡狀態(tài)的時(shí)候,Message會(huì)被回收掉,而不再進(jìn)入該線程對(duì)應(yīng)的MessageQueue中。否則,一切正常,enqueMessage就執(zhí)行單鏈表的插入操作,將Message插入到MessageQueue中。

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we dont have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

next()方法源碼分析

在 MessageQueue 中消息的讀取其實(shí)是通過(guò)內(nèi)部的 next() 方法進(jìn)行的,next() 方法是一個(gè)無(wú)限循環(huán)的方法。博客

如果消息隊(duì)列中沒(méi)有消息,則該方法會(huì)一直阻塞,

當(dāng)有新消息來(lái)的時(shí)候 next() 方法會(huì)返回這條消息并將其從單鏈表中刪除。

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

6.0.1.1 子線程更新UI有哪些方式?runOnUiThread如何實(shí)現(xiàn)子線程更新UI?View.post(Runnable r)更新UI?

子線程更新UI有哪些方式

主線程中定義Handler,子線程通過(guò)mHandler發(fā)送消息,主線程Handler的handleMessage更新UI

用Activity對(duì)象的runOnUiThread方法

創(chuàng)建Handler,傳入getMainLooper

View.post(Runnable r)

runOnUiThread如何實(shí)現(xiàn)子線程更新UI

如何使用代碼如下所示

new Thread(new Runnable() {
    @Override
    public void run() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                tv_0.setText("滾犢子++++");
            }
        });
    }
}).start();

看看源碼,如下所示

如果msg.callback為空的話(huà),會(huì)直接調(diào)用我們的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler對(duì)象是在主線程中創(chuàng)建的,所以handler的handlerMessage方法的執(zhí)行也會(huì)在主線程中。

在runOnUiThread程序首先會(huì)判斷當(dāng)前線程是否是UI線程,如果是就直接運(yùn)行,如果不是則post,這時(shí)其實(shí)質(zhì)還是使用的Handler機(jī)制來(lái)處理線程與UI通訊。博客

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

@Override
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

View.post(Runnable r)更新UI

代碼如下所示

tv_0.post(new Runnable() {
    @Override
    public void run() {
        tv_0.setText("滾犢子");
    }
});

源碼原理如下所示

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

private HandlerActionQueue getRunQueue() {
    if (mRunQueue == null) {
        mRunQueue = new HandlerActionQueue();
    }
    return mRunQueue;
}

View.post(Runnable r)使用注意事項(xiàng)

看源碼的注釋可知:如果view已經(jīng)attached,則調(diào)用ViewRootImpl中的ViewRootHandler,放入主線程Lopper等待執(zhí)行。如果detach,則將其暫存在RunQueue當(dāng)中,等待其它線程取出執(zhí)行。

View.post(Runnable r)很多時(shí)候在子線程調(diào)用,用于進(jìn)行子線程無(wú)法完成的操作,或者在該方法中通過(guò)getMeasuredWidth()獲取view的寬高。需要注意的是,在子線程調(diào)用該函數(shù),可能不會(huì)被執(zhí)行,原因是該view不是attached狀態(tài)。博客

子線程更新UI總結(jié)概括

handler.post(Runnable r)、 view.post(Runnable r)、activity.runOnUIThread(Runnable r)等方法。跟進(jìn)去看源碼,發(fā)現(xiàn)其實(shí)它們的實(shí)現(xiàn)原理都還是一樣,最終都是通過(guò)Handler發(fā)送消息來(lái)實(shí)現(xiàn)的。

6.0.1.3 使用Hanlder的postDealy()后消息隊(duì)列會(huì)發(fā)生什么變化?

post delay的Message并不是先等待一定時(shí)間再放入到MessageQueue中,而是直接進(jìn)入并阻塞當(dāng)前線程,然后將其delay的時(shí)間和隊(duì)頭的進(jìn)行比較,按照觸發(fā)時(shí)間進(jìn)行排序,如果觸發(fā)時(shí)間更近則放入隊(duì)頭,保證隊(duì)頭的時(shí)間最小、隊(duì)尾的時(shí)間最大。此時(shí),如果隊(duì)頭的Message正是被delay的,則將當(dāng)前線程堵塞一段時(shí)間,直到等待足夠時(shí)間再喚醒執(zhí)行該Message,否則喚醒后直接執(zhí)行。

6.0.1.4 ThreadLocal有什么作用?如何避免UI線程盡量只做跟UI相關(guān)的工作);

線程本地存儲(chǔ)的功能

ThreadLocal類(lèi)可實(shí)現(xiàn)線程本地存儲(chǔ)的功能,把共享數(shù)據(jù)的可見(jiàn)范圍限制在同一個(gè)線程之內(nèi),無(wú)須同步就能保證線程之間不出現(xiàn)數(shù)據(jù)爭(zhēng)用的問(wèn)題,這里可理解為T(mén)hreadLocal幫助Handler找到本線程的Looper。

技術(shù)博客大總結(jié)

怎么存儲(chǔ)呢?底層數(shù)據(jù)結(jié)構(gòu)是啥?

每個(gè)線程的Thread對(duì)象中都有一個(gè)ThreadLocalMap對(duì)象,它存儲(chǔ)了一組以ThreadLocal.threadLocalHashCode為key、以本地線程變量為value的鍵值對(duì),而ThreadLocal對(duì)象就是當(dāng)前線程的ThreadLocalMap的訪問(wèn)入口,也就包含了一個(gè)獨(dú)一無(wú)二的threadLocalHashCode值,通過(guò)這個(gè)值就可以在線程鍵值值對(duì)中找回對(duì)應(yīng)的本地線程變量。

如何避免UI線程盡量只做跟UI相關(guān)的工作);

耗時(shí)的操作(比如數(shù)據(jù)庫(kù)操作,I/O,連接網(wǎng)絡(luò)或者別的有可能阻塞UI線程的操作)把它放在多帶帶的線程處理盡量用Handler來(lái)處理UIthread和別的thread之間的交互.使用Thread或者HandlerThread時(shí),調(diào)用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)設(shè)置優(yōu)先級(jí),否則仍然會(huì)降低程序響應(yīng),因?yàn)槟J(rèn)Thread的優(yōu)先級(jí)和主線程相同。使用Handler處理工作線程結(jié)果,而不是使用Thread.wait()或者Thread.sleep()來(lái)阻塞主線程。

6.0.1.5 為什么一個(gè)線程只有一個(gè)Looper、只有一個(gè)MessageQueue,可以有多個(gè)Handler?

注意:一個(gè)Thread只能有一個(gè)Looper,可以有多個(gè)Handler

Looper有一個(gè)MessageQueue,可以處理來(lái)自多個(gè)Handler的Message;MessageQueue有一組待處理的Message,這些Message可來(lái)自不同的Handler;Message中記錄了負(fù)責(zé)發(fā)送和處理消息的Handler;Handler中有Looper和MessageQueue。

為什么一個(gè)線程只有一個(gè)Looper?技術(shù)博客大總結(jié)

需使用Looper的prepare方法,Looper.prepare()??梢钥聪略创a,Android中一個(gè)線程最多僅僅能有一個(gè)Looper,若在已有Looper的線程中調(diào)用Looper.prepare()會(huì)拋出RuntimeException(“Only one Looper may be created per thread”)。

所以一個(gè)線程只有一個(gè)Looper,不知道這樣解釋是否合理!更多可以查看我的博客匯總:github.com/yangchong21…

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

關(guān)于其他內(nèi)容介紹

01.關(guān)于博客匯總鏈接

1.技術(shù)博客匯總

2.開(kāi)源項(xiàng)目匯總

3.生活博客匯總

4.喜馬拉雅音頻匯總

5.其他匯總

02.關(guān)于我的博客

我的個(gè)人站點(diǎn):www.yczbj.org, www.ycbjie.cn

github:github.com/yangchong21…

知乎:www.zhihu.com/people/yczb…

簡(jiǎn)書(shū):www.jianshu.com/u/b7b2c6ed9…

csdn:my.csdn.net/m0_37700275

喜馬拉雅聽(tīng)書(shū):www.ximalaya.com/zhubo/71989…

開(kāi)源中國(guó):my.oschina.net/zbj1618/blo…

泡在網(wǎng)上的日子:www.jcodecraeer.com/member/cont…

郵箱:[email protected]

阿里云博客:yq.aliyun.com/users/artic… 239.headeruserinfo.3.dT4bcV

segmentfault頭條:segmentfault.com/u/xiangjian…

掘金:juejin.im/user/593943…

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7059.html

相關(guān)文章

  • 06.Android消息機(jī)制問(wèn)題

    摘要:通過(guò)向消息池發(fā)送各種消息事件通過(guò)處理相應(yīng)的消息事件。子線程往消息隊(duì)列發(fā)送消息,并且往管道文件寫(xiě)數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù),主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢,再次睡眠。 目錄介紹 6.0.0.1 談?wù)勏C(jī)制Hander作用?有哪些要素?流程是怎樣的? 6.0.0.2 為什么一個(gè)線程只有一個(gè)Looper、只有一個(gè)MessageQueue,可以有多個(gè)Handle...

    Aomine 評(píng)論0 收藏0
  • Android四大組件BroadcastReceiver

    摘要:作為的四大組件之二,其應(yīng)用場(chǎng)景非常多。作用可以監(jiān)聽(tīng)或接收應(yīng)用或系統(tǒng)發(fā)出的廣播消息,并做出響應(yīng)??梢灾付í?dú)立的進(jìn)程四大組件都可以通過(guò)此屬性指定自己的獨(dú)立進(jìn)程。對(duì)于應(yīng)用內(nèi)廣播的動(dòng)態(tài)注冊(cè)方式,回調(diào)中的返回值是。 前言 Hi,大家好,又雙見(jiàn)面啦,上一期我們講了如何使用Activity,肯定有不少小伙伴已經(jīng)創(chuàng)建了屬于自己的FirstActivity,那么這一期我們主要為大家介紹第二個(gè)重要組件Br...

    plus2047 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<