美文网首页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