欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android的Handler消息传递机制

程序员文章站 2022-03-09 19:24:32
...

能简单说得我们尽量不复杂:

为了避免ANR,我们会通常把 耗时操作放在子线程里面去执行,因为子线程不能更新UI,所以当子线程需要更新的UI的时候就需要借助到安卓的消息机制,也就是Handler机制了。

一个程序的运行,就是一个进程的在执行,一个进程里面可以拥有很多个线程。

主线程:也叫UI线程,或称ActivityThread,用于运行四大组件和处理他们用户的交互。 ActivityThread管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的主线程负责执行。 
ActivityThread既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。

子线程: 用于执行耗时操作,比如 I/O操作和网络请求等。(安卓3.0以后要求耗访问网络必须在子线程种执行)更新UI的工作必须交给主线程,子线程在安卓里是不允许更新UI的。

一、 基本概念

什么是消息机制? —— 不同线程之间的通信。

什么安卓的消息机制,就是 Handler 运行机制。

安卓的消息机制有什么用? —— 避免ANR(Application Not Responding) ,一旦发生ANR,程序就挂了,奔溃了。

什么时候会触发ANR?(消息机制在什么时候用?) —— 以下两个条件任意一个触发的的时候就会发生ANR

  • 在activity中超过5秒的时间未能响应下一个事件
  • BroadcastReceive超过10未响应

造成以上两点的原因有很多,比如网络请求, 大文件的读取, 耗时的计算等都会引发ANR

如何避免ANR 
首先明白两点:

  • 主线程不能执行耗时操作(避免ANR)
  • 子线程不能直接更新UI界面

结合起来这两点的解决办法是:把耗时操作放到子线程去执行,然后使用Handler去更新UI

二、消息机制的分析理解

安卓的异步消息处理机制就是handler机制

主线程,ActivityThread被创建的时候就会创建Looper 
Looper被创建的时候创建MessageQueue。 
也就是说主线程会直接或简介创建出来Looper和MessageQueue。

Handler的工作机制简单来说是这样的

1、Handler发送消息仅仅是调用MessageQueue的enqueueMessage向插入一条信息到MessageQueue

2、Looper不断轮询调用MeaasgaQueue的next方法

3、如果发现message就调用handler的dispatchMessage,ldispatchMessage被成功调用,接着调用handlerMessage()

简图

Android的Handler消息传递机制

1、Handler的铁三角—— Handler、MessageQueue和Lopper

android的消息机制就是指Handler机制,Handler机制的运行需要MeeageQueue和Looper的辅助。

2、MessageQueue(消息队列)的工作原理

MessageQueue中文翻译就是消息队列,它内部存储了一组信息,存放的是Message,以队列的形式对外提供了插入和删除的工作(虽然名字叫做队列,但是其内部的 存储结构是单链表)

主要 插入 和 读取 两个操作,这两个操作对应着两个方法:

  • 插入(入队) enqueueMessage(Message msg, long when)
  • 读取(出队) next()

enqueueMessage方法

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 don't 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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

可以看出enqueueMessage主要是单链表的插入操作。

next方法

next方法在这里是一个无限循环的方法,如果消息队列里面没有消息,那么他就会处于阻塞状态,当有新的消息到来的时,next就会返回这条消息并且将其从单链表中移除。

3、Looper的工作原理

Looper中文翻译是轮询器或者消息泵或者循环。个人还是叫做轮询器比较形象一些。

3.1、Looper的作用

Looper是一个轮询器,它的作用不断轮询MessageQueue,当如果有新的消息就交给Handler处理,如果轮询不到新的消息,那就自身就处于阻塞状态。

3.2、Looper的构造函数创建了MessageQueue

我们通过查看Loop而这个类,可以发现的他的构造方法里面创建了一个MessageQueue,然后将当前线程的对象保存起来

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
  • 1
  • 2
  • 3
  • 4

3.3、new Handler的hanlder不能没有Looper

new出来一个Handler但是没有创建Looper的话就会报错。

"Can't create handler inside thread that has not called Looper.prepare()"); ,
  • 1

解决办法就是new Handler的时候加上Looper.prepare();

3.4、主线程(ActivityThread),被创建的时候就会创建一个Looper

