谈谈Hanlder消息机制

1、什么是Handler,为什么要有Handler?

Android中主线程也叫UI线程,主线程主要是用来创建、更新UI的。而其他耗时操作,比如网络访问、文件处理、多媒体处理等都需要在子线程中操作,之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢失帧,那么主线程处理代码最好不要超过16毫秒。当子线程处理完数据后,为了防止UI处理逻辑的混乱,Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。

2、Handler的使用方法

post(runnable)
sendMessage(message)
其实post(runnable)和sendMessage(message)最终底层都是调用了sendMessageAtTime方法。

3、Handler消息机制中涉及到哪些类,各自的功能是什么?

Handler主要用于跨线程通信。涉及MessageQueue、Message、Looper、Handler这4个类。

  • Message:消息对象。对象的内部实现是单列链表,最大长度是50,用于缓存消息对象,达到重复利用消息对象的目的,以减少消息对象的创建。

  • MessageQueue:消息队列。主要功能是向消息池投递信息(MessageQueue.enqueueMessage)和取走消息池的信息(MessageQueue.next) 。

  • Handler:消息对象的发送者和处理者。负责向消息池中发送消息(Handler.enqueueMessage)和处理消息(Handler.handleMessage) 。

  • Looper:消息队列的处理者。用于轮询消息队列里面的消息对象,不断从MessageQueue队列中轮询取出Message,交由handler的dispatchMessage()方法进行消息的分发,dispatchMessage方法再调用handleMessage()方法进行消息回调。

它们之间的类关系:Looper里面有一个MessageQueue消息队列,MessageQueue有一组待处理的Message,Message中有一个用于处理消息的Handler,Handler中有Looper和MessageQueue。

4、Handler消息机制的原理

在应用启动时,ActivityThread类的main方法里面初始化调用Looper.preperMainLooper方法,调用该方法的目的是在Looper中创建MessageQueue成员变量并把Looper对象绑定到当前线程中(ThreadLocal)。当调用Handler的sendMessage方法的时候,就将Message对象添加到了Looper创建的MessageQueue队列中,同时给Message指定了target对象,其实这个target对象就是Handler对象。由于在main方法里面初始化时执行了Looper.loop()方法,该方法从Looper的成员变量MessageQueue队列中不断轮询取出Message,交由handler的dispatchMessage()方法进行消息的分发,dispatchMessage方法再调用handleMessage()方法进行消息回调,这样就完成了整个消息机制。

5、Handler线程安全问题

对于子线程访问主线程的Handler对象,你可能会问,多个子线程都访问主线程的Handler对象,发送消息和处理消息的过程中会不会出现数据的不一致呢?答案是不会出现数据的不一致问题。因为Handler对象通过ThreadLocal管理的Looper对象是线程安全的,不管是添加消息到消息队列还是从消息队列中读取消息都是通过synchronized同步保护的,所以不会出现数据不一致现象。

6、Handler引起的内存泄漏以及解决办法

原因:
非静态内部类持有外部类的强引用,导致外部Activity无法释放。

解决办法:
1、handler内部持有外部activity的弱引用
2、把handler改为静态内部类
3、mHandler.removeCallbacksAndMessages

//代码示例
public class MainActivity extends AppCompatActivity {
 
    //创建静态内部类
    private static class MyHandler extends Handler{
        //持有弱引用MainActivity,GC回收时会被回收掉.
        private final WeakReference mAct;
        public MyHandler(MainActivity mainActivity) {
            mAct = new WeakReference(mainActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity mainAct = mAct.get();
            super.handleMessage(msg);
            if(mainAct != null){
                //执行业务逻辑
            }
        }
    }

    private static final Runnable myRunnable = new Runnable() {
        @Override
        public void run() {
            //执行业务逻辑
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyHandler myHandler=new MyHandler(this);
        //通过postDelayed延迟发送
        //myHandler.postDelayed(myRunnable, 1000 * 5);
        //通过sendMessageDelayed延迟发送
        Message message = Message.obtain();
        message.what = 1;
        myHandler.sendMessageDelayed(message, 1000 * 5);
    }
}

7、一个线程可以有几个Looper、几个MessageQueue和几个Handler?

在Android中,Looper类利用了ThreadLocal的特性,保证了每个线程只存在一个Looper对象。

static final ThreadLocal sThreadLocal = new ThreadLocal();

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));
}

Looper构造函数中创建了MessageQueue 对象,因此一个线程只有一个MessageQueue。

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

可以有多个Handler。
Handler在创建时将Looper和MessageQueue关联起来:

public Handler(Callback callback, boolean async) {
    ...
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    ...
}

8、可以在子线程直接创建一个Handler吗?会出现什么问题,那该怎么做?

不能在子线程直接new一个Handler。因为Handler的工作依赖于Looper,而Looper又是属于某一个线程的,其他线程不能访问,所以在线程中使用Handler时必须要保证当前线程中Looper对象创建并且启动循环。不然会抛出异常throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");

正确做法是:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();//为线程创建Looper对象
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {
               
            }
        };
        Looper.loop();//启动消息循环
    }
}

9、既然线程中创建Handler时需要Looper对象,为什么主线程不用调用Looper.prepare()创建Looper对象?

在应用启动时,ActivityThread类的main方法里面调用了Looper.prepareMainLooper()方法,Looper.prepareMainLooper()方法里面调用了Looper.prepare()方法,方法里面将Looper对象set到sThreadLocal对象当中。

10、Looper死循环为什么不会导致应用卡死,会消耗大量资源吗?

对于线程执行一段可执行代码,当可执行代码执行完成后,线程生命周期便终止了,线程退出。而对于主线程,我们是绝不希望运行一段时间后,自己就退出了,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,Binder线程也是采用死循环的方法,通过循环方式与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,Looper.loop()本身不会导致应用卡死。

主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在Looper的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

11、MessageQueue是队列吗?它是什么数据结构?

MessageQueue不是队列,它内部使用一个Message链表来实现消息的存和取。 链表的排列依据是Message.when,表示Message期望被分发的时间,该值是SystemClock. uptimeMillis()与delayMillis之和。

12、handler.postDelayed()函数延时执行计时是否准确?

当上一个消息存在耗时任务的时候,会占用延时任务执行的时机,实际延迟时间可能会超过预设延时时间,这时候就不准确了。

13、handler发送延时消息是怎么处理的?

根据消息队列入队规制,如果队列中没消息,那么不管要入队的消息有没有延时,都放到队列头。如果队列中有消息,那么要跟队列头的消息比较一下延时,如果要入队的消息延时短,则放队列头,否则,放到队列中去,需要移动链表。

入队规制的好处是,延时越长的消息在队列越后面,所以next方法取到一个延时消息时,如果判断时间没有到,就进行阻塞,不用管后面的消息,因为队列后面的消息延迟时间更长。

14、你了解HandlerThread吗?

HandlerThread继承自Thread,它是一种可以使用Handler的Thread,它的实现也很简单,在run方法中也是通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环(与我们手动创建方法基本一致),这样在实际的使用中就允许在HandlerThread中创建Handler了。

public class HandlerThread extends Thread {
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
}

由于HandlerThread的run方法是一个无限循环,因此当不需要使用的时候通过quit或者quitSafely方法来终止线程的执行。

你可能感兴趣的:(谈谈Hanlder消息机制)