Android 中 Handler 的基本使用

一均芽、前言:

Android中的消息機(jī)制是通過(guò)Handler來(lái)實(shí)現(xiàn)的党巾。隨著EventBus和RxJava等依托觀察者模式的消息傳遞機(jī)制的出現(xiàn)瘪吏,當(dāng)前在Android開(kāi)發(fā)中Handler的使用已經(jīng)不如之前那么重要蔑祟,但是Android系統(tǒng)所提供的Handler中的各種編程思路和設(shè)計(jì)方案晌区,對(duì)我們?cè)诰幊趟枷氲奶嵘€是有很大益處的寞埠。

gitHub 地址:https://github.com/lyyRunning/ThreadDemo0629.git

1.Handler是什么?

Handler是Android給我們提供用于更新UI的一套機(jī)制屁置,也是一套消息處理機(jī)制。我們用它可以發(fā)送消息仁连,也可以用它處理消息蓝角。在Android開(kāi)發(fā)中有著非常重要的地位。

2.為什么要使用Handler?

當(dāng)一個(gè)應(yīng)用程序運(yùn)行時(shí)饭冬,它會(huì)創(chuàng)建一個(gè)進(jìn)程使鹅。這個(gè)進(jìn)程就是我們的主線程(UI線程&Activity Thread) 。在主線程中昌抠,會(huì)默認(rèn)為我們?cè)谙到y(tǒng)中默認(rèn)創(chuàng)建一個(gè)Looper患朱,這個(gè)Looper會(huì)與我們的Message Queue 和 主線程有一定聯(lián)系。 在main線程中炊苫,主要是運(yùn)行一個(gè)Message Queue裁厅,管理著頂級(jí)的應(yīng)用程序(Activity,Boardcast Receiver…)這些頂級(jí)應(yīng)用程序在默認(rèn)情況下都會(huì)在主線程中創(chuàng)建劝评。這就是為什么我們需要在主線程中更新UI姐直。

Android在設(shè)計(jì)的過(guò)程中,就封裝了一套消息創(chuàng)建蒋畜、傳遞、處理的機(jī)制撞叽。如果不遵循這樣的機(jī)制姻成,是沒(méi)有辦法更新UI信息的插龄,會(huì)拋出異常信息。

非主線程更新UI的后果

我們可以嘗試在一個(gè)新的線程中更新UI科展,會(huì)發(fā)現(xiàn)程序崩潰了均牢。查看Logcat可以看到這樣的一句提示

Only the original thread that created a view hierarchy can touch its views.

這告訴我們,在實(shí)際開(kāi)發(fā)中才睹,我們需要遵循Google為我們?cè)O(shè)定的這樣的機(jī)制徘跪。

那么如何在其他線程達(dá)到更新UI的目的呢?使用Handler就是其中一種辦法琅攘。

3.Handler的作用

根據(jù)Android Developer網(wǎng)站上的描述垮庐,Handler主要有兩個(gè)用途

  • 定時(shí)地去發(fā)送一個(gè)Message或Runnable對(duì)象
  • 可以跳轉(zhuǎn)到另一個(gè)線程中去執(zhí)行一些操作

4.Handler的使用方式

關(guān)于Message

參數(shù)

  • public int arg1(arg2):如果只需要存儲(chǔ)幾個(gè)整型數(shù)據(jù),arg1 和 arg2是setData()的低成本替代品坞琴。

  • public Object obj: 發(fā)送給接收者的任意對(duì)象哨查。當(dāng)使用Message對(duì)象在線程間傳遞消息時(shí),如果它包含一個(gè)Parcelable的結(jié)構(gòu)類(不是由應(yīng)用程序?qū)崿F(xiàn)的類)剧辐,此字段必須為非空(non-null)寒亥。其他的數(shù)據(jù)傳輸則使用setData(Bundle)方法。

  • public Messenger replyTo:用戶自定義的消息代碼荧关,這樣接受者可以了解這個(gè)消息的信息溉奕。每個(gè)handler各自包含自己的消息代碼,所以不用擔(dān)心自定義的消息跟其他handlers有沖突忍啤。

