Android基础之Handler理解

作者: 正阳Android | 来源:发表于2017-11-17 11:29 被阅读4次

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

}

相关文章

网友评论

    本文标题:Android基础之Handler理解

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