Android Handler消息机制原理

Android中使用 Handler场景

在开发中更新我们刷新视图都需要在主线程中更新,子线程是不支持更新视图操作的。所以当我们做一些耗时操作的时候可以不能马上得到反馈刷新UI,比如下载文件或者下载图片这些操作都比较耗时,我们一般会重新创建一个子线程异步处理耗时操作,这样就不会堵塞主线程导致卡顿的情况。异步处理成功后如果这个时候我们需要更新视图操作就不能直接更新了,这个时候Handler就起到了作用。我们可以用Handler Looper MessageQueue这套异步消息处理机制来处理这种情况,线程中发送消息通知,主线程来处理消息刷新视图。

@Override

      protected void onCreate(@Nullable Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          new Thread(new Runnable() {
              @Override
              public void run() {
                  //耗时的下载代码...
                  //发送消息
                  mHandler.sendMessage(Message.obtain());
              }
          }).start();
      }


      static Handler mHandler = new Handler() {
          @Override
          public void dispatchMessage(Message msg) {
              super.dispatchMessage(msg);
              //处理消息刷新视图
          }
      };

Handler使用的生产消费者设计模式

生产消费者模式就是类似于生活中寄快递一样,寄东西的人可以视为生产者,而快递员就是消息缓存区、快递员负责全部快递的维护和派发、只要有还有快递快递员就是一直送到没有为止,收件人就是消费者负责接收快递员的快件。可以看到整个过程发件人不需要关心收件人的具体情况,只需要把收件人的地址写对就行。而快递员也不需要关心发件人和收件人的信息,快递员只负责收快递按快递地址送到到收件人手里。收件人只需要等待快递的送达就好了。

优点

  • 低耦合
    为什么不直接让消费者调用生产者的某个方法?如果这样直接调用必然会产生相互依赖的情况也就是耦合,如果以后生产者和消费者某一方变化都有可能会影响到对方,但是如果二者直接不直接依赖而是通过缓冲区来交互,这样耦合性就大大减低了。

  • 支持并发
    生产者可以放心的产生数据直接扔给消息缓冲区,而不用等待消费者那边是否已经处理完了消息,而导致生产者等待状态(生产者消息堵塞),这样就可以不用依赖消费者的处理速度了,互相独立。

  • 自由发挥
    这种模式还有一好处就是,如果生产者生产的数据速度过快,而消费者那边处理的比较慢,那么这个时候消息都会存在于缓冲区。这样生产者就能慢慢的消费这些数据了,所以定为自由发挥。

流程图

Handler Looper MessageQueue 的职责

简要流程图

MessageQueue 就是设计模式中的缓冲区,它负责接收生产者发送过来的数据先进先出的队列形式,保存着所有消息。在UI Thread中通过looper 不断从MessageQueue 取出消息在执行任务。

Looper 的主要工作就是维护MessageQueque中的消息队列,它负责从MessageQueue中取出要执行的消息任务,先判断Looper是否为null,不为null就循环状态不断从MessageQueue中取出消息,然后通过dispatchMessage派发出去就行处理。

    public static void loop() {
           final Looper me = myLooper();
           if (me == null) {
               throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
           }
           final MessageQueue queue = me.mQueue;
           .....
           for (;;) {
               Message msg = queue.next(); // might block
               if (msg == null) {
                   // No message indicates that the message queue is quitting.
                   return;
               }
                ........
               try {
                   msg.target.dispatchMessage(msg);
               } finally {
                   if (traceTag != 0) {
                       Trace.traceEnd(traceTag);
                   }
               }
                .........
               msg.recycleUnchecked();
           }
       }

每个线程只能有一个Looper对象,而且是通过ThreadLocal来存放,其他线程无法访问当前的Looper。

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

Looper可以让一个普通线程具有消息循环的能力,这是源码给出的一段示例。

       class LooperThread extends Thread {
            public Handler mHandler;

                public void run() {
                    Looper.prepare();

                   mHandler = new Handler() {
                        public void handleMessage(Message msg) {
                           // process incoming messages here
                        }
                    };

                    Looper.loop();
                }
        }

每个application都默认拥有一个Looper对象注释可以看到。Framework会通过JNI调用如下二个方法创建一个MainLooper,所以说UI线程默认就有一个Looper对象,可通过Looper.myLooper()方法获取到。

      /**
         * Initialize the current thread as a looper, marking it as an
         * application's main looper. The main looper for your application
         * is created by the Android environment, so you should never need
         * to call this function yourself.  See also: {@link #prepare()}
         */
        public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }

        /**
         * Returns the application's main looper, which lives in the main thread of the application.
         */
        public static Looper getMainLooper() {
            synchronized (Looper.class) {
                return sMainLooper;
            }
        }

常用的方法 prepare()创建一个looper对象,在其他线程中想具有消息循环功能这个方法就必须调用。

      public static void prepare() {
             prepare(true);
         }


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

开始循环取出消息

  public static void loop() {}

Handler 是负责把Message压入Queue中,还负责处理Message。Handler工作必须依赖Looper才行,没有Looper对象会抛出RuntimeException异常,不管是post sendMessage 还是延时的消息发送,最终都会到enqueueMessage方法中把消息传到缓冲区等待Looper处理。


     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;
            mCallback = callback;
            mAsynchronous = async;
        }

Handler导致的内存泄漏问题

下面这段代码是一个内部类,在Java中内部类或者匿名内部类都会隐私的持有外部类对象,而在Android中使用Handler的一般都是Activity,这就导致如果handler还在执行中而actiivty finsh掉,activity就不能被正常销毁回收。进而GC的时候就导致JVM不能回收Activity,有可能多次操作后就OOM了。

Handler mHandler = new Handler() {
         @Override
         public void dispatchMessage(Message msg) {
             super.dispatchMessage(msg);
             //处理消息刷新视图
         }
     };

解决办法
1.把内部类换成static, static不会隐式持有外部对象。

static Handler mHandler = new Handler() {
             @Override
             public void dispatchMessage(Message msg) {
                 super.dispatchMessage(msg);
                 //处理消息刷新视图
             }
         };

由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用

2.一般Handler导致的内存泄漏都是因为,消息处理是异步的。finsh的时候消息还在处理等待状态,这个时候可以在activity finsh的时候把handler移除掉,调用removeCallbacks方法移除。
3.关闭Activity的时候停掉你的后台线程。

总结

Handler 消息处理和发送的角色, 主要有二个作用 1.发送消息 2.处理消息
Looper 消息轮循器 looper方法里面是一个死循环,它不断从MessageQueue中取出消息,直到为null为止。
MessageQueue 消息队列 保存着全部消息的一个队列

在其他线程中使用Looper,可以看到一个Looper对象可以有多个Handler对象。

new Thread(new Runnable() {
             @Override
             public void run() {
                 Looper.prepare();
                 Handler handler1=new Handler(){
                     @Override
                     public void dispatchMessage(Message msg) {
                         Log.i(TAG, System.currentTimeMillis()+"=1");
                     }
                 };
                 Handler handler2=new Handler(){
                     @Override
                     public void dispatchMessage(Message msg) {
                         Log.i(TAG, System.currentTimeMillis()+"=2");
                         Looper.myLooper()
                     }
                 };
                 handler1.sendMessage(Message.obtain());
                 Looper.loop();
                 handler1.sendMessage(Message.obtain());
                 handler1.sendMessage(Message.obtain());
                 handler1.sendMessage(Message.obtain());
                 handler2.sendMessage(Message.obtain());

             }
         }).start();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容