博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Handler、Looper、MessageQueue、HandlerThread、ActivityThread、Message源码分析
阅读量:2389 次
发布时间:2019-05-10

本文共 15727 字,大约阅读时间需要 52 分钟。

一、Handler、Looper、MessageQueue、HandlerThread、ActivityThread、Message结构

1、Message:消息;其中包含了消息ID,消息对象以及处理的数据等,由MessageQueue统一列队管理,终由Handler处理

2、MessageQueue:消息队列;用来存放Handler发送过来的消息,并按照FIFO(先入先出队列)规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等Looper的抽取。

3、Looper:消息泵,不断从MessageQueue中抽取Message执行。因此,一个线程中的MessageQueue需要一个Looper进行管理。Looper是当前线程创建的时候产生的(UI Thread即主线程是系统帮忙创建的Looper,而如果在子线程中,需要手动在创建线程后立即创建Looper[调用Looper.prepare()方法])。也就是说,会在当前线程上绑定一个Looper对象。

4、Thread:线程;进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。

5、HandlerThread:Thread+Looper;具有Looper功能的子线程。

6、ActivityThread:主线程;一个应用的主线程。

在上图中,Handler通过Looper向MessageQueue中插入消息,Looper从MessageQueue(单链表)取出消息再交给Handler处理。ActivityThread为系统主线程(Looper,非继承Thread),HandlerThread可以认为是子线程(Looper+Thread)。Handler默认是ActivityThread的Looper,也可以传入HandlerThread的Looper。通过上述模型,实现消息的跨线程通信。

二、Handler源码分析

1、构造函数

public Handler() {       this(null, false);   }    public Handler(Callback callback, boolean async) {       if (FIND_POTENTIAL_LEAKS) {           final Class
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; }

(a)校验是否可能内存泄漏

Handler的构造函数中首先判断了FIND_POTENTIAL_LEAKS的值,为true时,会获取该对象的运行时类,如果是匿名类,成员类,局部类的时候判断修饰符是否为static,不是则提示可能会造成内存泄漏。

问:为什么匿名类,成员类,局部类的修饰符不是static的时候可能会导致内存泄漏呢?
答:因为,匿名类,成员类,局部类都是内部类,内部类持有外部类的引用,如果Activity销毁了,而Hanlder的任务还没有完成,那么Handler就会持有activity的引用,导致activity无法回收,则导致内存泄漏;静态内部类是外部类的一个静态成员,它不持有内部类的引用,故不会造成内存泄漏。

(b)初始化一个Looper mLooper

如果没有调用Looper.prepare()则不能再线程里创建handler!我们都知道,如果我们在UI线程创建handler,是不需要调用这个方法的,但是如果在其他线程创建handler的时候,则需要调用这个方法。

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));    }    public static Looper getMainLooper() {        synchronized (Looper.class) {            return sMainLooper;        }    }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(false),这个方法我们刚才已经看了,是创建一个Looper对象,然后存到sThreadLocal中;

然后判断sMainLooper是否为空,空则抛出异常
sMainLooper不为空,则sMainLooper = myLooper()

至此sMainLooper对象赋值成功,所以,我们需要知道prepareMainLooper()这个方法在哪调用的,跟一下代码,就发现在ActivityThread的main方法中调用了Looper.prepareMainLooper();。现在真相大白:

当我们在UI线程中创建Handler的时候sThreadLocal里的Looper是在ActivityThread的main函数中调用了prepareMainLooper()方法时初始化的。

(c)初始化一个MessageQueue mQueue

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

2、Handler发送消息

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

3、Handler接收消息

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

三、Looper源码分析

