美文网首页
Android 要点学习笔记(一)Handler消息机制

Android 要点学习笔记(一)Handler消息机制

作者: 跟我去北方吧 | 来源:发表于2021-03-02 15:18 被阅读0次

    一、什么是进程、什么是线程,有和区别?

    进程和线程的基本定义是:进程是分配资源的基本单位,线程是独立运行和独立调度的基本单位;
    通俗的来讲一个应用程序一般就是一个进程(进程名:默认就是包名),我们访问数据是以进程为单位的(一般情况下进程之间是不允许直接访问到对方的数据,除非使用跨进程间通信)
    而线程是cpu执行的基本,一个进程可以包含一个或多个线程

    二、为什么主线程不能执行耗时操作?

    我们在打开一个应用程序时,AMS的startProcessLocked()方法中启动进程时,会为进程创建好主线程,也就是下图:


    1.png

    传递的"android.app.ActivityThread"即为我们通俗意义上的“主线程”
    进程启动的时候,会通过创建ActivityThread并调用其main(String[] args)方法开启主线程,因此进程和主线程是一 一对应的;
    大家都知道主线程是用来处理UI渲染绘制的,同时主线程也负责处理AMS对四大组件的调度分发处理,最终完成执行;
    如果在主线程中执行耗时操作,线程里的loop循环会阻塞,导致事件停止分发,最直观的就是界面卡住了,阻塞时间超过5秒直接ANR了;

    三、为什么子线程无法更新UI界面?

    由于Android的UI控件不是“线程安全”的,如果多线程并发访问,可能导致UI控件出现未知问题
    (线程安全是指,多线程编程中线程安全的代码会通过同步机制,保证各个线程都能正确执行)
    那么为什么UI控件不加同步机制,让它本省线程安全呢?
    加锁会使UI访问逻辑变得很复杂,加锁会降低UI的访问效率,因为加锁会阻塞某些线程的执行

    四、简述Handler机制?Handler机制的4个要素?

    Handler是跨线程通信机制,因为主线程不能执行耗时操作,需要在子线程中执行耗时操作,而子线程中不能直接对UI进行操作;所以当有耗时操作后的UI界面更新时,需要使用线程和Handler跨线程通信机制更新主线程UI界面;

    四个要素:
    Message(消息):需要被传递的消息
    MessageQueue(消息队列):负责存储管理消息,单链表维护,插入和删除有优势;
    Handler(消息处理器):负责发送、处理消息
    Looper(消息池):负责关联线程和消息的分发,Looper创建时会创建一个 MessageQueue;
    Looper.loop()后创建循环持续从MessageQueue那新的消息传递给Handler处理;

    五、 Handler机制的流程?

    1、应用程序创建进程时,主线程ActivityThread也被创建
    在主线程的main()函数中创建mainLooper并执行Looper.loop();

    1.png

    2、四大组件里的Handler创建时
    会获取主线程的Looper并拿到Looper的MessageQueue

    2.png

    3、Handler通过sendMessage发送消息,最终调用到queue.enqueueMessage
    在消息队列中添加该消息

    3.png
    ------------------------enqueueMessage()源码及分析如下:------------------------
    boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {//消息是否占用
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        } else if (msg.target == null) {//msg.target即Handler,不能为空
            throw new AndroidRuntimeException("Message must have a target.");
        } else {
            synchronized(this) {
                if (this.mQuitting) {//消息队列的持有者Looper已经quit了
                    RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
                    Log.w("MessageQueue", e.getMessage(), e);
                    return false;
                } else {
                    msg.when = when;//延迟时间赋值给msg
                    Message p = this.mMessages;//消息队列
                    boolean needWake;
                    if (p != null && when != 0L && when >= p.when) {
                        //消息队列不为空,延时不为零,延时大于队列第一个消息的延时时长
                        needWake = this.mBlocked && p.target == null && msg.isAsynchronous();
                        //while循环依次比较延时时长,时长越长,插入的位置越靠后
                        while(true) {
                            Message prev = p;
                            p = p.next;
                            if (p == null || when < p.when) {
                                msg.next = p;
                                prev.next = msg;
                                break;
                            }
    
                            if (needWake && p.isAsynchronous()) {
                                needWake = false;
                            }
                        }
                    } else {
                        //反之就是:消息队列为空 或 延时为零 或延时时长小于队列第一个消息的延时时长
                        msg.next = p;
                        this.mMessages = msg;
                        needWake = this.mBlocked;
                    }
    
                    if (needWake) {
                        nativeWake(this.mPtr);
                    }
    
                    return true;
                }
            }
        }
    }
    

    4、主线程执行了Looper.loop(),开始消息循环不断轮询调用MessageQueue.next()
    取得队列中下一个消息Message,并调用Handler的dispatchMessage(msg)传递给Handler

    4.png
    MessageQueue.next()函数从消息队列取出消息源码分析如下:
    
    Message next() {
        int pendingIdleHandlerCount = -1;
        int nextPollTimeoutMillis = 0;
    
        while(true) {
            while(true) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
    
                nativePollOnce(this.mPtr, nextPollTimeoutMillis);
                synchronized(this) {
                    long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = this.mMessages;
                    if (msg != null && msg.target == null) {
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while(msg != null && !msg.isAsynchronous());
                    }
    
                    if (msg != null) {
                        if (now >= msg.when) {
                            this.mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                this.mMessages = msg.next;
                            }
    
                            msg.next = null;
                            msg.markInUse();
                            return msg;
                        }
    
                        nextPollTimeoutMillis = (int)Math.min(msg.when - now, 2147483647L);
                    } else {
                        nextPollTimeoutMillis = -1;
                    }
                    
                    if (this.mQuitting) {//Looper.quit(),也就是退出程序了
                        this.dispose();
                        return null;
                    }
                    
                    ………………此处省略………………
                    this.mBlocked = true;//msg为空,且没有退出程序,Blocked在next()循环中
                }
            }
    
    ………此处省略………
    }
    

    5、Handler最终调用重写的handleMessage处理消息

    5.png

    六、Looper.loop()是死循环,为什么不会造成应用卡死?

    这是一个很有意思的问题,经过Handler消息机制的深入学习,我们已经知道:

    1、一个程序能正常运行、组件能正常调度、界面能正常刷新,后面是消息机制不停在起作用;
    2、Looper.loop()循环和MessageQueue.next()循环,就是为了不断把消息拿出,让消息机制不会停止;
    3、当消息队列为空时,只是界面和组件暂时不需要更新,从MessageQueue.next()代码可以看到,消息队列为空循环不会停止,只有循环不停止等到有新的消息入列时才会及时取出来;
    4、也就是说,恰恰是Looper.loop()的死循环,才能保证消息的及时发出,才能保持消息机制的正常运行;

    通过上面的描述我们其实已经知道了,即应用卡死(即ANR)和主线程阻塞是没什么关系的;
    ANR全称Application Not Responding,即应用程序无响应,指的是界面阻塞,即消息队列阻塞;

    七、那么在主线程里做耗时操作,会造成ANR呢?

    ANR,Application Not Responding即应用程序无响应,是界面阻塞

    1、应用程序的界面更新,是由主线程的消息处理机制完成的
    2、主线程的消息处理机制,是由Looper.loop()和MessageQueue.next()的死循环不断取出消息,来维持消息机制不断更新的
    3、主线程中新增耗时操作,就会把Looper.loop()的循环暂停住,等新增的耗时操作完成后Looper.loop()的循环才会继续走下去
    4、loop循环停止一段时间,相当于消息机制就停止了一段时间,这段时间内无法响应用户操作,界面UI无法更新
    5、当这个时间超过5s,即引发ANR

    八、一个线程能否有多个Looper,能否有多个Handler,Handler和Looper之间关系?

    1、一个线程只能有一个Looper,并且只有一个MessageQueue被Looper持有
    2、一个线程可以有多个Handler,Handler发送的Message会被标记上Target

    Handler和Looper是多对一的关系,各个Handler发送的Message会被标记上Target,根据延时长短被先后放入到一个MessageQueue中,在Looper.loop()时依次取出msg,根据msg的target发送回给指定的Handler处理

    九、在子线程直接new Handler可以么?需要怎么做?

    不可以,看源码可以知道

    直接new Handler,会报RuntimeException
    "Can't create handler inside thread that has not called Looper.prepare()"
    就是,无法在没有 Looper.prepare()之前就在线程中创建Handler

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

    在四大组件中可以直接new Handler是因为,主线程ActivityThread的main()函数中会自动创建Looper对象无需我们自己管理;
    在子线程中,正确的方法是:
    先Looper.prepare()
    然后new Handler()
    最后Looper.loop()让消息队列运行起来
    不过我们不建议子线程中再加Handler
    因为Looper.loop()直接子线程就阻塞了,无法再做其他的耗时操作


    6.png

    十、Message要如何创建?哪种创建方法最好?

    有三种方式:
    第一种是新建了实例
    后两种方式比较好,直接复用了消息池中已有的Message实例

    Message msg1 = new Message(); 
    Message msg2 = Message.obtain(); 
    Message msg3 = handler.obtainMessage();
    

    十一、Handler的PostDelay()方法使用后,消息队列是如何处理的?

    查看源码:
    postDelayed()直接调用的sendMessageDelayed()
    并通过getPostMessage(Runnable r)获取Message实例
    最后还是调用到MessageQueue.enqueueMessage()函数将消息添加到消息链表

    
    public final boolean postDelayed(Runnable r, long delayMillis) {
        return this.sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0L) {
            delayMillis = 0L;
        }
    
        return this.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = this.mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        } else {
            return this.enqueueMessage(queue, msg, uptimeMillis);
        }
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (this.mAsynchronous) {
            msg.setAsynchronous(true);
        }
    
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    

    十二、Handler使用时应注意哪些问题?有什么解决方法?

    Handler在使用时很容易遇到内存泄漏问题

    在发送消息是无论调用的是handler.sendMessage或handler.sendMessageDelayed最终都会调用到
    enqueueMessage()将msg添加到MessageQueue中,如下图源码:
    发送出去的msg都会持有handler实例
    (这是因为消息执行时,也会根据msg持有的handler实例将msg发回给对应的Handler执行)

    
    package android.os;
    
    import android.os.IMessenger.Stub;
    import android.util.Log;
    import android.util.Printer;
    
    public class Handler {
        ………… 
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;//msg将会持有Handler实例
            if (this.mAsynchronous) {
                msg.setAsynchronous(true);
            }
        
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    }
    

    所以Handler在使用时遇到内存泄漏问题的原因是:
    handler发送的延时消息会持有handler实例,而handler我们很多时候是使用的匿名内部类,
    java的特性匿名内部类会隐式持有外部类对象的引用
    也就是说,如果在延时过程中Activity等组件退出了,msg持有handler,handler会持有activity,
    这就造成了activity的内存无法回首,造成activity内存泄漏

    解决办法:
    Handler声明时使用静态内部类,持有外部类的若应用

    相关文章

      网友评论

          本文标题:Android 要点学习笔记(一)Handler消息机制

          本文链接:https://www.haomeiwen.com/subject/rtxxqltx.html