方法

另外 腐宋,用Message來(lái)傳遞還可通過(guò)Message的setData(Bundle)方法來(lái)傳遞。獲取時(shí)調(diào)用getData()方法檀轨。與sendData相似的還有peekData胸竞。

  • public void setData(*Bundle data):設(shè)置一個(gè)任意數(shù)據(jù)值的Bundle對(duì)象。如果可以参萄,使用arg1和arg2域發(fā)送一些整型值以減少消耗卫枝。

  • public Bundle peekData():與getData()相似,但是并不延遲創(chuàng)建Bundle讹挎。如果Bundle對(duì)象不存在返回null校赤。

  • public Bundle getData():獲取附加在此事件上的任意數(shù)據(jù)的Bundle對(duì)象,需要時(shí)延遲創(chuàng)建筒溃。通過(guò)調(diào)用setData(Bundle)來(lái)設(shè)置Bundle的值马篮。需要注意的是,如果通過(guò)Messenger對(duì)象在進(jìn)程間傳遞數(shù)據(jù)時(shí)怜奖,需要調(diào)用Bundle類的Bundle.setClassLoader()方法來(lái)設(shè)置ClassLoader浑测,這樣當(dāng)接收到消息時(shí)可以實(shí)例化Bundle里的對(duì)象。

  • public static Message obtain(): 從全局池中返回一個(gè)新的Message實(shí)例。在大多數(shù)情況下這樣可以避免分配新的對(duì)象迁央。

5.handleMessage方法

handleMessage方法用于接收Message對(duì)象并進(jìn)行相應(yīng)的處理掷匠,對(duì)應(yīng)Handler的sendMessage方法。

使用時(shí)應(yīng)在handler中重寫(xiě)此方法岖圈。當(dāng)在其他線程調(diào)用sendMessage方法時(shí)讹语,handleMessage方法便會(huì)被回調(diào),并攜帶sendMessage方法調(diào)用者在Message中存入的信息蜂科。當(dāng)我們想要在其他線程更新UI時(shí)顽决,就可以用主線程中創(chuàng)建的Handler調(diào)用sendMessage方法,然后在該Handler重寫(xiě)的handleMessage方法中做相應(yīng)的處理导匣。

比如此處才菠,我們?cè)趆andleMessage方法中進(jìn)行更新TextView的操作,并把Message的arg1作為文本的內(nèi)容逐抑。

private Handler mHandler = new Handler(){
    public void handleMessage(Message msg){
        mTextView.setText(""+msg.arg1+"-"+msg.arg2);
    };
};

new Thread(){
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
         Message message = Message.obtain();
            message.arg1 = 88;
            mHandler.sendMessage(message);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}.start();
6.post方法

剛剛的異常我們已經(jīng)看到鸠儿,那如何才能使用Handler在這個(gè)新建的線程更新UI呢?

我們可以這樣做:在主線程中創(chuàng)建一個(gè)Handler厕氨。然后在子線程中辈讶,我們可以調(diào)用Handler的post方法杀赢,并向其中傳遞一個(gè)Runnable為參數(shù)虫腋,在Runnable中更新UI即可竭业。

private TextView mTextView;
private Handler mHandler = new Handler();

mTextView = findViewById(R.id.tv_text);
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mTextView.setText("update");
                }
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();
7.postDelayed方法

postDelayed與post方法非常接近,僅僅是參數(shù)多了一個(gè)long類型的參數(shù)delayMills国葬。它與post的區(qū)別就是它會(huì)在delayMills這段時(shí)間之后再去執(zhí)行Runnable的方法贤徒,也就是延遲執(zhí)行。

