跨进程通信Android开发Android知识

Android消息机制篇——别人所知道和不知道的Handler

2017-03-10  本文已影响84人  黑白咖

前言:穷则变,变则通,通则久。——《周易》

Handler一般用于线程间的通信,通常项目中的异步实现都是基于Handler来实现的,前面在学习IntentService的时候已经说过了。今天主要是要理清一下Handler——Looper——MessageQueue之间的业务往来。

Handler构造方法

public Handler()
public Handler(Callback callback)
public Handler(Looper looper)
public Handler(Looper looper, Callback callback)
@hide public Handler(Callback callback, boolean async)
@hide public Handler(Looper looper, Callback callback, boolean async)

Handler的构造方法分为两种

#使用默认的Looper
Handler(Callback callback, boolean async) 
#使用自定义的Looper进行初始化
Handler(Looper looper, Callback callback, boolean async) 

async会为所有的Message和Runnable标记setAsynchronous(boolean),不过所有的构造函数都会给这个值传递false。

通常情况下,我们都会使用无参的构造函数,然后重写里面的handleMessage实现方法。我们在使用Handler的时候,相信每一位都见到过以下的警告信息

Eclipse

或者

Android Studio

这个Handler类应该设置为静态,否则可能会发生泄漏(只是可能而已,并不是说那么容易就发生内存泄漏的,事实上很多时候都不会出现这种内存泄漏的问题,不然我们的APP早一百年前就崩溃了)。接下来研究怎么才能去掉这个警告,我们来仔细研究Handler的这个使用默认Looper的构造方法

    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) {
                //警告 Handler类应该是静态的,否则可能会发生内存泄漏
                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;
    }

可以看到当我们把这个类定义成匿名,成员,局部类的时候,并且这个类没有static修饰符。那么就会抛出这个警告。那么去掉这个警告也就很简单了,添加static
在这个构造函数中,通过Looper.myLooper()获取当前线程所关联的Looper对象,同时也获取到Looper内的MessageQueue对象,至此,消息传递的两个重要要素MessageQueueLooper就初始化完成了。

    //获取当前线程关联的Looper对象
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

另一种形式的构造函数因为太过简单,就没什么好说的了

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

对于这种构造方法,我们来看一下用法即可,使用自定义的Looper,要确保这个Looper已经prepare才行,真的是书读百遍其义自见,关于Looper我想很多人并不是那么熟悉,所以真正懂得Handler的人可以好好鞭策一下那些面试者啊

public class HandlerThread extends Thread {
Handler handler;
    @Override
    public void run() {
        Looper.prepare();
        handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                       // do something
                    }
                };
        Looper.loop();
    }
}

关于自定义Looper的Handler,我想有两个问题,大家是没有注意的:
1、我们都知道Looper会调用loop()方法进行轮训,第一个问题就是在子线程创建的调用loop会阻塞线程,所以如果在loop方法后执行的语句,就需要等到loop跳出循环才继续执行。

2、第二个问题也是最难的问题,loop方法会阻塞线程,那么主线程的loop为什么不会阻塞线程呢?
首先我们的APP运行时会创建一个进程,而所有的线程的资源是共享的,线程的执行需要时间片,在这里,我们的loop既然是一个死循环,那么必定是需要一个线程来运行才不会阻塞主线程。我们的主线程一直都处于运行之前,不然的话就会出现所谓的ANR了,
所以结论一,主线程的loop必然也是创建了一个新的线程来运行的

这个主线程在哪里创建的呢?
事实上,会在进入死循环之前便创建了新binder线程,在代码ActivityThread.main()中:

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

        //创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread(); 

        //建立Binder通道 (创建新线程)
        thread.attach(false);

        Looper.loop(); //消息循环运行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程.
关于IPC部分的,不展开说明。

对比obtainMessage和obtainMessage(Object...)的重载方法

Handler调用obtainMessage()以及它的所有重载方法,都会调用对应的Message中的obtain()重载方法,然后返回一个Message对象,之后我们就可以对这个Message设置一些属性,通过handler把它发送出去。

public final Message obtainMessage()
public final Message obtainMessage(int what)
public final Message obtainMessage(int what, Object obj)
public final Message obtainMessage(int what, int arg1, int arg2)
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)

