Android基础之Handler理解

1.Handler: 用于线程之间的通信,是一种异步回调机制.

2.Handler内部实现主要涉及到Looper,Message,MessageQueue以及Thread类.

   Handler: 发送消息和接收处理消息

   Message: 消息载体

   MessageQueue: 消息队列,存放发送的消息,内部使用单链表结构.

   Looper: 轮询器,从消息队列里面循环获取消息,有消息立即处理,没有消息就会阻塞,等待消息到来.

   Thread :线程,每一个线程最多只允许一个Looper.

3.流程可以总结为:

       调用Looper.prepare方法,创建Looper对象,Looper的构造方法中会创建一个MessageQueue,同时将looper绑定   到当前线程. 

      创建Handler的时候,其构造方法里会获取到当前线程的Looper对象,并获取到该looper绑定的消息队列,messagequeue;当调用sendmessage()的时候其底层实际上调用了enqueuemessage方法,该方法主要做了两步,第一步是msg.target= this,也就是指向当前handler;第二步是,调用了ueue.enqueueMessage(msg, uptimeMillis);方法,将消息插入到当前线程的消息队列.

        Looper.loop方法,会循环的从messagequeue读取消息,并调用msg.target.dispatchmessage(msg)方法分发消息.,上面可以知道msg.target其实就是当前线程的handler,所以其实就是调用了handler的dispatchmessage方法,交给handlemessag处理.

现在我们来思考几个问题

1.上面说了,轮询器去读取消息是循环读取,这是一个死循环,为什么我们的程序没有卡死呢?

答; ui线程,也就是我们说的主线程,也被称作ActivityThread,默认初始化了Looper,这也是我们可以使用handler的原因.我们知道线程是一段可执行的代码,代码执行完毕,线程生命周期也就结束了,步入死亡;ui线程也就是main线程如果也是这样执行结束就死亡肯定不是我们期望的结果,所以如果我们想要将线程一直执行下午,那么最简单的做法就是让代码一直执行下去,而死循环便能保证不会退出.当然这里不是简单的死循环,没有消息的时候会进行休眠,让出cpu,这样我们的程序就不会卡死.;这里既然是死循环,那么其他事务是如何处理的呢?其实我们看源码会知道,有一个thread.attach(false),创建了新的线程去处理.真正会卡死主线程的操作是在回调方法 onCreate/onStart/onResume 等操作时间过长,会导致掉帧,甚至发生 ANR,looper.loop 本身不会导致应用卡死。

2.Handler是如何 能线程切换呢?

答: 首先我们要知道线程是共享资源的,所以 Handler 处理不同线程问题就只要注意异步情况即可。Handler 创建的时候会采用当前线程的 Looper 来构造消息循环系统,Looper 在哪个线程创建,就跟哪个线程绑定,并且Handler 是在他关联的 Looper 对应的线程中处理消息的.

3.系统为什么不允许在子线程中访问UI?

这是因为 Android 的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么为什么系统不对UI控件的访问加上锁机制呢?缺点有两个:

①首先加上锁机制会让UI访问的逻辑变得复杂

②锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

所以最简单且高效的方法就是采用单线程模型来处理UI操作。

那么子线程一定不能更新UI吗?

答:当然是可以的,我们可以使用Handler来完成;也可以使用 Activity 对象的 runOnUiThread 方法。

4.那为什么我们在子线程中使用toast和dialog会报错呢?

答:直接调用会报错,提示需要使用loopprepare和looper.loop方法(但是不建议这么做,因为它会使线程无法执行结束,导致内存泄露)

5.如何处理Handler 使用不当导致的内存泄露?

答: 上文内存泄漏的我们讲过,使用handler可能会导致内存泄漏,譬如,延迟发送消息,Activity销毁但是消息还没执行,另外就是handler若是使用到了context传入了Actiity也会导致出现内存泄漏的问题.

解决办法: 1.有延时消息,要在 Activity 销毁的时候移除 Messages

                2. 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者 Activity 使用弱引用。.

本文参考文章: https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650241824&idx=1&sn=aa4be9ab94828bfff4ee9108a85a0f0b&chksm=88638a4fbf1403594b7e2e0fc04a30ced3ec7819cc891b4e872b52e29692b9f7a11a8658a97d&mpshare=1&scene=23&srcid=1117IBUFS6V6EDHPf0emAMs3#rd


有兴趣的话可以看下源码;

private static void prepare(booleanquitAllowed){// 创建一个Looper对象

         if(sThreadLocal.get() !=null) {// 每个线程只允许有一个looper

                    thrownewRuntimeException("Only one Looper may be created per thread"); 

            } 

            sThreadLocal.set(newLooper(quitAllowed)); 

 }

// 看下Looper的构造方法

private   Looper(boolean   quitAllowed){  

      mQueue =newMessageQueue(quitAllowed);// new了一个消息队列

       mThread = Thread.currentThread(); // 绑定到当前线程

 }

//  Looper.loop()方法,循环从消息队列中读取消息,并且分发消息

public static void loop(){

          //myLooper方法会从ThreadLocal中获取到当前线程的Looper

           finalLooper me = myLooper();

                if(me ==null) {// 若获取不到looper对象

                       thrownewRuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

                }

          final   MessageQueue queue = me.mQueue; 

           Binder.clearCallingIdentity();

          final  long  ident = Binder.clearCallingIdentity();

         for(;;) {//循环的去MessageQueue中读取消息

                   Message msg = queue.next();

                       if(msg ==null) {

                         return; 

                  } 

                  Printer logging = me.mLogging;

        if(logging !=null) { 

                logging.println(">>>>> Dispatching to "+ msg.target +" "+ msg.callback +": "+ msg.what); 

 }//调用发送该消息的Handler的dispatchMessage方法处理该消息 

  //这里的msg.target == Handler,是在Handler发送消息的时候进行赋值的

           msg.target.dispatchMessage(msg);

                 if(logging !=null) { 

                 logging.println("<<<<< Finished to "+ msg.target +" "+ msg.callback);

                  }

                final        long    newIdent = Binder.clearCallingIdentity();

                 if(ident != newIdent) { 

           Log.wtf(TAG,"Thread identity changed from 0x"+ Long.toHexString(ident) +" to 0x"+             Long.toHexString(newIdent) +" while dispatching to "+ msg.target.getClass().getName() +" "+ msg.callback +" what="+ msg.what);

                        } 

             msg.recycleUnchecked();

}

Handler的构造方法

public    Handler(

                Callback callback,booleanasync){

                     if(FIND_POTENTIAL_LEAKS) {

                       finalClass klass = getClass();

                              if((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) ==0) { 

                  Log.w(TAG,"The following Handler class should be static or leaks might occur: "+       klass.getCanonicalName()); 

             } 

               }

               //获取当前线程中的Looper 

            mLooper = Looper.myLooper();

             if(mLooper ==null) {

           //如果当前线程没有Looper就会抛出异常。

                       thrownewRuntimeException("Can't create handler inside thread that has not called Looper.prepare()"); 

      }

             //获取Looper中的MessageQueue,可以看出,Looper中为每个线程维护了一个Message

              mQueue = mLooper.mQueue;

                   mCallback = callback; 

              mAsynchronous = async;

}


//Handler发送消息的方法,追踪senndmessage到最底层的方法就是enqueueMessage。

private boolean enqueueMessage(MessageQueue queue, Message msg,longuptimeMillis){

         msg.target =this;

         if(mAsynchronous) { 

      msg.setAsynchronous(true);

 }

    returnqueue.enqueueMessage(msg, uptimeMillis);

}

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

推荐阅读更多精彩内容