有時(shí)候我們需要定時(shí)的完成一些事情(比如定時(shí)更換TextView的文字)時(shí)汇四,就可以利用它延遲執(zhí)行的這一特點(diǎn)來(lái)實(shí)現(xiàn)接奈。做法是分別在主函數(shù)中以及它所執(zhí)行的Runnable中postDelayed一段時(shí)間。這樣就可以達(dá)到定時(shí)地完成某件任務(wù)的工作通孽。

比如下例就簡(jiǎn)單地實(shí)現(xiàn)了每隔一秒切換一次TextView文字的作用序宦。

8.removeCallbacks方法

比如我們這里有個(gè)定時(shí)更新TextView的文本的代碼,如果想要按下按鈕背苦,停止定時(shí)更換文本互捌,就可以通過(guò)removeCallbacks方法,傳入該Runnable來(lái)中止消息行剂。

二秕噪、Handler運(yùn)行機(jī)制

Handler的消息機(jī)制如下圖所示,主要包含兩個(gè)消息隊(duì)列厚宰,一個(gè)是消息的回收隊(duì)列腌巾,另一個(gè)是Message Q隊(duì)列。

1. 消息的回收隊(duì)列:消息回收隊(duì)列是為Handler提供消息對(duì)象的,當(dāng)Handler需要發(fā)送消息時(shí)壤躲,首先從消息回收隊(duì)列中獲取已被清空數(shù)據(jù)的消息對(duì)象城菊,若消息對(duì)隊(duì)列中此時(shí)沒(méi)有消息對(duì)象备燃,則創(chuàng)建新的消息對(duì)象碉克。當(dāng)消息對(duì)象被使用后,不會(huì)直接被當(dāng)做垃圾回收并齐,而是會(huì)進(jìn)入消息的回收隊(duì)列漏麦,在該隊(duì)列中會(huì)將消息對(duì)象上的所有數(shù)據(jù)清空,之后在隊(duì)列中等待被使用况褪。

2. 獲取消息 :直接通過(guò)以下兩種方式中的任一種獲取消息撕贞。

 Message message = Message.obtain();
  message.what = MAIN_UI;
  message.obj = "main發(fā)送消息的線程名稱" 

3. 創(chuàng)建Handler對(duì)象: 直接通過(guò)如下方式創(chuàng)建Handler對(duì)象即可。該Handler默認(rèn)使用Android提供的Looper测垛。

 Handler handler= new Handler();

4. 發(fā)送消息:通過(guò)如下等方式進(jìn)行消息的發(fā)送捏膨。

handlerUI.sendEmptyMessage(11);//立刻發(fā)送空的消息
handlerUI.sendMessage(message);//立刻發(fā)送攜帶數(shù)據(jù)的消息
handlerUI.sendMessageDelayed(message,1000);//延遲1000ms后發(fā)送攜帶數(shù)據(jù)的消息

5. Message Q隊(duì)列:消息隊(duì)列,用來(lái)管理消息食侮,會(huì)根據(jù)消息的執(zhí)行時(shí)間對(duì)消息進(jìn)行排序号涯。并通過(guò)Looper.loop對(duì)消息進(jìn)行輪詢?nèi)〕觯?dāng)隊(duì)列中有消息時(shí)就將消息取出交給Handler的handlerMessage()回調(diào)進(jìn)行消息的處理锯七,當(dāng)隊(duì)列中沒(méi)有消息時(shí)就進(jìn)行阻塞等待链快,這種機(jī)制又被稱作等待喚醒機(jī)制。Android中的等待喚醒機(jī)制是使用的Linux內(nèi)核的管道流機(jī)制眉尸。

6. 消息的處理:通過(guò)Looper.loop()取出消息隊(duì)列中待處理的消息域蜗,通過(guò)handler的handlerMessage()回調(diào)進(jìn)行消息的處理。處理完成的消息對(duì)象會(huì)進(jìn)入消息的回收隊(duì)列進(jìn)行回收噪猾。

image.png