调用上述的方法,最终都会调用到Message类中的静态方法,我们取其中一个比较详细的来研究一下即可

    public static Message obtain(Handler h, int what, 
            int arg1, int arg2, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;

        return m;
    }

可以看到,Message会先调用无参的obtain()方法,然后获取到一个Message对象后,再根据所给的参数对这个Message对象进行赋值。我们在使用Handler的时候,肯定都听说过,用new Message()所得到的Message对象效率并不高,为什么这样说呢?因为Message内部有一个静态的消息链(链结构),可以从里面重用一些回收的Message(执行完的Message),这样就可以减去new Message()带来的开销:

    public static Message obtain() {
        // 同一时刻,只会有一个线程会访问sPool
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

如果我们想要获取一个Message对象,那么直接调用Message.obtain()方法即可。这样做可以稍微优化一点点性能。搞不好可能还真能优化,少new一些对象可以降低GC的频率。

发送消息

使用Handler发送消息,有很多种方法,大致分成两类

#Message类型
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)

#Runaable类型
public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
public final boolean postDelayed(Runnable r, long delayMillis)

不管是postxxxAtTime或者是sendxxxDelayed,实质上到最后都是调用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;
        }
        // 把Message对象添加到消息队列中
        return enqueueMessage(queue, msg, uptimeMillis);
    }

那么对于post方法,会先转化成对应sendMessage的方法,也就是说不管是Runnable对象还是Message对象,最终添加到MessageQueue的都是Message对象

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    private static Message getPostMessage(Runnable r, Object token) {
        Message m = Message.obtain();
        m.obj = token;
        m.callback = r;
        return m;
    }

而sendMessageAtTime实际上做的就是把消息添加到MessageQueue中

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 调用消息队列里面的方法,把消息插入到队列中
        return queue.enqueueMessage(msg, uptimeMillis);
    }

至此,我们的Message就添加到MessageQueue中去了,当然,有可能会添加失败的,如果这个队列处于正在退出的状态,那么就会释放掉这个Message,并且返回false。

有两个比较特殊的发送消息的方法

public final boolean postAtFrontOfQueue(Runnable r)
public final boolean sendMessageAtFrontOfQueue(Message msg)

很明显这是一种插队方法,发送的消息会添加到队列的头部

    public final boolean sendMessageAtFrontOfQueue(Message msg) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        // 因为时间为0,小于其他的Message,所以会先执行这个消息
        return enqueueMessage(queue, msg, 0);
    }

处理消息

有三种方法可以处理消息的,执行顺序由上往下依次是
1、直接执行Runnable的消息
2、实现Handler内的Callback接口,在构造Handler的时候传进去,如果返回false的话,那么继续执行第三个方法。
3、重写Handler内的handlerMessage方法

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 如果消息的Runnable对象不为空,调用它的run方法
            handleCallback(msg);
        } else {
           // 消息是一个Message对象
            if (mCallback != null) {
                // Handler的Callback接口不为空
                if (mCallback.handleMessage(msg)) {
                   //接口返回true的话,那么return不再往下执行
                    return;
                }
            }
           //执行Handler里面的handleMessage方法
            handleMessage(msg);
        }
    }

还有一些关于从消息队列中移除Message或者Callback,或者查找队列中是否存在Message或Callback的方法就不研究了,在我的认识中,消息机制都是执行的非常快,除非是延时或者定时的消息,否则一般很快就会执行完。我们只需要记住
removeCallbacksAndMessages(Object token):移除那些msg.obj == token的对象,如果token是null的话,那么移除所有的Message和Callback。

public final void removeMessages(int what)
public final void removeMessages(int what, Object object)
public final void removeCallbacksAndMessages(Object token)

Looper

这是一个用于在线程中轮训消息的类,默认的线程是没有消息轮训的,为了创建一个Looper轮训消息,在线程中调用prepare()初始化looper,调用loop开始轮训处理消息直到Looper停止。Looper基本上都是和Handler一起工作的.
先来看一下Looper的构造方法了,Looper有一个私有的构造方法,在构造方法内保存当前线程的信息,并且创建一个新的消息队列MessageQueue,这个构造函数由Looper的prepare()方法调用。

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

