美文网首页
Android Handler消息机制

Android Handler消息机制

作者: 三十五岁养老 | 来源:发表于2022-03-08 19:54 被阅读0次

参考链接:https://www.zhihu.com/question/34652589/answer/90344494

学习安卓的开发或多或少有以下问题,我们从问题角度分析消息机制

  • Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
  • 没看见哪里有相关代码为这个死循环准备了一个新线程去运转?
  • Activity的生命周期这些方法这些都是在主线程里执行的吧,那这些生命周期方法是怎么实现在死循环体外能够执行起来的?

进程

每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。

线程

线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。

当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。

Android消息处理机制本质

一个线程开启循环模式持续监听并依次处理其他线程给它发的消息。

loop启动时机

应用进程启动时,会调用ActivityThread.main()方法

public static void main(String[] args) {
        ....

        //创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread(); 

        //建立Binder通道 (创建新线程)
        thread.attach(false);

        Looper.loop(); //消息循环运行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
  • 创建looper和MessageQueue对象,用于处理主线程的消息
  • 通过thread.attach(false)将本地代理(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件)注册至ams,该Binder线程通过Handler将Message发送给主线程
  • Looper.loop()开启消息循环运行

ActivityThread实际上并非线程,不像HandlerThread类,ActivityThread并没有真正继承Thread类,承载ActivityThread的主线程是由Zygote fork而创建的进程。

Activity生命周期函数执行流程

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:


image.png
  • 应用启动时通过thread.attach(false)将本地代理(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件)注册至ams
  • ActivityManagerService(简称AMS),通过ApplicationThreadProx调用app ApplicationThread线程
    -ActivityThread的内部类H继承于Handler, ApplicationThread通过handler将消息发送至主线程队列
  • 在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。

pipe/epoll机制

主线程的死循环一直运行是不是特别消耗CPU资源呢
这里就涉及到Linux pipe/epoll机制,简在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。

epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

Android 为什么要设计为只能在主线程中更新 UI 呢?

  • Android 在子线程中更新 UI 是不安全的,如果多个子线程同时修改一个控件的数据,后果是不可控的
  • 如果给 UI 更新机制加锁,会降低 UI 的访问效率,并且可能阻塞某些线程的执行

Handler创建避免内存泄露

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

系统提示内存泄露, 原因是因为内部类默认持有外部类的引用,当 Activity 被用户关闭时,因为 Handler 持有了 Activity 的引用,就造成了 Activity 无法被回收,从而导致了内存泄漏。

private static class MyHandler extends Handler{
    private WeakReference<Activity> weakReference;
    public MyHandler(Activity activity){
        weakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
                case 0:                     
                  Toast.makeText(weakReference.get(),Thread.currentThread().getName(),Toast.LENGTH_SHORT).show();
                  break;
            }
    }
}

private MyHandler handler = new MyHandler(this);

可以通过静态内部类的方式实现一个 Handler,此时内部类并不持有外部类对象的应用,需要在内部类的构造方法内增加一个外部类(Activity)的弱应用。这样,即使 Activity 被关闭,Activity 也能顺利被回收。

使用IdleHandler处理延迟任务

IdleHandler在主线程空闲时执行任务,而不影响其他任务的执行,启动过程可以用此进行优化

       Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                //此处添加处理任务
                return false;
            }
        });

在主线程空闲,也就是activity创建完成之后,它会执行queueIdle()方法中的代码。

相关文章

网友评论

      本文标题:Android Handler消息机制

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