原理解析:
Handler 創(chuàng)建完畢后霉祸,這個(gè)時(shí)候內(nèi)部的 Looper 以及 MessageQueue 就可以和 Handler 一起協(xié)調(diào)工作了,然后通過(guò) Handler 的 post 方法將一個(gè) Runnable 投遞到 Handler 內(nèi)部的 Looper 中去處理袱蜡,也可以通過(guò) Handler 的 send 方法發(fā)送一個(gè)消息,這個(gè)消息同樣會(huì)在 Looper 中去處理迅细。其實(shí) post 方法最終也是通過(guò) send 方法發(fā)送消息的。

接下來(lái)看一下 send 的工作過(guò)程:當(dāng) Handler 的 send 方法被調(diào)用時(shí)帆离,它會(huì)調(diào)用 MessageQueueMessage 方法將這個(gè)消息放入消息隊(duì)列中岸夯,然后 Looper 發(fā)現(xiàn)有新消息到來(lái)時(shí),就會(huì)處理這個(gè)消息,最終消息中的 Runnable 或者是 Handler 的 handlerMessage 方法會(huì)被調(diào)用刮刑。注意 Looper 是運(yùn)行在創(chuàng)建 Handler 所在的線程中的蔽氨,這樣一來(lái) Handler 中的業(yè)務(wù)邏輯就被切換到創(chuàng)建 Handler 所在的線程中去執(zhí)行了。

三柳琢、使用Handler向主線程發(fā)送消息

通過(guò)handler向主線程發(fā)送消息是Handler使用過(guò)程中最常規(guī)的一種方式绍妨,也是最普遍的一種使用方式润脸。當(dāng)創(chuàng)建Handler對(duì)象時(shí),若使用空參構(gòu)造創(chuàng)建他去,則創(chuàng)建出的Handler默認(rèn)使用UI線程中的Looper毙驯,這個(gè)時(shí)候通過(guò)Looper.loop()取出的消息在handlerMessage()中進(jìn)行處理時(shí),是在UI線程中進(jìn)行處理的灾测。這也就是為什么大家常說(shuō):Handler是運(yùn)行在主線程中的爆价。

這又分成兩種情況:

1. 主線程->主線程 發(fā)送消息:在主線程中通過(guò)創(chuàng)建好的handler調(diào)用sendMessage()方法發(fā)送消息即可。

2. 子線程->主線程 發(fā)送消息:在子線程中通過(guò)創(chuàng)建好的handler調(diào)用sendMessage()方法發(fā)送消息即可行施。

本次以主線程和子線程向主線程發(fā)送消息為例:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.btn1)
    Button btn1;
    @BindView(R.id.btn2)
    Button btn2;
    @BindView(R.id.tv_message)
    TextView tvMessage;


    private static final int HANDLER_UI = 1;
    private static final int MAIN_UI = 2;


    /**
     * 運(yùn)行在主線程的Handler:使用Android默認(rèn)的UI線程中的Looper
     */
    public Handler handlerUI = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case HANDLER_UI:
                    String strData = (String) msg.obj;
                    tvMessage.setText(strData);
                    break;
                case MAIN_UI:
                    String strData2 = (String) msg.obj;
                    tvMessage.setText(strData2);
                    break;
                default:
                    break;
            }
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        tvMessage.setText("主線程發(fā)送 Handler");

    }

    @OnClick({R.id.btn1, R.id.btn2, R.id.btn3})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn1:
                //主線程發(fā)送
                Message message = Message.obtain();
                message.what = MAIN_UI;
                message.obj = "main發(fā)送消息的線程名稱:" + Thread.currentThread().getName();
                handlerUI.sendMessage(message);

                break;
            case R.id.btn2:
                //子線程發(fā)送
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = Message.obtain();
                        message.what = HANDLER_UI;
                        message.obj = "子線程發(fā)送消息的線程名稱:" + Thread.currentThread().getName();
                        handlerUI.sendMessage(message);

                    }
                }).start();
                break;

            case R.id.btn3:
                ThreadActivity.launch(MainActivity.this);
                break;
            default:
        }
    }
}

