美文网首页Android技术知识程序员Android知识
Android 你需要掌握的知识(四)

Android 你需要掌握的知识(四)

作者: Destiny_ZRJ | 来源:发表于2017-09-21 13:11 被阅读0次

    目录

    Handler.png

    一.Handler

    一.什么是Handler

    Handler是android 机制的一个上层接口,Handler通过发送和处理Message和Runnable对象来关联相对应线程的MessageQueue。
    1.可以让对应的Message和Runnable在未来的某个时间点进行相应处理
    2.让想要处理的耗时操作放在子线程。让更新UI的操作放在主线程

    (在很多应用场景中需要做一些耗时操作,当这些耗时操作完成后,需要在UI上进行相应的改变。然而耗时操作是在子线程中进行的,android的线程是不安全的,不能在子线程中更新UI,android引入了handler机制,通过handler发送消息机制来通知主线程更新UI)

    二.Handler的使用方法

    1.post(runnable)

    public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    private static Message getPostMessage(Runnable r) {
        //获取一个message,把Runnable封装成了一个Message对象
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    //delayMillis为延误时间,上一层传值为0
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    
    
     public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, uptimeMillis);
        }
    

    在源码中,可以看到post(runnable)方法是把Runnable封装成了一个Message对象,并且将Runnable对象设置给了Message对象的callback字段,最后将Message对象插入消息队列。
    再来重点看sendMessage方法

    2.sendMessage(message)

      public final boolean sendMessage(Message msg)
        {
            return sendMessageDelayed(msg, 0);
        }
    

    看源码和post方法一样同样调用了sendMessageDelayed(msg,time)方法。将消息发送到MessageQueue中。
    看一下使用方法:
    1.首先要创建一个Handler对象,复写handleMessage方法,根据Message what的值进行不同的处理操作。

       private Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case 1:
                        //在这里可以进行UI操作
                        text.setText("你好");
                        break;
                    default:
                        break;
                }
    
            }
        };
    

    2.创建Message对象,可以通过Message what来进行赋值,设置Message所携带的数据,然后把Message放到Handler中,通过handler.sendMessage方法,将Message传入Handler中,然后通过handleMessage方法进行UI线程的处理

            new Thread(new Runnable() {
                @Override
                public void run() {
                    Message message = new Message();
                    message.what = 1;
                    handler.sendMessage(message);
                }
            }).start();
    

    三.Handler机制的原理

    架构图.png

    Looper:是每一个线程中所独有的,用来为一个线程开启一个消息循环,它通过它的loop()方法读取它下面MessageQueue的消息,读取到消息后把消息发送给Handler来进行处理。
    MessageQueue:是一个消息队列,它是一个先进先出的方式来管理的Message,在创建Looper的时候,其实它已经创建了MessageQueue,所以创建Looper的时候,Looper和MessageQueue已经关联到了一起。
    Message:是消息对象,它里面有很多参数(what,arg1,arg2,obj(可以传一些复杂的对象))
    Handler:它有两个作用,发送消息和处理消息,处理Looper发送过来的消息,Looper发送消息也是通过Handler来进行的,Handler发送消息不是漫无目的发送,它不能发送到其他线程,它只能发送到它相关线程的MessageQueue当中,而MessageQueue又是和Looper关联的,所以说Handler要发送消息必须有一个维护它的Looper,这时候就把Looper,MessageQueue,Handler关联到了一起

    我们看一下Handler的源码
    首先看一下Handler的构造方法

      public Handler(Callback callback, boolean async) {
            if (FIND_POTENTIAL_LEAKS) {
                final Class<? extends Handler> 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) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            //通过Looper创造了MessageQueue
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        } 
    

    从源码里可以看到在构造方法中 “mLooper = Looper.myLooper()” 创造了Looper,“mQueue = mLooper.mQueue”又通过Looper的成员变量创造了MessageQueue。在构造方法当中Handler已经和MessageQueue关联到了一起,而MessageQueue又是通过Looper来管理的。
    那么Looper是如何获取的呢?可以看下myLooper这个方法

        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
      /**
         * Return the Looper object associated with the current thread.  Returns
         * null if the calling thread is not associated with a Looper.
         */
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    
    

    可以看到是通过ThreadLocal 这个JAVA中的机制通过get()方法获取的。
    ThreadLocal 线程本地存储区(Thread Local Storage) 通过不同的线程访问同一个ThreadLocal,不管是它的set()方法还是get()方法,它们对ThreadLocal所做的读写操作仅限于各自线程的内部。也就是不同线程彼此不能访问对方的ThreadLocal。
    这就是Handler里为什么要通过ThreadLocal来保存Looper,这样它就可以使每一个线程有单独唯一的Looper。
    那么ThreadLocal 的set()方法在什么时候调用的呢?Handler机制在什么时候设置的ThreadLocal?

      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));
        }
    
       private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    

    可以看到在prepare()方法中“ sThreadLocal.set(new Looper(quitAllowed))”,就是它创建了一个Looper对象,并且把这个Looper对象设置给了ThreadLocal,保证了每一个线程Looper的唯一性。Looper内部维护一个MessageQueue。
    这时候整个的MessageQueue通过Looper跟线程关联上了,这样不同的线程就不能访问其他的消息队列了。

    现在已经创建好了Looper,创建好了MessageQueue,我们知道创建Handler的两种方法(post(runnable),sendMessage(message)),两种方法都需要通过开启Looper.loop()方法才能从MessageQueue中获取消息。消息循环的建立就是通过Looper.loop()方法。我们来看一下loop()这个方法,

        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;
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {  // 死循环
                Message msg = queue.next(); // 获取消息(might block 没消息会阻塞)
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                final long traceTag = me.mTraceTag;
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
                try {
                   //处理消息
                    msg.target.dispatchMessage(msg);   
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                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();
            }
        }
    

    通过代码可以看出,loop()方法实际上就是创建了一个for的死循环,然后从消息队列中逐个获取消息(当没有消息时,queue.next()会阻塞,直到有消息才继续执行),最后处理消息的过程。
    总结一下:Looper通过Looper.prepare()来创建Looper对象,保存在ThreadLocal中,然后通过Looper.loop()开启循环,来进行消息的分发

      /*package*/ Handler target;
        
      /*package*/ Runnable callback;
        
      // sometimes we store linked lists of these things
      /*package*/ Message next;
    

    消息循环当中,最终调用“msg.target.dispatchMessage(msg) ”
    从Message源码中可以看到target其实就是一个Handler,所以其实最后还是通过Handler将消息传给了消息队列,而消息队列又将消息分发给Handler来处理。这就是Handler的两个作用,一个是发送消息,一个是处理消息
    然后看一下dispatchMessage(msg)方法的源码

      /**
         * Subclasses must implement this to receive messages.
         */
        public void handleMessage(Message msg) {
        }
        
        /**
         * Handle system messages here.
         */
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
     private static void handleCallback(Message message) {
            message.callback.run();
        }
    

    从源码中可以看到dispatchMessage(msg)方法其实只是一个分发的方法,首先会判断msg.callback是否为空,(.callback其实是一个Runnable对象,在上面的使用方法中有写)如果不为空,就执行handleCallback方法,而handleCallback方法里面其实调用的就是线程的run()方法。如果为空,将执行handleMessage来进行处理。

    图片来源于网络.png

    总结:如图,创建一个Looper,创建Looper的同时Looper内部又创建了一个MessageQueue,而在创建Handler的时候取出当前线程的Looper,又通过Looper对象获取到MessageQueue,然后Handler在该MessageQueue中添加一条Message。Looper开启一个循环,不断的从MessageQueue中获取消息,然后从MessageQueue头部获取的Message以Message.target的形式交给Handler,通过dispatchMessage分发由handleMessage或者调用callback进行处理,处理完成之后还是会发送消息到Looper中不断的又开启循环从MessageQueue中获取消息。

    四.Handler引起的内存泄漏以及解决办法

    看一下刚才写的Handler用法


    内存泄漏.png

    我们可以看到提示上说代码可能会发生内存泄漏。

    那么内存泄漏是如何发生的呢:在Java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用,通过图上代码可以看到我们创建的Handler不是静态内部类,所以它会隐匿的持有Handler 这个Activity的引用。如果Activity被回收了,而在Handler的内部还在做一些耗时操作从而导致Handler没有被释放,所以Handler持有的Activity的引用也不能被释放,导致Activity无法被GC回收,就会造成内存泄漏。

    如何解决内存泄漏:
    1.把Handler改成静态内部类

      private MyHandler myHandler = new MyHandler();
        private static class MyHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        }
    

    2.Handler内部持有外部Actvity弱引用

     private MyHandler myHandler = new MyHandler(MainActivity.this);
        private static class MyHandler extends Handler {
            //持有弱引用MainActivity,GC回收时会被回收掉.
            WeakReference<MainActivity>  weakReference;
    
            public MyHandler(MainActivity mainActivity){
                weakReference = new WeakReference<MainActivity>(mainActivity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (weakReference.get()!=null){
                    //执行业务逻辑
                    weakReference.get().text.setText("你好");
                }else {
                    Log.e("","MainActivity已经被销毁");
                }
    
            }
        }
    

    3.在Activity生命周期的onDestroy()方法中调用Handler的removeCallbacksAndMessages()方法。

       @Override
        protected void onDestroy() {
            super.onDestroy();
            handler.removeCallbacksAndMessages(null);
        }
    

    相关文章

      网友评论

        本文标题:Android 你需要掌握的知识(四)

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