Android異步消息處理機(jī)制完全解析-Handler詳解

參考資料

如果在非UI線程中更新UI會(huì)出現(xiàn)問(wèn)題嗎?

實(shí)踐:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.id_tv)
    TextView idTv;

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

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000*5);
                    idTv.setText("Javen205測(cè)試非UI線程更新UI會(huì)出現(xiàn)什么異常呢?");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

我們運(yùn)行項(xiàng)目就會(huì)以下異常

非UI線程中更新UI出現(xiàn)

為了更直觀的看到報(bào)錯(cuò)原因,我們找到源碼ViewRootImpl的checkThread方法,看它做了些什么。

ViewRootImpl.checkThread()

但是Android為什么要這樣搞呢?

我們仔細(xì)看這句話,只有創(chuàng)建了View的線程才能對(duì)這個(gè)View進(jìn)行操作。而我們一般View都是為了顯式在UI上的。Android正是為了防止我們?cè)诜荱I線程去操作這些UI上的控件,才加了限制的。因?yàn)閁I體驗(yàn)對(duì)用戶來(lái)說(shuō)是最直觀的,如果誰(shuí)都有權(quán)限去操作一下,那UI要么很亂,要么控制很復(fù)雜。

竟然Android是不允許我們?cè)诜荱I線程中去執(zhí)行更新UI,那我們要怎么解決這個(gè)問(wèn)題呢?那我們就要使用Android 提供的Hander機(jī)制去更新UI了

一、什么是Handler

Handler是Android提供的用來(lái)更新UI的一套機(jī)制,也是一套消息處理機(jī)制,我們可以通過(guò)它發(fā)送消息,也可以通過(guò)它處理消息。

二、為什么要使用Handler

Android在設(shè)計(jì)的時(shí)候,就封裝了一套消息創(chuàng)建、傳遞、處理機(jī)制,如果不遵循這樣的機(jī)制就沒(méi)有辦法更新UI信息,就會(huì)拋出異常。

三、Handler怎么用呢?

  • 在非UI線程借助Handler.post(Runnable)更新UI

首先在Activity中實(shí)例化一個(gè)Hander

Handler handler = new Handler();

然后在子線程中調(diào)用Handler.post(Runnable)更新UI
詳細(xì)的代碼如下:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.id_tv)
    TextView idTv;

    Handler handler = new Handler();

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

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000*5);
//                    idTv.setText("Javen205測(cè)試非UI線程更新UI會(huì)出現(xiàn)什么異常呢?");
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            idTv.setText("Javen205測(cè)試非UI線程更新UI會(huì)出現(xiàn)什么異常呢?");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

  • 在非UI線程借助Handler.postDelayed(Runnable, DelayTime)定時(shí)執(zhí)行相關(guān)動(dòng)作

栗子:實(shí)現(xiàn)三張圖片自動(dòng)切換

實(shí)現(xiàn)三張圖片自動(dòng)切換
 private  int images[]= {R.drawable.image4,R.drawable.image6,R.drawable.image7};
    
 private int index=0;

Handler handler = new Handler();

Thread myThread = new Thread(){
        @Override
        public void run() {
            index++;
            index = index%3;
            System.out.println(index);
            idImg.setImageResource(images[index]);
            handler.postDelayed(myThread,1000);
        }
    };


handler.postDelayed(myThread,1000);

要是我們想停止圖片的切換,那要如何操作呢?

  • Handler移除一個(gè)消息
handler.removeCallbacks(myThread);
  • 自定義Handler和Message

自定義Hander(customHander)

Handler customHander = new Handler() {
       @Override
       public void handleMessage(Message msg) {
           idTv.setText("msg.arg1>"+msg.arg1+ "\nmsg.arg2>" +msg.arg2 +"\nmsg.obj>"+((Dog)msg.obj).toString());
       }
   };

實(shí)體類

package com.javen205.entity;

import java.io.Serializable;



public class Dog implements Serializable{
    private String name;
    private int  age;

    public Dog() {
    }

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

使用只定義Hander(customHander)發(fā)送消息

Dog dog=new Dog("薩摩耶",1);
Message message = new Message();
                message.arg1 = 1;
                message.arg2 = 2;
                message.obj = dog;
                customHander.sendMessage(message);               

復(fù)用Message

 Dog dog=new Dog("薩摩耶",1);
//                Message message = new Message();
                Message message= customHander.obtainMessage();
                message.arg1 = 1;
                message.arg2 = 2;
                message.obj = dog;
                customHander.sendMessage(message);

原理:obtainMessage()方法做了哪些操作呢?