上述代碼有一定的隱患允坚,因?yàn)槿绱耸褂肏andler會(huì)導(dǎo)致內(nèi)存泄露魂那,此處是為了舉例簡(jiǎn)潔才如此使用蛾号,正常使用Handler時(shí)要避免這種方式,解決這種導(dǎo)致內(nèi)存泄露可通過(guò)——WeakReference(弱引用)涯雅,后面會(huì)詳細(xì)介紹鲜结。

四、使用Handler向子線程發(fā)送消息

上述說(shuō)的是Handler常規(guī)的使用方式活逆,但有時(shí)因?yàn)闃I(yè)務(wù)需求精刷,需要向子線程發(fā)送消息。這時(shí)候就需要尋找突破點(diǎn)了蔗候,仔細(xì)觀察其實(shí)很容易發(fā)現(xiàn)怒允,消息機(jī)制最核心的點(diǎn)就是消息的Looper機(jī)制,UI線程之所以能夠進(jìn)行消息的實(shí)現(xiàn)是因?yàn)锳ndroid默認(rèn)提供了一個(gè)Looper锈遥,含有Looper的線程被稱為UI線程纫事,UI線程和子線程之間唯一的區(qū)別就是一個(gè)有Looper另外的一個(gè)沒(méi)有。既然突破點(diǎn)已經(jīng)找到所灸,那直接創(chuàng)建一個(gè)含有Looper的子線程丽惶,即可實(shí)現(xiàn)向該子線程發(fā)送消息。那如何創(chuàng)建一個(gè)含有Looer的子線程能爬立?需要兩步:

  1. 在創(chuàng)建的子線程的run()方法中進(jìn)行Looper相關(guān)的配置钾唬。

  2. 在創(chuàng)建Hander時(shí)的構(gòu)造方法中傳入1線程中的Looper。

代碼實(shí)現(xiàn)如下:

public class ThreadActivity extends AppCompatActivity {

    @BindView(R.id.tv_message)
    TextView tvMessage;
    @BindView(R.id.btn1)
    Button btn1;
    @BindView(R.id.btn2)
    Button btn2;
    private Handler threadHandler;
    private static final int HANDLER_THREAD = 3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tvMessage.setText("子線程處理Handler侠驯,原始用法");
        createLooperHandler();
    }

    @OnClick({R.id.btn1, R.id.btn2, R.id.btn3})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn1:
                break;
            case R.id.btn2:
                //子線程發(fā)送
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = Message.obtain();
                        message.what = HANDLER_THREAD;
                        message.obj = "子線程發(fā)送消息的線程名稱:" + Thread.currentThread().getName();
                        threadHandler.sendMessage(message);

                    }
                }).start();
                break;
            case R.id.btn3:
                ThirdActivity.launch(ThreadActivity.this);
                break;

            default:
        }
    }


    /**
     * 創(chuàng)建一個(gè)可以包含looper的子線程抡秆,并開(kāi)啟
     */
    private void createLooperHandler() {
        MyThread myThread = new MyThread();
        myThread.start();

        //注釋1
        SystemClock.sleep(100);
        threadHandler = new Handler(myThread.threadLooper) {
            @Override
            public void handleMessage(final Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case HANDLER_THREAD:

                        //添加上當(dāng)前線程名稱
                        final String thrMsg = (String) msg.obj + "\n 收到的線程:" + Thread
                                .currentThread().getName();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {

                                tvMessage.setText(thrMsg);
                            }
                        });

                        break;

                    default:
                        break;
                }
            }
        };
    }

    public static void launch(Context context) {
        Intent intent = new Intent(context, ThreadActivity.class);
        context.startActivity(intent);
    }


    private class MyThread extends Thread {

        private Looper threadLooper;

        @Override
        public void run() {
            Looper.prepare();
            threadLooper = Looper.myLooper();
            Looper.loop();
        }
    }
}

