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

publicstaticvoidmain(String[] args) {

      ......

// 创建 Looper、Handler、MessageQueue

      Looper.prepareMainLooper();

      ......

ActivityThread thread =newActivityThread();

thread.attach(false, startSeq);

if(sMainThreadHandler ==null) {

// 指定主线程的 handler 为 H

          sMainThreadHandler = thread.getHandler();

      }

      ......

// 开始准备接收消息

      Looper.loop();

  }

}

// 准备主线程的 Looper

frameworks/base/core/java/android/os/Looper.java

publicstaticvoidprepareMainLooper() {

prepare(false);

    synchronized (Looper.class) {

if(sMainLooper !=null) {

thrownewIllegalStateException("The main Looper has already been prepared.");

        }

        sMainLooper = myLooper();

    }

}

// prepare 方法中会创建一个 Looper 对象

frameworks/base/core/java/android/os/Looper.java

privatestaticvoidprepare(boolean quitAllowed) {

if(sThreadLocal.get() !=null) {

thrownewRuntimeException("Only one Looper may be created per thread");

    }

sThreadLocal.set(newLooper(quitAllowed));

}

// Looper 对象创建的时候,同时创建一个 MessageQueue

frameworks/base/core/java/android/os/Looper.java

privateLooper(boolean quitAllowed) {

mQueue =newMessageQueue(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这个方法,因为它是从缓存池中取的,可以减少资源浪费,而且效率也高。

publicstaticMessageobtain(){

synchronized(sPoolSync) {

if(sPool !=null) {

                Message m = sPool;

                sPool = m.next;

m.next =null;

m.flags =0;// clear in-use flag

                sPoolSize--;

returnm;

            }

        }

returnnewMessage();

    }

Handler的常用方法如下:

privateHandler mHandler =newHandler(){

@Override

publicvoidhandleMessage(Message msg){

super.handleMessage(msg);

        }

    };

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

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

//可能引入泄露的方法

privatefinalHandler mHandler =newHandler(){

@Override

publicvoidhandleMessage(Message msg){

//.....

        }

    };

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

//延迟1分钟发送消息

mHandler.postDelayed(newRunnable(){

@Override

publicvoidrun(){

//......

            }

},1000*60);

    }

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

privatestaticclassCommonHandlerextendsHandler{

privatefinalWeakReference mActivity;

publicCommonHandler(HandlerActivityactivity){

mActivity =newWeakReference(activity);

        }

@Override

public void handleMessage(Messagemsg){

HandlerActivityactivity = 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等)以及由其创建的所有窗口。

源码中有三个主要方法:

enqueueMessage 方法往消息列表中插入一条数据,

next 方法从消息队列中取出一条消息并将其从消息队列中移除

quit 方法退出消息列表,通过参数 safe 决定是否直接退出

next 方法

Messagenext(){

// 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.

finallongptr = mPtr;

if(ptr ==0) {

returnnull;

        }

intpendingIdleHandlerCount = -1;// -1 only during first iteration

intnextPollTimeoutMillis =0;

for(;;) {

if(nextPollTimeoutMillis !=0) {

                Binder.flushPendingCommands();

            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized(this) {

// Try to retrieve the next message.  Return if found.

finallongnow = 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();

returnmsg;

                    }

}else{

// No more messages.

nextPollTimeoutMillis = -1;

                }

// Process the quit message now that all pending messages have been handled.

if(mQuitting) {

                    dispose();

returnnull;

                }

//...省略代码

        }

    }

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

quit方法

voidquit(booleansafe){

if(!mQuitAllowed) {

thrownewIllegalStateException("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);

    }

}

privatevoidremoveAllMessagesLocked(){

    Message p = mMessages;

while(p !=null) {

        Message n = p.next;

        p.recycleUnchecked();

        p = n;

    }

mMessages =null;

}

privatevoidremoveAllFutureMessagesLocked(){

finallongnow = 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 =newHandler();

        Looper.loop();

//子线程

        Looper looper = .....;

//主线程

Handler handler =newHandler(looper);

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

publicclassHandlerThreadActivityextendsActivity{

@Override

protectedvoid onCreate(BundlesavedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_handler);

HandlerThreadhandlerThread =newHandlerThread("HandlerThread");

        handlerThread.start();

HandlermHandler =newHandler(handlerThread.getLooper()){

@Override

public void handleMessage(Messagemsg) {

super.handleMessage(msg);

Log.d("HandlerThreadActivity.class","uiThread2------"+Thread.currentThread());//子线程

            }

        };

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

mHandler.sendEmptyMessage(1);

    }

}

源码是这样的:

publicstaticvoidprepare(){

prepare(true);

    }

privatestaticvoidprepare(boolean quitAllowed){

if(sThreadLocal.get() !=null) {

thrownewRuntimeException("Only one Looper may be created per thread");

        }

sThreadLocal.set(newLooper(quitAllowed));

    }

privateLooper(boolean quitAllowed){

mQueue =newMessageQueue(quitAllowed);

        mThread = Thread.currentThread();

    }

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

publicHandler(Callback callback, booleanasync){

        ...

        mLooper = Looper.myLooper();

if(mLooper ==null) {

thrownewRuntimeException(

"Can't create handler inside thread "+ Thread.currentThread()

+" that has not called Looper.prepare()");

        }

        mQueue = mLooper.mQueue;

        mCallback = callback;

mAsynchronous =async;

    }

publicstatic@NullableLoopermyLooper(){

returnsThreadLocal.get();

    }

staticfinalThreadLocal sThreadLocal=newThreadLocal();

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

publicstaticvoidmain(String[] args){

        ...

        Looper.prepareMainLooper();

        ...

// End of event ActivityThreadMain.

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        Looper.loop();

}

publicstaticvoidprepareMainLooper(){

prepare(false);

synchronized(Looper.class) {

if(sMainLooper !=null) {

thrownewIllegalStateException("The main Looper has already been prepared.");

            }

            sMainLooper = myLooper();

// MIUI ADD

sMainLooper.enableAnrMonitor(true);

        }

    }

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

publicstaticvoidloop(){

        ...

booleanslowDeliveryDetected =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);

                }

            }

            ...

        }

    }

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

        }

    }

privatestaticvoidhandleCallback(Message message){

        message.callback.run();

    }

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

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

privatestaticvoidprepare(boolean quitAllowed){

if(sThreadLocal.get() !=null) {

thrownewRuntimeException("Only one Looper may be created per thread");

        }

sThreadLocal.set(newLooper(quitAllowed));

    }

publicstatic@NullableLoopermyLooper(){

returnsThreadLocal.get();

    }

staticfinalThreadLocal sThreadLocal=newThreadLocal();

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

看看ThreadLocal代码:

publicvoidset(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

if(map !=null)

map.set(this, value);

else

            createMap(t, value);

    }

publicTget() {

        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;

returnresult;

            }

        }

returnsetInitialValue();

    }

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


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容