美文网首页Android
Handler的使用及实现原理分析

Handler的使用及实现原理分析

作者: gstansen | 来源:发表于2017-06-11 15:26 被阅读29次

    why should you know

    1.面试的经典问题
    2.知其然知其所以然

    相关名词解释

    主线程即UI线程

    Handler的简单使用

    情境一:主线程使用handler
    public class MainActivity extends AppCompatActivity {
        private MyHandler myHandler = new MyHandler(this);
        public TextView textView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            textView = (TextView)findViewById(R.id.text);
            doSth();
        }
        private void doSth(){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //模拟耗时操作
                        Thread.sleep(1000);
                        //将耗时操作的结果包装成message
                        Message message = new Message();
                        message.obj = "do something";
                        //发送message
                        myHandler.sendMessage(message);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
       private static class MyHandler extends Handler{
            //弱引用,防止内存泄漏
            private final WeakReference<MainActivity> mainActivityWeakReference;
    
            public MyHandler(MainActivity activity){
                mainActivityWeakReference = new WeakReference<>(activity);
            }
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                MainActivity mainActivity = mainActivityWeakReference.get();
                mainActivity.textView.setText((String)msg.obj);
    
                Log.d("handler_message",(String) msg.obj);
                Log.d("thread name",Thread.currentThread().getName());
            }
        }
    }
    

    输出结果

    D/handler_message: do something
    D/thread name: main   所以handleMessage里可以直接调用  mainActivity.textView.setText((String)msg.obj);
    
    

    这段代码模拟了一个简单的异步业务流程

    大致的调用流程就是在新线程中执行了一个耗时操作,然后把该结果塞给message,handler将发送这个message,最终通过handleMessage回调给主线程,然后在主线程中处理这个message,于是我们完成了一次的消息的收发(异步消息)。

    情境二:异步线程中使用handler
    public class MainActivity extends AppCompatActivity {
        private Handler mThreadHandler;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            doSthInOtherThread();
        }
      /**
         * 子线程使用handler
         */
     private void doSthInOtherThread(){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.d("thread A","thread  A run ");
                    Log.d("thread A name is",Thread.currentThread().getName());
                    Looper.prepare();
                    mThreadHandler = new Handler(){
                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            String message = (String)msg.obj;
                            Log.d("thread handler",message);
                            Log.d("thread name",Thread.currentThread().getName());
                        }
                    };
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Log.d("thread B","thread B run");
                            Log.d("thread B name is",Thread.currentThread().getName());
                            try {
                                //模拟耗时操作
                                Thread.sleep(1000);
                                Message message = new Message();
                                message.obj ="message from thread B";
                                mThreadHandler.sendMessage(message);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                    Looper.loop();
                }
            }).start();
        }
    }
    

    输出结果

    D/thread A: thread  A run 
    D/thread A name is: Thread-2
    D/thread B: thread B run
    D/thread B name is: Thread-3
    D/thread handler: message from thread B
    D/thread name: Thread-2
    这里的handleMessage不是在主线程中被回调的所以不能访问主线程中的控件
    

    分析

    虽然情境一的场景出现的更为频繁,但是情境二更具一般性,所以这里会具体分析情境二。

    情境二场景分析:情镜二建立了两个线程A和B;A中处理消息,B中执行耗时操作并发送消息;

    Handler消息机制所涉及的几个重要的类:Handler,MessageQueue,Looper,三者关系如下图所示

    handler.png

    (图片来源:http://vjson.com/wordpress/handler-looper原理分析.html ,不愿画图网上找了一张图)

    这张图较好的反映了上述三者的关系
    Handler:负责将消息(已经在异步线程中处理好的消息 eg:http请求返回的结果)发送至looper中的messagequeue,然后在handlemessage中处理这个消息

    MessageQueue:消息队列,负责存储message,这里MessageQueue是个单链表,先进入的消息将先被处理,然后被移除,下文会结合源码分析(上图没有反映出来)

    Looper:消息循环传递者,负责维护消息队列并不断从MessageQueue中取出消息传递给Handler,通过Looper.loop取消息,这个方法内有个for循环,并且是无限循环的,只有你身体被掏空了才会停 (>__< 当消息队列里没有消息了就会return了)

    整体的调用流程用文字来描述是这样的:
    你在线程A中要执行一个业务,在A中开启了一个线程B具体去执行这个耗时业务,在线程B得到操作的结果后,将这个结果包装成一条message,通过Handler发送至MessageQueue中,Looper在线程A中调用了loop方法后则会不停的从MessageQueue中取出消息交给Handler处理,最终会回调Handler的handleMessage方法,这样线程就又切换回A中了,A则可以对这个message进行处理,如果这里A是主线程的话,也就是上述的情境一,你就可以将message通过主线程显示出来了。

    Magic:
    线程是怎么切换的?
    答:这里先大致解释下,当Looper的loop方法被调用的时候,Looper会从MessageQueue中取出单链表中第一个msg(Message对象),之后会执行msg.target.dispatchMessage(msg),这个msg中有个target对象,然后target对象里有个dispatchMessage方法,dispatchMessage方法又会调用handleMessage,这时候机智的你肯定意识到了这个target其实就是Handler对象,其实这还不是重点,线程切换的重点在于loop方法是在线程A中执行的,所以即便消息是在线程B产生的,由于loop方法执行在线程A中,所以handleMessage方法也就执行在了线程A中,如果A是主线程,handleMessage方法也就回调在主线程了。

    其实知道这些,一般的面试也就够了,但最好还是看下源码!So , let`s read the fucking source code.

    一切从Handler创建开始说起

    Handler的创建总会调用以下两个方法中的一个

     方式1
     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());
                }
            }
    
            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;
        }
     方式2
     public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    两者的差异在于是否通过传递Looper对象创建Handler,但无论哪种方式Handler对象的创建都需要Looper对象,那么我们重点看一下方式1的创建过程。

     mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
    

    这里通过 Looper.myLooper()创建了一个Looper实例,然后判断这个mLooper是否为null,如果为null则抛出我们熟悉的异常

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

    所以我们在子线程中创建Handler之前一定要先调用Looper.prepare方法,然而我们在主线程中初始化Handler时并没有调用这个方法啊,其实在ActivityThread的main方法调用了prepareMainLooper方法,prepareMainLooper中也是调用prepare方法的,所以主线程中并不需要我们去调用Looper.prepare方法,要获取到主线程的Looper对象,你只需要调用Looper的getMainLooper方法即可。

    接着我们再来看看Looper.prepare方法里干了什么吧

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

    它会从ThreadLocal对象中查看是否已经有Looper的实例,如果已经有则又会抛出我们熟悉的异常了。ThreadLocal可以创建线程局部变量,为每个线程提供该变量独立的副本而不相互影响,这里只需要知道其提供Looper对象,并保证一个单独线程只存在一个Looper对象,这里不对其原理做详细分析。

    Only one Looper may be created per thread
    

    这意味着一个线程只能有一个Looper 对象,Looper.prepare方法只能在同一个线程中调用一次,之后ThreadLocal对象会将new出来的Looper对象set进去。

    我们再转回Handler构造函数中的Looper.myLooper方法中

    public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    

    其实就是调用ThreadLocal的get方法获取刚刚set进去的Looper对象

    那么MessageQueue又是怎么和Looper对象关联的呢

     private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    

    可以看到调用Looper的构造函数时会创建MessageQueue对象,并且Looper持有这个MessageQueue,并在Handler构造函数将MessageQueue交由Handler持有。

    好了,现在需要的对象都已经实例化了,再来看看Handler,MessageQueue,Looper是怎么处理消息的吧
    首先我们需要Handler的sendMessage方法,最终会调用 sendMessageAtTime

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

    然后调用了Handler的enqueueMessage方法

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    msg.target = this,可以看到Handler把自己传递进了Message中所以Message持有Handler对象,接着我们再看看MessageQueue的enqueueMessage方法

     boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
    
            synchronized (this) {
                if (mQuitting) {
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();
                    return false;
                }
    
                msg.markInUse();
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                //mark 1 如果链表中没有元素则将该消息置为表头
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    //mark 2 如果链表中有元素则将该消息添加至表尾
                    // Inserted within the middle of the queue.  Usually we don't have to wake
                    // up the event queue unless there is a barrier at the head of the queue
                    // and the message is the earliest asynchronous message in the queue.
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    这里的意思就是将新的message添加至链表的表尾,如果链表中没有元素则置为表头,这样消息就插入至MessageQueue中了。

    现在MessageQueue已经有消息了,是时候取出消息了,前文说了Looper会调用loop方法不断从MessageQueue中取出消息,这里为了更好的理解loop方法简化了其代码

      public static void loop() {
            for (;;) {
                //取出表头的message
                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 {
                }
            }
        }
    

    从代码中可以看到其不断从MessageQueue中获取位于表头的消息( queue.next()取的是表头元素),然后调用消息中的target的dispatchMessage方法,来看看dispatchMessage方法

    public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    这个其实是Handler中的方法,最后其实还是调用了handleMessage方法,但是之前有个判断,当msg.callback不为null才会调用handleMessage,其实这个callback就是Runnable接口,当你调用Handler的post方法发送消息而不是通过sendMessage发送消息时,它最后就会调用Runnable中的run方法,而不是handleMessage。这里稍微解释一下,post方式中的Runnable接口会被包装成一个Message,最终也会调用enqueueMessage方法,所以post也可以发送消息。

    到这里整个过程也就分析完了,拿到消息的你可以爱干啥干啥了!

    Tips:

    1.当你在主线程使用Handler时要注意内存泄漏的情况,可以在Handler中用弱引用的Activity
    2.主线程使用Handler时使用post方式和sendMessage方式时,post的run方法或者Handler的handleMessage方法都将在主线程回调
    3.loop方法被调用的线程就是run方法,handleMessage方法被回调线程(2是特例,loop方法在主线程调用)

    总结

    虽然现在做异步时可能更多使用RxJava了,但是Handler还是要掌握的。
    Handler其实不只可以做异步通信,它更多的是作为一种消息机制存在,甚至可以利用其设计一套事件总线方案,Android源码中有很多地方都使用到了Handler,所以了解它也有益于我们对于Android相关源码的阅读。

    DSC_0203.jpg

    相关文章

      网友评论

        本文标题:Handler的使用及实现原理分析

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