通過(guò)上面的方法,確實(shí)可以實(shí)現(xiàn)向子線程發(fā)送消息吟策,但是其實(shí)還是有隱患的儒士,眼尖的人已經(jīng)看到上面81行(注釋1)處的代碼了,為什么要進(jìn)行睡眠100毫秒踊挠,其實(shí)是因?yàn)镸yThread在調(diào)用start方法后乍桂,代碼向下運(yùn)行冲杀,進(jìn)行了創(chuàng)建Handler的操作,在創(chuàng)建Handler時(shí)需要在構(gòu)造方法中傳入MyThread的Looper睹酌,而只有當(dāng)MyThread開(kāi)啟調(diào)用run()方法后才會(huì)創(chuàng)建出Looper权谁,兩者操作是在不同線程中,所以不能確定誰(shuí)會(huì)先被執(zhí)行憋沿。故旺芽,如果不進(jìn)行睡眠的話,就會(huì)概率性出現(xiàn)空指針異常辐啄。那如何才能保證既可以向子線程發(fā)送消息采章,主線程又不用出現(xiàn)睡眠等待呢?其實(shí)android也知道這種情況壶辜,所以向我們提供了一個(gè)類HandlerThread來(lái)解決上述問(wèn)題悯舟。

五、HandlerThread的使用

 HnadlerThread是Thread的子類砸民,是專門(mén)向子線程發(fā)送消息使用的抵怎。使用步驟如下:

public class ThirdActivity extends AppCompatActivity {

    @BindView(R.id.tv_message)
    TextView tvMessage;
    @BindView(R.id.btn1)
    Button btn1;
    @BindView(R.id.btn2)
    Button btn2;
    private Handler handlerThreadHandler;
    private static final int HANDLER_THREAD = 3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tvMessage.setText("子線程處理Handler,正常用法");
        createLooperHandler();
    }

    @OnClick({R.id.btn1, R.id.btn2})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn1:
                break;
            case R.id.btn2:
                //子線程發(fā)送
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = Message.obtain();
                        message.what = HANDLER_THREAD;
                        message.obj = "發(fā)送的線程:" + Thread.currentThread().getName();
                        handlerThreadHandler.sendMessage(message);

                    }
                }).start();
                break;

            default:
        }
    }


    /**
     * 創(chuàng)建一個(gè)可以包含looper的子線程岭参,并開(kāi)啟
     */
    private void createLooperHandler() {
        HandlerThread handlerThread = new HandlerThread("handler_name1");
        handlerThread.start();

        handlerThreadHandler = new Handler(handlerThread.getLooper()) {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case HANDLER_THREAD:
                        //添加上當(dāng)前線程名稱
                        final String thrMsg = (String) msg.obj + "\n 收到的線程:" + Thread
                                .currentThread().getName();


                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                tvMessage.setText(thrMsg);
                            }
                        });
                        break;

                    default:
                        break;
                }
            }
        };
    }

    public static void launch(Context context) {
        Intent intent = new Intent(context, ThirdActivity.class);
        context.startActivity(intent);
    }
}

六反惕、Handler內(nèi)存泄露

Handler使用過(guò)程中,我們需要特別注意一個(gè)問(wèn)題演侯,那就是Handler可能會(huì)導(dǎo)致內(nèi)存泄漏姿染。

具體原因如下:

  • Handler的生命周期與Activity不同,Handler會(huì)關(guān)聯(lián)Looper來(lái)管理Message Queue秒际。這個(gè)隊(duì)列在整個(gè)Application的生命周期中存在悬赏,因此Handler不會(huì)因Activity的finish()方法而被銷毀。

  • 非靜態(tài)(匿名)內(nèi)部類會(huì)持有外部對(duì)象程癌,當(dāng)我們這樣重寫(xiě)Handler時(shí)它就成為了一個(gè)匿名內(nèi)部類舷嗡,這樣如果調(diào)用finish方法時(shí)Handler有Message未處理的話,就會(huì)導(dǎo)致Activity不能被銷毀嵌莉。

