Android Handler解析

Android Handler解析

大概的将Handler实现的源码看来一遍.
将学到的和理解的Mark下来.

概述:
Android系统规定只能在UI线程更新界面.然后这一规定首先必须解决的问题就是如何在非UI线程更新界面.或许Hanlder就是这样提出的.而且Hanlder使用上用的最多的就是在这一方面.

一.Android设计之初为什么规定只能在UI线程更新界面

界面这东西如果在多线程的情况下也可以自由更新看起来是一件很糟糕的事情.但是我觉得多线程之下更新UI并不是什么特别糟糕的事情.更重要一个关键点在于更新界面的原子性和并发性.个人认为只要更新界面是原子的并且不存在并发即可.如果允许多线程更新界面,势必会每次跟新界面的时候都必须通过锁来实现原子性和阻止出现并发.

或许基于这样的原因,Android干脆只允许在UI线程更新界面.只允许在UI线程更新界面.我理解是只有一个线程在一个时刻更新界面.这样很简单粗暴的解决原子性和并发性的问题

二.引入UI线程更新UI带出的问题

既然系统在设计之初就规定只能在UI线程更新界面.那么你怎么也得设计一个东西给我在非UI线程更新界面吧?不然还玩什么东东呢?Handler就是这样冒出来.Android通过Handler,Looper实现非UI线程更新界面.(Looper是实现,Handler是辅助.感觉这样形容更贴切一点)

归纳:Handler机制因为Android系统规定只能在UI更新界面而引入的.

三.Handler简单使用

在接触Handler之前首先感受Handler使用.

最简单的用法:

Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

protected void onCreate(Bundle savedInstanceState) 
        {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_adapter);

       //这样用
       handler.sendEmptyMessage();

       //或者这样
       handler.post(new Runnable() {
            @Override
            public void run() {
             ....   
            }

       //或者这这样
       View view = findViewById(...);
       view.post(new Runnable() {
            @Override
            public void run() {
             ....   
            }
        });
        }

首先来看看new Handler()这个构造函数

public Handler() {
        //这里可以无视
        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 = null;
    }

通过Looper.myLooper()获取本线程所保存的Looper.Looper是何物?暂时知道有这东西存在即可.Android使用ThreadLocal使每一个线程的Looper都是不同的.即每一个线程各自保存一个独有的Looper实例.

接下来解析解释上面的代码.有一个背景是要知道的.主线程即UI线程默认系统会创建Looper.所以Handler构造函数调用Looper.myLooper()就是返回系统帮我们创建的Looper.

-这里的意思是,只要你在UI线程创建一个Handler实例,默认是绑定系统为UI线程创建的Looper.Handler绑定了哪一个Looper那么,在使用handler.sendEmptyMessage(…),handler.post(…)的时候就是哪一个Looper负责处理.
(注,Handler还有可以指定Looper的构造函数.Handler(Looper looper, Callback callback),Handler(Looper looper))

Handler的sendEmptyMessage,post函数其实就是投递消息到所绑定的Looper里面所带的消息队列.Looper看到自己的消息队列里面有信息就立即去处理.一直如此循环.

四.创建我们自己的Looper

Looper的创建没什么难度

new Thread()
        {
            @Override
            public void run() {
                Looper.prepare();

                Looper.loop();
                //这以后的代码都不会执行除非loop退出
                //Looper.loop();函数是一个死循环除非收到退出消息
            }
        }.start();

通过在子线程里调用Looper.prepare()为该子线程创建Looper.(非UI线程不会自动创建Looper.需要我们自己调用Looper.prepare()创建Looper)Looper.loop()开启消息循环.一旦调用loop()函数Looper就不断读取消息队列有消息则取出来并且处理.没有消息就阻塞该线程.这里要注意我们创建的Looper是以一个子线程的身份运行.

四.Handler,Looper为何物?

Handler是一个辅助我们将消息放到关联的Looper的帮组类.而Looper呢?很简单.带一个用于存放消息队列的死循环.

用子线程举例.子线程通过prepare()创建一个Looper.调用loop()进入死循环不断等等、处理消息.在我们创建Handler的时候通过捆绑Looper即可以完成Handler和Looper的关联.

五.源码分析

说了那么多在于到达源码讲解了!

1,UI线程在哪里创建Looper?

    public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy. We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

ActivityThread.main函数里面创建Looper.系统级别代码创建的.所以当我们在UI线程new Handler的时候Looper.myLooper()返回的是UI线程对应的Looper.当然UI线程的Looper是最牛逼的.在哪里都可以获取调用Looper.getMainLooper()即可.

2,在子线程怎么创建一个捆绑着UI线程Looper的handler?
new Handler(Looper.getMainLooper());
这时候此handler捆绑的是UI线程的Looper,对该handler发生消息都是都是在UI线程里面处理.也就实现了非UI线程更新界面啦!

3,消息如何流转?

A.handler.sendEmptyMessage(…)/handler.post(…)

 public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

sendEmptyMessage会调用sendEmptyMessageDelayed

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

sendEmptyMessageDelayed会调用sendMessageDelayed

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

sendMessageDelayed会调用sendMessageAtTime

关键点就在sendMessageAtTime
sendMessageAtTime做了一件很简单的事情.把消息插入到queue.即该handler绑定的队列中(MessageQueue).很简单吧?Handler就是一个辅助类.辅助你把消息插入该handler所绑定的Looper的队列.接下来就是从源码分析Looper怎么应该有消息的到来.

 public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
    }

此时此刻handler对于的Looper应该是在死循环中等的消息的到来.首先看看Looper.loop()函数的源码.


    /** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the 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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        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
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            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.recycle();
        }
    }

关注这一句Message msg = queue.next()即可.queue.next()会阻塞,直至有消息返回.然后就是这样一句msg.target.dispatchMessage(msg)
啥调用msg.target?
target为何物?
看会sendMessageAtTime就知道msg.target里面存放的是Handler的实例

整个过程就是一个线程通过Handler投递消息到Looper.Looper通过获取消息并且回调Handler实例的dispatchMessage()函数.来一个图更直接明了.
Android Handler解析_第1张图片
可以看到如果捆绑了XThread的Looper那么通过handler投递消息以后都是XThread处理该消息.即完成一次线程的切换.如果XThread是UI线程那么就可以实现在非UI线程更新界面了.

最后附上dispatchMessage函数.

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

这里要清楚说明.消息处理有三个阶段.
首先如果msg内部有callback那么callback函数会处理消息.callback函数处理完就完了.(callback在handler.post函数赋值)
然后如果在创建实例的时候调用Handler(Callback callback)函数传递callback.那么callback函数会截取一次.当callback函数返回true.父类的handleMessage()不会给调用.

  public interface Callback {
        public boolean handleMessage(Message msg);
    }

最后没有传递Callback,或者Callback返回false的时候.调用父类的handleMessage()函数处理消息.

参考资料

1: 深入理解Android 卷I
2: Android开发艺术探索
3: Android面试常客Handler详解 - http://www.imooc.com/view/267

你可能感兴趣的:(多线程,android,UI,界面)