 /**
     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
     *  If you don't want that facility, just call Message.obtain() instead.
     */
    public final Message obtainMessage()
    {
        return Message.obtain(this);//this 就是Hander本身
    }

我們看看Message.obtain(Handler h) 又做了哪些操作呢?

 /**
     * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h; 

        return m;
    }

target就是Hander自己,指消息要發(fā)送給誰(shuí)

再來(lái)看看obtain()方法

/**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

這個(gè)方法就是跟我們平常new Message 對(duì)象一樣 ,只不過(guò)是在之前添加了一判斷,判斷系統(tǒng)中是否存在空的Message,如果存在就直接返回否則就創(chuàng)建一個(gè)Message對(duì)象。

其實(shí)發(fā)送消息也可以這樣玩,不使用直接使用Hander而是使用Message的sendToTarget()方法,代碼如下。

 Dog dog=new Dog("薩摩耶",1);
//                Message message = new Message();
                Message message= customHander.obtainMessage();
                message.arg1 = 1;
                message.arg2 = 2;
                message.obj = dog;
//                customHander.sendMessage(message);
                message.sendToTarget();

我們來(lái)看看Message.sendToTarget()方法源碼

 /**
     * Sends this Message to the Handler specified by {@link #getTarget}.
     * Throws a null pointer exception if this field has not been set.
     */
    public void sendToTarget() {
        target.sendMessage(this);
    }

上面提到過(guò)target就是Hander自己,其實(shí)就是調(diào)用Hander自己的一個(gè)sendMessage,跟我們普通發(fā)送Message沒(méi)有什么區(qū)別只是里面封裝了一個(gè)target,本質(zhì)還是調(diào)用了Hander.sendMessage(...)

  • Handler 消息攔截使其接收者接收不到消息

自定義Handler(interceptHander)攔截消息

Handler interceptHander = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            System.out.println("is intercept Handler>"+msg.what);
            // 設(shè)置true攔截消息
            return true;
        }
    }){
        @Override
        public void handleMessage(Message msg) {
            System.out.println("is intercept Handler");
        }
    };

interceptHander發(fā)送一個(gè)消息Message

 interceptHander.sendEmptyMessage(1);

四、Handler幾種發(fā)送消息方式之間區(qū)別

可以參考:Handler發(fā)送sendMessage和postRunnable的區(qū)別

五、Handler的原理是什么?

handler原理圖
  • Handler封裝了消息的發(fā)送:內(nèi)部會(huì)跟Looper關(guān)聯(lián)

  • Looper(消息封裝的載體):內(nèi)部包含一個(gè)消息隊(duì)列(MessageQueue),所有Handler發(fā)送的消息都會(huì)走向這個(gè)消息隊(duì)列;
    Looper.Looper方法是一個(gè)死循環(huán),不斷的從MessageQueue取消息,如果有消息就處理消息,沒(méi)有消息就阻塞。

  • MessageQueue(消息隊(duì)列):可以添加消息,并處理消息

總結(jié):Handler負(fù)責(zé)發(fā)送消息,Looper負(fù)責(zé)接收Handler發(fā)送的消息,并直接把消息回傳給Handler自己(handleMessage),MessageQueue就是一個(gè)存儲(chǔ)消息的容器。

六、自定義一個(gè)與線程相關(guān)的Handler

1、線程中創(chuàng)建一個(gè)Looper 可以使用 Looper.prepare();方法
2、實(shí)例化一個(gè)Handler
3、調(diào)用Looper.loop();方法循環(huán)處理消息

public class HandlerActivity extends AppCompatActivity {
    private MyThread myThread;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            System.out.println("UI :"+Thread.currentThread());
        }
    };

    class MyThread extends Thread{
        public Handler handler;
        @Override
        public void run() {
            Looper.prepare();

            handler= new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    System.out.println("currentThread:"+Thread.currentThread());
                }
            };

            Looper.loop();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        myThread = new MyThread();
        myThread.start();
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread.handler.sendEmptyMessage(1);
        handler.sendEmptyMessage(1);
    }
}

輸出結(jié)果:

System.out: currentThread:Thread[Thread-151,5,main]
System.out: UI :Thread[main,5,main]

七、非UI線程真的不能更新UI嗎?

我們運(yùn)行下面的代碼測(cè)試:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        new Thread() {
            @Override
            public void run() {
                idTv.setText("Javen205測(cè)試非UI線程更新UI會(huì)出現(xiàn)什么異常呢?");
            }
        }.start();
    }