解決方法:

  1. 可以在外部新建一個(gè)類进萄,這樣便可解決這個(gè)問(wèn)題。但這樣無(wú)疑過(guò)于麻煩了锐峭,內(nèi)部類更方便些中鼠。
  2. 可以同時(shí)使用靜態(tài)內(nèi)部類和弱引用,當(dāng)一個(gè)對(duì)象只被弱引用依賴時(shí)它便可以被GC回收沿癞。

注意:要static和弱引用要同時(shí)使用援雇,否則由于非靜態(tài)內(nèi)部類隱式持有了外部類Activity的引用,而導(dǎo)致Activity無(wú)法被釋放

public static class MyHandler extends Handler {
        WeakReference<Activity> mWeakReference;
 
        public MyHandler(Activity activity) {
            mWeakReference = new WeakReference<Activity>(activity);
        }
 
        @Override
        public void handleMessage(Message msg) {
            Activity activity = mWeakReference.get();
            if (activity == null) {
                return;
            }
 
            switch (msg.what) {
                case 101:
                    adapter.notifyDataSetChanged();
                    break;
 
                default:
                    break;
            }
        }
    }

七椎扬、總結(jié)

  1. Looper 中還有一個(gè)特殊概念惫搏,那就是 ThreadLocal具温,ThreadLocal并不是線程,它的作用是可以在每個(gè)線程中存儲(chǔ)數(shù)據(jù)筐赔。ThreadLocal可以在不同的線程中互不干擾地存儲(chǔ)并提供數(shù)據(jù)铣猩,通過(guò)ThreadLocal可以輕松獲取每個(gè)線程的 Looper茴丰。當(dāng)然需要注意的是,線程是默認(rèn)沒(méi)有 Looper 的贿肩,如果需要使用 Handler 就必須為線程創(chuàng)建 Looper。

  2. 主線程之所以可以接收Handler消息汰规,是因?yàn)橹骶€程在啟動(dòng)時(shí),已經(jīng)創(chuàng)建了Looper對(duì)象控轿,這也是在主線程默認(rèn)可以使用 Handler 的原因。

  3. Handler 的工作主要包含消息的發(fā)送和接受過(guò)程茬射。消息的發(fā)布主要post 的一系列方法以及 send 的一系列方法來(lái)實(shí)現(xiàn)的冒签,post 的一系列方法最終通過(guò) send 的一系列方法來(lái)實(shí)現(xiàn)的。

八萧恕、原理總結(jié):

1. ThreadLocal 的工作原理:

ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類刚梭,通過(guò)它可以在指定的線程中存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后票唆,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù)朴读,對(duì)于其他線程來(lái)說(shuō)則無(wú)法獲取到數(shù)據(jù)。
對(duì)于 Handler 來(lái)說(shuō)走趋,它需要獲取當(dāng)前線程的 Looper衅金,很顯然 Looper 的作用域就是線程,并且不同的線程具有不同的 Looper簿煌,這個(gè)時(shí)候通過(guò) ThreadLocal 就可以輕松實(shí)現(xiàn) Looper 在線程中的存取氮唯。

2. 消息隊(duì)列的工作原理:

消息隊(duì)列在 Android 中指的是 MessageQueue,MessageQueue主要包含兩個(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ì)。

3. Looper 的工作原理:

Looper在 Android 的消息機(jī)制中扮演著消息循環(huán)的角色私沮,具體來(lái)說(shuō)就是不停地從MessageQueue 中查看是否有新的消息始赎,如果有新的消息就會(huì)立刻處理,否則就一直阻塞在那里仔燕。