线程默认是没有Looper的,但是为什么在主线程没有创建的Looper就可以使用Handler?主线程是特别的。主线程,也就是ActivityThread,主线程被创建的时候就会创建一个Looper,这点是比较特殊的,也正因为这点,所以我们在主线程创建了Handler就直接能用了。

3.5、Looper的ThreadLocal

Looper有一个特殊的概念,那就是ThreadLocal,(他并不是线程),他的作用是帮助Handler获得当前线程的Looper(多个线程可能有多个Looper)

Looper 的几个方法

  • 创建:Looper.prepare() : 为当前线程创建一个Looper,prepareMainLooper() : UI线程(ActivityThread)创建Looper的
  • 开启:Looper.loop() : 开启消息轮询
  • 退出:quit() : 直接退出Looper, 
    quitSafely() : 设定一个标记,只有当目前已有消息处理完毕之后才会执行退出操作。

Looper这个类里面最重要的方法就是loop()开启消息循环这个方法了,

public static final void loop() {  
    Looper me = myLooper();  
    MessageQueue queue = me.mQueue;  
    while (true) {  
        Message msg = queue.next(); // might block  
        if (msg != null) {  
            if (msg.target == null) {  
                return;  
            }  
            if (me.mLogging!= null) me.mLogging.println(  
                    ">>>>> Dispatching to " + msg.target + " "  
                    + msg.callback + ": " + msg.what  
                    );  
            msg.target.dispatchMessage(msg);  
            if (me.mLogging!= null) me.mLogging.println(  
                    "<<<<< Finished to    " + msg.target + " "  
                    + msg.callback);  
            msg.recycle();  
        }  
    }  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

可以看到,这个方法从第4行开始,进入了一个死循环,然后不断地调用的MessageQueue的next()方法,我想你已经猜到了,这个next()方法就是消息队列的出队方法。不过由于这个方法的代码稍微有点长,我就不贴出来了,它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()方法的第14行,每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法的第6行就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示:

public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {  
        handleCallback(msg);  
    } else {  
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  
                return;  
            }  
        }  
        handleMessage(msg);  
    }  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在第5行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!

4、Handler的工作原理

主要工作:消息的 发送 和 接受 。

Handler消息发送的形式

先附上两份最简单的日常正常使用post和send方式的代码 
send方式sendEmptyMessage方法的小demo

public class MainActivity extends Activity {

    private static final int MSG_CHANGE_TEXT = 0x2001;
    private TextView mTv;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (msg.what){
                case MSG_CHANGE_TEXT:
                    mTv.setText("send方式修改的文字");
                    break;
            }
        }
    };;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTv= (TextView) findViewById(R.id.mTv);
        mTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handler.sendEmptyMessage(MSG_CHANGE_TEXT);
            }
        });
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

post方式的postDelayed方法的小demo

public class MainActivity extends Activity {
    private Handler handler;
    private TextView mTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler = new Handler();

        mTv = (TextView) findViewById(R.id.mTv);

        mTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                change();
            }
        });

    }

    private void change(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mTv.setText("啊哈哈哈");
                    }
                },300);
            }
        }).start();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

两种形式,post和send,其实post最终还是会调用send

Handler消息接收的形式

再一遍简要地附上handler工作形式

1、Handler发送消息仅仅是调用MessageQueue的enqueueMessage向插入一条信息到MessageQueue

2、Looper不断轮询调用MeaasgaQueue的next方法

3、如果发现message就调用handler的dispatchMessage,ldispatchMessage被成功调用,接着调用handlerMessage()

如果是post发送来的message,那么就让这个message所持有的Runnable执行run方法,非常简单。 
Message的Callback 是一个Runnable对象,Handler的post的重载的函数不管参数多少,肯定都是有Runnable的。

 private static void handleCallback(Message message) {
        message.callback.run();
    }
  • 1
  • 2
  • 3

如果是send方法发送的,那么就执行handleMessage,这个方法我们非常熟悉了,google的给的备注的也说了,子类必须实现方法以接受这些Message。这也就是我们最常见的最常用的方式了。

 /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
  • 1
  • 2
  • 3
  • 4
  • 5