Android Handler Looper MessageQueue之间的爱恨情仇

Handler众所周知是学习安卓以及以后实战中必须要掌握并且熟练的技术点,因为它可以说是安卓开发,安卓应用程序所有地方都用到的技术了,小到点击一个按钮改变文字,大到系统事件分发,只要用到通信的地方,都离不开它。了解它,熟悉它,掌握它是做好安卓开发的必备技术。

前言

做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,并不是Android 规定只能在主线程中访问 UI ,而是因为ViewRootImpl在主线程中创建 ,所以UI的绘制也需要在主线程中执行 。如果我们需要做一些耗时的操作并且操作结束后要修改 UI ,那么就需要用到 Android 提供的 Handler 切换到主线程来访问 UI 。因此,系统之所以提供 Handler,主要原因就是为了解决在子线程中无法访问 UI 的问题。

概述

知道Handler的童鞋肯定也会想到它的消息机制,怎么发送消息,怎么处理消息,怎么按照分发出去的消息顺序,处理消息的,那就要知道,Looper和MessageQueue了,因为它们就像是Handle的兄弟和下属一样,忠心耿耿的陪伴着它。

在应用启动时,系统的主线程会创建Looper和MessageQueue,下面我们来看ActivityThread.main 方法被调用的逻辑。

frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
       ......
       // 创建 Looper、Handler、MessageQueue
       Looper.prepareMainLooper();
       ......
       ActivityThread thread = new ActivityThread();
       thread.attach(false, startSeq);

       if (sMainThreadHandler == null) {
           // 指定主线程的 handler 为 H
           sMainThreadHandler = thread.getHandler();
      }
       ......
       // 开始准备接收消息
       Looper.loop();
  }
}

// 准备主线程的 Looper
frameworks/base/core/java/android/os/Looper.java
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

// prepare 方法中会创建一个 Looper 对象
frameworks/base/core/java/android/os/Looper.java
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));
}

// Looper 对象创建的时候,同时创建一个 MessageQueue
frameworks/base/core/java/android/os/Looper.java
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

可以看到Looper是在系统的main方法中创建的,也就是系统的主线程,Looper 负责的就是创建一个 MessageQueue,然后进入一个无限循环体不断从该 MessageQueue 中读取消息,而消息的创建者就是一个或多个 Handler 。

MessageQueue是一个消息队列,Looper是一个循环体,Message是消息,而Handler是消息的句柄。

下面一步一步用代码来看:

Message

Message中有4个变量参数,what ,arg1,arg2,obj;经常使用其中的参数作为判断handleMessage的判断条件,其中一般有如下3种方式创建Message:

(1)通过Message的构造器模式创建:handle.sendMessage(msg);

(2)通过Message的obtain函数去操作:handler.sendMessage(msg);

(3)通过Obtain(handler)方式去创建 :msg.sendToTarget();

发现其实通过obtain是优先从缓存的Message池中去取,当取不到时候才会创建一个新的Message,所以以后大家在使用Handle发送消息的时候,尽量使用obtain这个方法,因为它是从缓存池中取的,可以减少资源浪费,而且效率也高。

 public static Message obtain() {
        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();
    }

Handler的常用方法如下:

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

此中方式创建Handler时候会造成内存泄露,原因是因为非静态的匿名内部类和非静态内部类是可以持有一个外部类的引用的;因为在Java.class编译时候会将非静态的内部类和外部类编译成2个文件存储,非静态的内部类可以访问外部类的成员变量和成员方法这个是为什么呢?就是因为非静态的内部类是持有外部类的一个隐式引用,而静态内部类和外部类之间没有,两个几乎相当于两个独立的Class文件。

至于这个引用为什么会造成内存泄露呢?先说下根本原因吧,是因为handler持用Activity的引用,而Handler中如果消息没有处理完,Activity在销毁时候是没有办法被GC回收的,GC回收是根据对象可达性,这个Handler是绑定在主线程,主线程是应用入口,当Handler处理不完此消息必然造成Activity无法回收,最终内存泄露;像下面的代码就会造成1分钟Activity无法回收:

    //可能引入泄露的方法
    private final Handler mHandler = new Handler(){

        @Override
        public void handleMessage(Message msg){

            //.....

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);

        //延迟1分钟发送消息
        mHandler.postDelayed(new Runnable(){

            @Override
            public void run(){
                //......
            }

        }, 1000*60);
    }

知道原因解决方法也跟着出来了: 1.使用静态的内部类替代非静态内部类; 2.在onDestory方法时移除Handler中的消息也可以解决;MessageQueue

private static class CommonHandler extends Handler{

        private final WeakReference<HandlerActivity> mActivity;

        public CommonHandler(HandlerActivity activity){
            mActivity = new WeakReference<HandlerActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg){
            HandlerActivity activity = mActivity.get();
            if(activity != null){
                //.....
            }
        }
    }

MessageQueue:
MessageQueue 负责管理消息队列,通过一个单链表的数据结构来维护。每个线程内部都维护了一个消息队列——MessageQueue。消息队列MessageQueue,顾名思义,就是存放消息的队列(好像是废话…)。那队列中存储的消息是什么呢?假设我们在UI界面上单击了某个按钮,而此时程序又恰好收到了某个广播事件,那我们如何处理这两件事呢? 因为一个线程在某一时刻只能处理一件事情,不能同时处理多件事情,所以我们不能同时处理按钮的单击事件和广播事件,我们只能挨个对其进行处理,只要挨个处理就要有处理的先后顺序。 为此Android把UI界面上单击按钮的事件封装成了一个Message,将其放入到MessageQueue里面去,即将单击按钮事件的Message入栈到消息队列中,然后再将广播事件的封装成以Message,也将其入栈到消息队列中。也就是说一个Message对象表示的是线程需要处理的一件事情,消息队列就是一堆需要处理的Message的池。线程Thread会依次取出消息队列中的消息,依次对其进行处理。MessageQueue中有两个比较重要的方法,一个是enqueueMessage方法,一个是next方法。enqueueMessage方法用于将一个Message放入到消息队列MessageQueue中,next方法是从消息队列MessageQueue中阻塞式地取出一个Message。在Android中,消息队列负责管理着顶级程序对象(Activity、BroadcastReceiver等)以及由其创建的所有窗口。