Handler 的工作需要 Looper造垛,沒(méi)有 Looper 的線程就會(huì)報(bào)錯(cuò),那么如何為一個(gè)線程創(chuàng)建 Looper 呢晰搀?其實(shí)很簡(jiǎn)單五辽,通過(guò) Looper.prepare()即可為當(dāng)前線程創(chuàng)建一個(gè) Looper,接著通過(guò) Looper.loop()來(lái)開(kāi)啟消息循環(huán)外恕,如下所示:

  new Thread(){
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
            }
    }.start();

loop方法是一個(gè)死循環(huán)杆逗,唯一跳出循環(huán)的方式 MessageQueue 的 next 方法返回 null。
當(dāng) Looper 的 quit 方法被調(diào)用時(shí)鳞疲,Looper 就會(huì)調(diào)用 MessageQueue 的 quit 或者 quitSafely 方法來(lái)通知消息隊(duì)列退出罪郊,當(dāng)消息對(duì)列被標(biāo)記為退出狀態(tài)是,它的 next 方法就會(huì)返回 null尚洽。

4. Handler 原理:

Handler 的工作主要包含消息的發(fā)送和接收過(guò)程腺毫。消息的發(fā)送可以通過(guò)post 的一系列方法以及 send 的一系列方法來(lái)實(shí)現(xiàn),post 的一系列方法最終是通過(guò) send 方法的一系列方法來(lái)實(shí)現(xiàn)的睛挚。

Handler 發(fā)送消息的過(guò)程僅僅是向消息隊(duì)列中插入了一條消息扎狱,MessageQueue 的 next 方法就會(huì)返回這條消息給 Looper叁熔,Looper 接收到消息后就開(kāi)始處理了荣回,最終消息由 Looper 交 Handler處理,即 Handler 的 dispatchMessage 方法就會(huì)被調(diào)用壕吹,這時(shí) Handler 就進(jìn)入了處理消息的階段。


參考博客
作者:N0tExpectErr0r
地址:https://blog.csdn.net/qq_21556263/article/details/82759061

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末踏堡,一起剝皮案震驚了整個(gè)濱河市顷蟆,隨后出現(xiàn)的幾起案子帐偎,更是在濱河造成了極大的恐慌蛔屹,老刑警劉巖兔毒,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件育叁,死亡現(xiàn)場(chǎng)離奇詭異擂红,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)变秦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蹦玫,“玉大人樱溉,你說(shuō)我怎么就攤上這事福贞⊥J浚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵逻族,是天一觀的道長(zhǎng)骄崩。 經(jīng)常有香客問(wèn)我,道長(zhǎng)抠璃,這世上最難降的妖魔是什么宇弛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任枪芒,我火速辦了婚禮舅踪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悍赢。我一直安慰自己货徙,他們只是感情好痴颊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布蠢棱。 她就那樣靜靜地躺著,像睡著了一般糕再。 火紅的嫁衣襯著肌膚如雪突想。 梳的紋絲不亂的頭發(fā)上蒿柳,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音妓蛮,去河邊找鬼蛤克。 笑死构挤,一個(gè)胖子當(dāng)著我的面吹牛惕鼓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矾飞,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼洒沦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼申眼!你這毒婦竟也來(lái)了括尸?” 一聲冷哼從身側(cè)響起姻氨,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤剪验,失蹤者是張志新(化名)和其女友劉穎功戚,沒(méi)想到半個(gè)月后啸臀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年轧铁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旦棉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡救斑,死狀恐怖脸候,靈堂內(nèi)的尸體忽然破棺而出运沦,到底是詐尸還是另有隱情晾匠,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站澜共,受9級(jí)特大地震影響嗦董,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奇唤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一咬扇、第九天 我趴在偏房一處隱蔽的房頂上張望懈贺。 院中可真熱鬧梭灿,春花似錦、人聲如沸配乱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至休溶,卻和暖如春扰她,著一層夾襖步出監(jiān)牢的瞬間徒役,已是汗流浹背忧勿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留熏挎,地道東北人坎拐。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓哼勇,卻偏偏與公主長(zhǎng)得像猴蹂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354