public static final void loop() {        Looper me = myLooper();          MessageQueue queue = me.mQueue;          // 开始了死循环        while (true) {              Message msg = queue.next(); // 无消息或者等待时会阻塞            if (msg != null) {                if (msg.target == null) {                      return;                  }                  if (me.mLogging!= null){               me.mLogging.println(   ">>>>> Dispatching to " + msg.target + " "    + msg.callback + ": " + msg.what   );              }                msg.target.dispatchMessage(msg);  //target为msg对应的handler                if (me.mLogging!= null) {               me.mLogging.println(    "<<<<< Finished to    " + msg.target + " "   + msg.callback);              }                msg.recycle();              }        }    }

Looper的loop()方法死循环读取MessageQueue的消息,然后交给Handler处理,msg.target.dispatchMessage(msg);  //target为msg对应的handler。

四、MessageQueue源码分析

MessageQueue中最重要的就是两个方法:

1.enqueueMessage向队列中插入消息
2.next 从队列中取出消息

enqueueMessage代码分析:

boolean enqueueMessage(Message msg, long when) {        if (msg.target == null) {//msg.target就是发送此消息的Handler            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;//将延迟时间封装到msg内部            Message p = mMessages;//消息队列的第一个元素            boolean needWake;            if (p == null || when == 0 || when < p.when) {               //如果此队列中头部元素是null(空的队列,一般是第一次),或者此消息不是延时的消息,则此消息需要被立即处理,此时会将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,然后判断如果Looper获取消息的线程如果是阻塞状态则唤醒它,让它立刻去拿消息处理                          msg.next = p;                mMessages = msg;                needWake = mBlocked;            } else {                //如果此消息是延时的消息,则将其添加到队列中,原理就是链表的添加新元素,按照when,也就是延迟的时间来插入的,延迟的时间越长,越靠后,这样就得到一条有序的延时消息链表,取出消息的时候,延迟时间越小的,就被先获取了。插入延时消息不需要唤醒Looper线程。                  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;    }

源码中主要的地方我给了注释,可以参考参考。

由此可以看出:
MessageQueue中enqueueMessage方法的目的有两个:
1.插入消息到消息队列
2.唤醒Looper中等待的线程(如果是及时消息并且线程是阻塞状态)
同时我们知道了MessageQueue的底层数据结构是单向链表,MessageQueue中的成员变量mMessages指向的就是该链表的头部元素。

接下来我们再来分析一下取出消息的方法next():

next()方法代码比较多,下面是主要部分

Message next() {            final long ptr = mPtr;        if (ptr == 0) {           //从注释可以看出,只有looper被放弃的时候(调用了quit方法)才返回null,mPtr是MessageQueue的一个long型成员变量,关联的是一个在C++层的MessageQueue,阻塞操作就是通过底层的这个MessageQueue来操作的;当队列被放弃的时候其变为0。            return null;        }        int pendingIdleHandlerCount = -1; // -1 only during first iteration        int nextPollTimeoutMillis = 0;        for (;;) {            if (nextPollTimeoutMillis != 0) {                Binder.flushPendingCommands();            }            //阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的。           //如果nextPollTimeoutMillis=-1,一直阻塞不会超时。           //如果nextPollTimeoutMillis=0,不会阻塞,立即返回。           //如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。            nativePollOnce(ptr, nextPollTimeoutMillis);            synchronized (this) {                           final long now = SystemClock.uptimeMillis();                Message prevMsg = null;                Message msg = mMessages;                if (msg != null && msg.target == null) {                    //msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)                    //如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息)                    do {                        prevMsg = msg;                        msg = msg.next;                    } while (msg != null && !msg.isAsynchronous());                }                if (msg != null) {                    if (now < msg.when) {                        // 如果消息此刻还没有到时间,设置一下阻塞时间nextPollTimeoutMillis,进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞;                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                    } else {                        //正常取出消息                        //设置mBlocked = false代表目前没有阻塞                        mBlocked = false;                        if (prevMsg != null) {                            prevMsg.next = msg.next;                        } else {                            mMessages = msg.next;                        }                        msg.next = null;                        msg.markInUse();                        return msg;                    }                } else {                    //没有消息,会一直阻塞,直到被唤醒                    nextPollTimeoutMillis = -1;                }                if (mQuitting) {                    dispose();                    return null;                }            pendingIdleHandlerCount = 0;            nextPollTimeoutMillis = 0;        }    }

由此可以看出:

1.当首次进入或所有消息队列已经处理完成,由于此刻队列中没有消息(mMessages为null),这时nextPollTimeoutMillis = -1 ,然后会处理一些不紧急的任务(IdleHandler),之后线程会一直阻塞,直到被主动唤醒(插入消息后根据消息类型决定是否需要唤醒)。
2.读取列表中的消息,如果发现消息屏障,则跳过后面的同步消息。
3.如果拿到的消息还没有到时间,则重新赋值nextPollTimeoutMillis = 延时的时间,线程会阻塞,直到时间到后自动唤醒
4.如果消息是及时消息或延时消息的时间到了,则会返回此消息给looper处理。

通过enqueueMessage和next两个方法的分析我们不难得出:

消息的入列和出列是一个生产-消费者模式,Looper.loop()在一个线程中调用next()不断的取出消息,另外一个线程则通过enqueueMessage向队列中插入消息,所以在这两个方法中使用了synchronized (this) {}同步机制,其中this为MessageQueue对象,不管在哪个线程,这个对象都是同一个,因为Handler中的mQueue指向的是Looper中的mQueue,这样防止了多个线程对同一个队列的同时操作。

五、HandlerThread源码分析

1、HandlerThread继承Thread

public class HandlerThread extends Thread {    int mPriority;    int mTid = -1;    Looper mLooper;    private @Nullable Handler mHandler;    public HandlerThread(String name) {        super(name);        mPriority = Process.THREAD_PRIORITY_DEFAULT;    }}

2、使用Looper

@Overridepublic void run() {    mTid = Process.myTid();    Looper.prepare();//初始化Looper    synchronized (this) {        mLooper = Looper.myLooper();        notifyAll();    }    Process.setThreadPriority(mPriority);    onLooperPrepared();    Looper.loop();//Loop循环    mTid = -1;}

六、ActivityThread源码分析

ActivityThread即Android的主线程,也就是UI线程,ActivityThread的main方法是一个APP的真正入口,MainLooper在它的main方法中被创建。

public static void main(String[] args) {        ...        Looper.prepareMainLooper();        ActivityThread thread = new ActivityThread();        thread.attach(false);        if (sMainThreadHandler == null) {            sMainThreadHandler = thread.getHandler();//主线程Handler        }        if (false) {            Looper.myLooper().setMessageLogging(new                    LogPrinter(Log.DEBUG, "ActivityThread"));        }        // End of event ActivityThreadMain.        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);        Looper.loop();//启动Looper        throw new RuntimeException("Main thread loop unexpectedly exited");    }

首先 ActivityThread 并不是一个 Thread,就只是一个 final 类而已。我们常说的主线程就是从这个类的 main 方法开始,main 方法很简短,一眼就能看全(如上),我们看到里面有 Looper 了,那么接下来就找找 ActivityThread 对应的 Handler 啊,就是内部类 H,其继承 Handler,贴出 handleMessage 的小部分:

public void handleMessage(Message msg) {            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));            switch (msg.what) {                case LAUNCH_ACTIVITY: {                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;                    r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo);                    handleLaunchActivity(r, null);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                } break;                case RELAUNCH_ACTIVITY: {                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;                    handleRelaunchActivity(r);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                } break;                case PAUSE_ACTIVITY:                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");                    handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2, (msg.arg1&2) != 0);                    maybeSnapshot();                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                    break;                case PAUSE_ACTIVITY_FINISHING:                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");                    handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2, (msg.arg1&1) != 0);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                    break;                case STOP_ACTIVITY_SHOW:                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");                    handleStopActivity((IBinder)msg.obj, true, msg.arg2);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                    break;                case STOP_ACTIVITY_HIDE:                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");                    handleStopActivity((IBinder)msg.obj, false, msg.arg2);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                    break;                case SHOW_WINDOW:                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");                    handleWindowVisibility((IBinder)msg.obj, true);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                    break;                case HIDE_WINDOW:                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow");                    handleWindowVisibility((IBinder)msg.obj, false);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                    break;                case RESUME_ACTIVITY:                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");                    handleResumeActivity((IBinder) msg.obj, true, msg.arg1 != 0, true);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                    break;                case SEND_RESULT:                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");                    handleSendResult((ResultData)msg.obj);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);                    break;            ...........}