源码中有三个主要方法:

  1. enqueueMessage 方法往消息列表中插入一条数据,
  2. next 方法从消息队列中取出一条消息并将其从消息队列中移除
  3. quit 方法退出消息列表,通过参数 safe 决定是否直接退出

next 方法

 Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

               //...省略代码
        }
    }

可以发现 next 方法是一个无限循环的方法,如果消息队列中没有消息,那么 next 方法会一直阻塞在这里。当有新消息到来时,next 方法会从中获取消息出来返回给 Looper 去处理,并将其从消息列表中移除。

quit方法

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();//移除尚未处理的消息
        } else {
            removeAllMessagesLocked();//移除所有消息
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {
            removeAllMessagesLocked(); // 移除尚未处理的消息
        } else { // 正在处理的消息不做处理
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

从 上述代码中可以看出,当 safe 为 true 时,只移除尚未触发的所有消息,对于正在处理的消息不做处理,当 safe 为 false 时,移除所有消息。

消息队列MessageQueue只是存储Message的地方,真正让消息队列循环起来的是Looper,这就好比消息队列MessageQueue是个水车,那么Looper就是让水车转动起来的河水,如果没有河水,那么水车就是个静止的摆设,没有任何用处,Looper让MessageQueue动了起来,有了活力。

Looper

消息队列MessageQueue只是存储Message的地方,真正让消息队列循环起来的是Looper,这就好比消息队列MessageQueue是个水车,那么Looper就是让水车转动起来的河水,如果没有河水,那么水车就是个静止的摆设,没有任何用处,Looper让MessageQueue动了起来,有了活力。

Looper是用来使线程中的消息循环起来的。默认情况下当我们创建一个新的线程的时候,这个线程里面是没有消息队列MessageQueue的。为了能够让线程能够绑定一个消息队列,我们需要借助于Looper:首先我们要调用Looper的prepare方法,然后调用Looper的Loop方法。

其实说的简单点Looper就是一个循环处理消息队列的东东; 使用方式也简单,如下是将当前Handler和Looper关联,如果在主线程,关联是主线程,如果是子线程管理就子线程;

        //子线程
        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();

        //子线程
        Looper looper = .....;
        //主线程
        Handler handler = new Handler(looper);

子线程创建Handler一般使用HandlerThread方式:

public class HandlerThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        HandlerThread handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();

        Handler mHandler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("HandlerThreadActivity.class","uiThread2------"+Thread.currentThread());//子线程
            }
        };

        Log.d("HandlerThreadActivity.class","uiThread1------"+Thread.currentThread());//主线程
        mHandler.sendEmptyMessage(1);
    }
}

源码是这样的:

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

Looper对象实例化就是在Looper.prepare完成的,并且将当前线程绑定到mThread上,现在回过头看Handler创建时候就明白为什么没有单独开线程创建时候,绑定是主线程上;来看Handler创建方法:

 public Handler(Callback callback, boolean async) {
        ...
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

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

      static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

到这里还是没有直观的找到绑定的线程;但是我们忽略了一个ActivityThread,ActivityThread是Android程序的入口,一直运行,其实开始已经说过了main方法了,现在再来看下

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
}

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
            // MIUI ADD
            sMainLooper.enableAnrMonitor(true);
        }
    }

Handler,Looper在程序启动的时候就已经开始工作了,也就是说我们自己的ThreadLocal.get()获取的是ActivityThread线程即主线程; 在看Looper.loop:

    public static void loop() {
        ...
        boolean slowDeliveryDetected = false;
        //死循环,不断的取消息队列中消息
        for (;;) {
            //消息队列处理消息时候因为nativePollOnce阻塞休眠
            Message msg = queue.next(); // might block
            //无消息时候退出
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

             try {
                //处理消息
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
        }
    }

    public void dispatchMessage(Message msg) {
        //如果是post方式传入一个runnable执行即可
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //如果是public Handler(Callback callback)执行此处
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                //事件分发,是否自己消费掉
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        message.callback.run();
    }

这里更加明白了在ondestroy时候removeCallbacksAndMessages的含义了, 就是移除添加的postxxx,sendMessagexxx等消息; 至此整个Looper,Message,Handler,MessageQueue到此应该梳理比较清楚了;

还有一个类:ThreadLocal,在源码的Looper绑定线程时候看到,

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

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

        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();  

ThreadLocal是一个本地线程存放副本变量的工具,不同线程其变量值互不干扰,适用于多线程高并发场景完成多线程调用互不干扰的变量的值,可以类似理解为多进程操作同一个静态变量,其值也是互不干扰;

看看ThreadLocal代码:

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

实现原理就是set时候先从ThreadLocalMap去找,取不到再存放,get也是类似原理,看源码就明白了;

 

Handler消息机制在Android系统源码中进行了大量的使用,可以说是涉及了Android的方方面面,比如我们四大组件的启动,Application的创建等等,学好Handler相关的知识,可以帮助我们更好的去阅读Android源码,而且Handler在我们日常开发中直接或间接的会被用到。同时通过对Handler源码的学习,让我感受到了代码设计的背后,蕴藏着工程师大量的智慧,到了这里,关于Handler相关的知识就都讲完了,如果你还有什么问题,评论区告诉我吧。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值