我们先来看Looper中的一些变量,ThreadLocal是一个和线程关联,并且每一个调用了prepare方法的线程都会建立一种Thread和Looper之间的映射,一个Thread对应一个Looper,而sMainLooper则是保存的是UI线程的Looper对象,我们可以通过这个特性来判断当前线程是否处于主线程

Looper.myLooper() == getMainLooper()

当我们调用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));
    }

当我们调用prepare方法的时候,就会建立Thread和Looper之间的映射关系,并且这个方法同一个线程中只能调用一次,否则会抛出异常。
对于prepareMainLooper方法,这个方法会由系统来调用,在应用打开的时候创建主线程和Looper之间的映射。这个方法不应该由我们自己来调用,因为同样主线程只能有一个Looper与之关联。

   public static void prepareMainLooper() {
        //创建一个不允许关闭的MessageQueue
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

讲到这里,我们又不得不提一下loop方法了,我们都说Looper是负责从消息队列里面取出消息,然后执行的,那么具体是怎么个操作法呢

    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 (;;) {
            // 1.轮训获取消息,这是一个阻塞的方法
            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 {
                //2.处理消息
                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);
            }
            //3.回收消息
            msg.recycleUnchecked();
        }
    }

这就是Looper轮训的工作了,在一个无限循环中,首先通过调用MessageQueue的next()方法获取下一个Message对象,有可能会阻塞(当有消息添加到队列的时候就会获取该消息继续往下执行)。获取到消息后就用消息中的target对象,调用它的dispatchMessage(Message msg)方法,可想而知,这个target就是指向我们的handler的,执行完成后就会调用Message的recycleUnchecked()方法释放资源回收消息。

    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                //把这个释放掉的Message添加到Message的链结构里面
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

Looper下还有一些方法

    #获取当前线程所关联的Looper对象
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    #获取Looper对象管理的消息队列MessageQueue
    public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }

    #不安全,调用这个方法不会等待Message执行完毕
    public void quit() {
        mQueue.quit(false);
    }

    #调用这个方法会等待Message执行完毕再退出
    public void quitSafely() {
        mQueue.quit(true);
    }

内存泄漏

Handler泄漏从来不是一个陌生的话题,从上面我们可以知道每一个Message都会持有Handler对象的引用,而一个没有static修饰的Handler类,因为非静态内部类默认会持有外部类的引用,所以假如在一个Activity中有一个Handler,而这个Activity准备销毁的时候Message持有Handler的引用,而Handler又持有Activity的引用,那么这个时候Activity就会因为无法及时回收而发生泄漏。
要解决这个问题也非常简单,先写个模板

public abstract class ModelHandler<T extends Activity> extends Handler {
    private final  WeakReference<T> reference;
    public ModelHandler(T t){
        reference = new WeakReference<T>(t);
    }

    @Override
    public void handleMessage(Message msg) {
        if (reference.get() == null){
            return;
        }else {
            doSomething(msg);
        }
    }

    public abstract void doSomething(Message msg);

}

接着在我们需要的地方实现这个模板类

   static class NewHandler extends ModelHandler<HandlerActivity>{

        public NewHandler(HandlerActivity activity) {
            super(activity);
        }

       @Override
       public void doSomething(HandlerActivity activity, Message msg) {
           switch (msg.what){
               case 1:
                   activity.dosomething();
                   break;
               default:
                   break;
           }

       }
    }

//进行初始化
NewHandler handler = new NewHandler(this);

除此以外,我还会在Activity的onDestroy处把handler致空,这样的可以保证关闭Activity后,不会有新的消息发送到消息队列。当然了,关于内存泄漏的问题可能还有更好的解决方法,希望大佬们多多指点。

关于MessageQueue部分的就不讲了,我们只需要记住通过next()来获取消息,并且这是一个阻塞式的方法即可。

Handler消息机制的流程图,要知道一切消息的执行都离不开Looper和Handler


Handler消息传递机制

最后分享一个超厉害的在线画图网站 http://www.processon.com 基本上能想到的图都能在上面画,还可以导出图片!
最后的最后,卖个关子,Handler还可以实现进程间的通信,但是留到进程通信的时候再说吧。

参考文章:
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

上一篇下一篇

猜你喜欢

热点阅读