最開(kāi)始的案例中我們?nèi)サ鬞hread.sleep(...)會(huì)正常運(yùn)行嗎?答案:可正常運(yùn)行。是不是感覺(jué)有點(diǎn)奇怪呢?那為什么直接在Activity的onCreate中添加子線程可以直接更新UI呢?

詳細(xì)解答: 為什么我們可以在非UI線程中更新UI

八、Handler異步消息處理(HandlerThread)

Android HandlerThread 完全解析

Android異步消息處理機(jī)制完全解析,帶你從源碼的角度徹底理解

一個(gè)簡(jiǎn)單的例子

public class HandlerThreadActivity extends AppCompatActivity {
    private Handler handler;
    private HandlerThread thread;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        thread = new HandlerThread("Handler Thread");
        thread.start();
        handler = new Handler(thread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                // 處理耗時(shí)操作
               System.out.println("current thread>"+Thread.currentThread());
            }
        };
        handler.sendEmptyMessage(1);
    }
}

測(cè)試輸出結(jié)果
I/System.out: current thread>Thread[Handler Thread,5,main]

九、如何在主線程給子線程發(fā)送消息


public class HandlerThreadActivity extends AppCompatActivity {
    @BindView(R.id.id_btn1)
    Button idBtn1;
    @BindView(R.id.id_btn2)
    Button idBtn2;
    private Handler threadhandler;
    private HandlerThread thread;

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            System.out.println("UI thread>" + Thread.currentThread());
            // 給主線程發(fā)送消息
            Message message = new Message();
            message.what =1;
            threadhandler.sendMessageDelayed(message, 1000);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        ButterKnife.bind(this);
        thread = new HandlerThread("Handler Thread");
        thread.start();
        threadhandler = new Handler(thread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // 處理耗時(shí)操作
                System.out.println("current thread>" + Thread.currentThread());
                // 給主線程發(fā)送消息
                Message message = new Message();
                message.what =1;
                handler.sendMessageDelayed(message, 1000);
            }
        };
//        threadhandler.sendEmptyMessage(1);
    }

    @OnClick({R.id.id_btn1, R.id.id_btn2})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.id_btn1:
                handler.sendEmptyMessage(1);
                break;
            case R.id.id_btn2:
                handler.removeMessages(1);
                threadhandler.removeMessages(1);
                break;
        }
    }
}

十、Android中更新UI的幾種方式

  • handler sendMessage
  • runOnUIThread
  • handler post
  • view post

項(xiàng)目源碼地址:https://github.com/Javen205/Hander

推薦閱讀
AndroidStudio多渠道打包
Android依賴管理與私服搭建
Android Studio 上傳aar(Library)到JCenter
Android版-支付寶APP支付
Android版-微信APP支付
支付寶Wap支付你了解多少?
一張二維碼集成微信、支付寶支付

安利時(shí)間:
JPay是對(duì)微信App支付、支付寶App支付的二次封裝,對(duì)外提供一個(gè)相對(duì)簡(jiǎn)單的接口以及支付結(jié)果的回調(diào)

極速開(kāi)發(fā)微信公眾號(hào)是對(duì)微信公眾平臺(tái)接口的二次封裝。包括開(kāi)發(fā)者模式、事件回調(diào)監(jiān)聽(tīng)、微信模板消息、微信客服消息、自定義菜單、微信支付、素材管理等

如遇到問(wèn)題歡迎留言交流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,048評(píng)論 25 709
  • 異步消息處理線程啟動(dòng)后會(huì)進(jìn)入一個(gè)無(wú)限的循環(huán)體之中,每循環(huán)一次,從其內(nèi)部的消息隊(duì)列中取出一個(gè)消息,然后回調(diào)相應(yīng)的消息...
    cxm11閱讀 6,528評(píng)論 2 39
  • Android消息處理機(jī)制估計(jì)都被寫(xiě)爛了,但是依然還是要寫(xiě)一下,因?yàn)锳ndroid應(yīng)用程序是通過(guò)消息來(lái)驅(qū)動(dòng)的,An...
    一碼立程閱讀 4,594評(píng)論 4 36
  • Handler的由來(lái) 當(dāng)程序第一次啟動(dòng)的時(shí)候,Android會(huì)同時(shí)啟動(dòng)一條主線程(Main Thread)來(lái)負(fù)責(zé)處...
    wangling90閱讀 2,109評(píng)論 2 7
  • 我小姑比父親小十歲,我記事起,小姑早已成家,有了兒子和女兒。每年過(guò)春節(jié)或暑假,都會(huì)聚在一起,大人們家長(zhǎng)里短嘮嗑,我...
    玉妮閱讀 1,781評(píng)論 3 7

友情鏈接更多精彩內(nèi)容