美文网首页Android 面试
1.Handler的运行机制,线程间是如何通信的?

1.Handler的运行机制,线程间是如何通信的?

作者: Android看海 | 来源:发表于2017-07-11 10:24 被阅读16次

    Android应用程序是通过消息来驱动的,系统为每一个应用程序维护一个消息队例(MessageQueue),应用程序的主线程不断地从这个消息队例中获取消息(Looper),然后对这些消息进行处理(Handler),这样就实现了通过消息来驱动应用程序的执行。

    Handler在整个Android应用中占有很重要的地方,所以面试时我们经常要考查一下面试者是否了理解它的原理,并且能够将大体的流程表述清楚,沟通和表达能力有时比技术能力更重要。

    面试题:能讲讲Android的Handler机制吗?

    要讲清楚Android中的消息机制,肯定要先表述一下和Handler相关的一些类:

    Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
    MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
    Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
    Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。
    Handler相关类的代码量并不大,建议大家都去看一下,网上也有很多介绍和分析这些源码的文章,大家自己Google一下。大家把代码过了一遍后,会更加深对整个过程的理解,讲起来就从容多了。不建议大家为了面试去背书。

    面试时,如果一个人可以清楚的表达Handler的运行机制,那么接下来面试官会主要问一下一些实际开发中注意的地方。比如会问在一个工作线程中创建自己的消息队例应该怎么做?

    其实就是想从侧面验证他是否正的了解,是否知道要调用Looper.prepare(在每个线程只允许执行一次)。

    或者再问问是否用过HandlerThread,它有什么优缺点等。

    优点

    1.开发中如果多次使用类似new Thread(){...}.start()这种方式开启一个子线程,会创建多个匿名线程,使得程序运行起来越来越慢,而HandlerThread自带Looper使他可以通过消息来多次重复使用当前线程,节省开支;
    2.Android系统提供的Handler类内部的Looper默认绑定的是UI线程的消息队列,对于非UI线程又想使用消息机制,那么HandlerThread内部的Looper是最合适的,它不会干扰或阻塞UI线程。

    注意事项

    一旦我们使用了HandlerThread,需要特别注意给HandlerThread设置不同的线程优先级,CPU会根据设置的不同线程优先级对所有的线程进行调度优化。

    注意:Handler可能会引起的内存泄露

    private final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
            }
        };```
    但是,其实上面的代码可能导致内存泄露,当你使用Android lint工具的话,会得到这样的警告:
    >In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class
    
    看到这里,可能还是有一些搞不清楚,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下。
    * 1.当一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。主线程中的Looper生命周期和当前应用一样长。
    * 2.当一个Handler在主线程进行了初始化之后,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上已经发送的消息已经包含了一个Handler实例的引用,只有这样Looper在处理到这条消息时才可以调用Handler#handleMessage(Message)完成消息的正确处理。
    * 3.在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。关于这一内容可以查看[细话Java:”失效”的private修饰符](http://droidyue.com/blog/2014/10/02/the-private-modifier-in-java/)
    确实上面的代码示例有点难以察觉内存泄露,那么下面的例子就非常明显了
    ```java
    public class SampleActivity extends Activity {
    
      private final Handler mLeakyHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
          // ...
        }
      }
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // Post a message and delay its execution for 10 minutes.
        mLeakyHandler.postDelayed(new Runnable() {
          @Override
          public void run() { /* ... */ }
        }, 1000 * 60 * 10);
    
        // Go back to the previous Activity.
        finish();
      }
    }```
    分析一下上面的代码,当我们执行了Activity的finish方法,被延迟的消息会在被处理之前存在于主线程消息队列中10分钟,而这个消息中又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的SampleActivity的引用,所以这导致了SampleActivity无法回收,进行导致SampleActivity持有的很多资源都无法回收,这就是我们常说的内存泄露。
    ###注意:上面的new Runnable这里也是匿名内部类实现的,同样也会持有SampleActivity的引用,也会阻止SampleActivity被回收。
    要解决这种问题,思路就是避免使用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用[弱引用](http://droidyue.com/blog/2014/10/12/understanding-weakreference-in-java/)来处理。另外关于同样也需要将Runnable设置为静态的成员属性。注意:一个静态的匿名内部类实例不会持有外部类的引用。 修改后不会导致内存泄露的代码如下
    ```java
    public class SampleActivity extends Activity {
    
      /**
       * Instances of static inner classes do not hold an implicit
       * reference to their outer class.
       */
      private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;
    
        public MyHandler(SampleActivity activity) {
          mActivity = new WeakReference<SampleActivity>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
          SampleActivity activity = mActivity.get();
          if (activity != null) {
            // ...
          }
        }
      }
    
      private final MyHandler mHandler = new MyHandler(this);
    
      /**
       * Instances of anonymous classes do not hold an implicit
       * reference to their outer class when they are "static".
       */
      private static final Runnable sRunnable = new Runnable() {
          @Override
          public void run() { /* ... */ }
      };
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // Post a message and delay its execution for 10 minutes.
        mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
    
        // Go back to the previous Activity.
        finish();
      }
    }
    

    相关文章

      网友评论

        本文标题:1.Handler的运行机制,线程间是如何通信的?

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