android消息机制概述:
android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程,他们其实是密不可分的一个整体,只不过我们开发中经常用到的是Handler而已。
Handler的主要作用是将一个任务切换到指定的线程去运行,为什么呢?
因为android规定UI只能在主线程中访问和操作,为什么android不能在子线程中更新UI呢?因为android的UI线程不是线程安全的,如果允许多个线程并发访问可能会带来不可预期的状态。
那么为什么系统不对UI控件的访问加上锁机制呢?
缺点有两个:首先加上锁机制会让UI的操作变得复杂;其次会降低UI的访问效率,因为锁机制会阻塞某些线程的执行。因此最简单高效的方法就是采用单线程进行UI操作。对于我们开发者只不过是使用Handler切换一下UI的执行线程而已。
Handler的工作原理:
先明确几个概念:
HandlerThread 支持消息循环的线程
Handler 消息处理器
Looper 消息循环对象
MessageQueue 消息队列
Message 消息体
对应关系是:一对多,即(一个)HandlerThread、Looper、MessageQueue -> (多个)Handler、Message
Handler创建时会采用当前的Looper来构建内部的消息循环系统,UI线程是会自动创建出Looper对象的,而其他的消息循环线程是需要我们手动创建Looper对象的。Handler创建完毕后,这个时候其内部的Looper和MessageQueue就可以和Handler一起协同工作了,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中处理,也可以通过Handler的send方法来执行,其实post方法最后也是执行的send方法,只不过post是为了兼容java的异步线程。当Handler的send方法调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后Looper发现有新消息来到时,就会处理这个消息,最终消息中的Runnable或者Handler的handlerMessage方法就会被调用,这样业务逻辑就被切换到创建Handler所在的线程中去了。
HandlerThread handlerThread = new HandlerThread("handlerThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
//发送消息
handler.sendMessage(msg);
//接收消息
static class MyHandler extends Handler {
//对于非主线程处理消息需要传Looper,主线程有默认的sMainLooper
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
ThreadLocal的工作原理:
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程内部存储数据,数据存储以后,只有在指定的线程中可以获取到存储的数据。
一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候就可以考虑使用ThreadLocal。例如:对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同的线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现不同线程的Looper的存取。
ThreadLocal的另外的使用场景:复杂逻辑下的对象传递,例如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码的入口多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,我们该怎么做呢?这个时候其实就可以采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只需要通过get方法就可以获取到监听器。如果不使用ThreadLocal,那么可能有以下两种方法。
方案一:将监听器通过参数的形式传递下去,如果函数调用栈很深的话,几乎无法接受,整个程序看起来都不好了,简直太糟糕了。
方案二:将监听器设置为全局的静态变量,这样只有一个复杂线程或者两个复杂线程还好,但是如果有十个复杂线程呢?需要十个全局静态对象?对于程序来说简直不可想象。
下面举个ThreadLocal的例子:
ThreadLocal<Boolean> threadLocal=new ThreadLocal<Boolean>();
threadLocal.set(true);
Log.d("main Thread---", "threadLocal的值" + threadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set(false);
Log.d("Thread--1---", "threadLocal的值"+threadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Log.d("Thread--2---", "threadLocal的值"+threadLocal.get());
}
}).start();
答案分别为:true,false,null;
看一下ThreadLocal的源码,首先看set方法
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
从Thread'Local的set和get方法可以看出,它操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法,它们对于ThreadLocal的读写操作仅限于 各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不干扰工作的存储和修改数据。
MessageQueue的工作原理:
MessageQueue主要包含了两个操作:插入和读取,读取本身会伴随着删除操作,插入和读取分别为equeueMessage和next。
其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。尽管MessageQueue叫做消息队列,其实它是单链表的结构,单链表在插入和删除上面有更高的效率。
enqueueMessage的方法无非就是单链表的插入,就不啰嗦了,我们看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);
...................
}
我们可以发现next是一个无限循环的方法,如果消息队列中没有消息,那么next方法就会一直阻塞在这里,当有新消息来时,next就会返回这条消息并且从单链表中移除。
Looper的工作原理:
Looper在android消息机制中扮演着消息循环的角色,具体就是它会不停的从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。
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));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
/** Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
Handler的工作需要looper,我们看一下源码的创建looper的方法prepare,看注释都好清楚,不想啰嗦了又!~
我们看一下Lopper的构造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
创建时会首先创建MessageQueue对象,我们知道Lopper的主要功能就是从MessageQueue中读取消息,看最重要的方法:
/**
* 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.recycleUnchecked();//放到消息池,以防复用
}
}
总的过程:
(1)创建消息循环
prepare()用于创建Looper消息循环对象。Looper对象通过一个成员变量ThreadLocal进行保存。
(2)获取消息循环对象
myLooper()用于获取当前消息循环对象。Looper对象从成员变量ThreadLocal中获取。
(3)开始消息循环
loop()开始消息循环。循环过程如下:
每次从消息队列MessageQueue中取出一个Message
使用Message对应的Handler处理Message
已处理的Message加到本地消息池,循环复用
循环以上步骤,若没有消息表明消息队列停止,退出循环。退出循环时Looper会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出。
msg.target.dispatchMessage(msg);
msg.target就是我们的Handler对象了,消息取出之后会调用Handler的dispatchMessage方法。
Handler的工作原理:前面说过Handler的主要作用其实就是发送消息和处理消息,总的过程:
(1)发送消息
Handler支持2种消息类型,即Runnable和Message。因此发送消息提供了post(Runnable r)和sendMessage(Message msg)两个方法。从下面源码可以看出Runnable赋值给了Message的callback,最终也是封装成Message对象对象。
(2)处理消息
Looper循环过程的时候,是通过dispatchMessage(Message msg)对消息进行处理。处理过程:先看是否是Runnable对象,如果是则调用handleCallback(msg)进行处理,最终调到Runnable.run()方法执行线程;如果不是Runnable对象,再看外部是否传入了Callback处理机制,若有则使用外部Callback进行处理;若既不是Runnable对象也没有外部Callback,则调用handleMessage(msg),这个也是我们开发过程中最常覆写的方法了。
(3)移除消息
removeCallbacksAndMessages(),移除消息其实也是从MessageQueue中将Message对象移除掉。
看看处理消息的方法:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {//此处的callBack是个接口,我们一般都是重写Handler的handleMessage方法,这里提供另外一种方式
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
感悟:主线程会在应用建立之初就创建消息循环机制,每一个事件全部响应在这个消息循环机制之下,通过looper.loop,不断的从messagequenu中取得最新的message交由对象的handler处理,但是如果一个事件处理的时间过长,就会ANR。Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了.
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;
...........
}
}
可以看见Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施。
如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。
延伸:是否只有在主线程线程才能更新UI呢?UI只能在主线程更新么?这篇文章里面,最后一个例子:
class LooperThread extends Thread
{
4 public void run()
{
Looper.prepare();
//代码1....
Looper.loop();
//代码2....
}
}
Looper.prepare();
和
Looper.loop();
会创建该线程的消息循环,之间的代码只在该线程内起作用。
我们一般接受的思想就是不要在子线程中更新UI,为什么呢?这你就不得不提到 activity的启动过程中的检查机制.
public void setContentView(View view, ViewGroup.LayoutParams params) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Window.Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
执行完上面的setContentView后,DecorView还没有被WindowManager正式添加到Window中,接着会调用到ActivityThread类的handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow窗口,handleResumeActivity中需要执行addView方法,WindowManagerImpl 将 addView操作交给WindowManagerGlobal来实现,WindowManagerGlobal的addView函数中创建了一个ViewRootImpl对象root,然后调用ViewRootImpl类中的setView成员方法,setView中主要做:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
我们看到有这个checkThread,他就是来保证我们必须在ui线程中更新view,那么是不是子线程就不能更新ui了呢,,并不是这样的,你在oncreate中执行ui更新时:
(new Thread(){
@Override
public void run() {
super.run();
Log.e(“pid”,Thread.currentThread().getId()+””);
((TextView)findViewById(R.id.keep)).setText(“测试代码”);
}
}).start();
checkThread方法没执行到,这时候你相当于可以设置ui,但是你设置线程sleep几秒后,在更新,就会报错,只适合checkTread已经执行到了。