如果你了解下linux的epoll你就知道为什么不会被卡住了,先说结论:阻塞是有的,但是不会卡住 

主要原因有2个

(1)epoll模型 

当没有消息的时候会epoll.wait,等待句柄写的时候再唤醒,这个时候其实是阻塞的。所有的ui操作都通过handler来发消息操作。 (MessageQueue源码里面有调用的native方法即为epoll模型)

(2)比如屏幕刷新16ms一个消息,你的各种点击事件,所以就会有句柄写操作,唤醒上文的wait操作,所以不会被卡死了。

七、Handler、Thread、HandlerThread区别

Handler:发送Message消息,接收Message回调消息,实现跨线程通信的操作者。

Thread:线程;进程中执行运算的最小单位,亦即执行处理机调度的基本单位。

HandlerThread:Thread+Looper;具有Looper功能的线程。相对于ActivityThread主线程而言,HandlerThread为子线程。

Handler的目标是解决子线程与主线程通信的问题,而HandlerThread则是解决子线程与子线程的通讯问题。

转载地址:http://osxab.baihongyu.com/

你可能感兴趣的文章
ubuntu系统的定制裁剪(适用于嵌入式瘦客户端)
查看>>
嵌入式之系统移植详解(linux)
查看>>
openstack之 glance_image和instances存储目录解析
查看>>
centos7(三节点)搭建ceph环境
查看>>
将linux(ubuntu)安装到U盘下面--便携式ubuntu和使用dd制作U盘安装工具
查看>>
linux之强大的find命令
查看>>
python使用变量操作mysql语句
查看>>
linux bridge 网桥详解
查看>>
Mysql之主键、外键和各种索引
查看>>
python main()函数 name == ‘main’:
查看>>
flask一个基本的http响应流程
查看>>
linux常见的文件及目录操作12个命令
查看>>
挂载ceph的rbd块存储作为本地磁盘块
查看>>
ceph的块设备的两种使用方式及代码示例
查看>>
查看python中模块的所有方法
查看>>
python通过librados库通过底层的rados操作ceph的对象存储和块存储
查看>>
在客户端使用python来调用boto S3 API来操作librados库
查看>>
ceph存储数据的详细流程(CRUSH)
查看>>
linux内核模块详解
查看>>
ceph集群的扩展(centos7环境)
查看>>