从入坑到放弃是一个系列,讲述的是一个Android开发者在工作和学习时的悲惨命运,也希望读者在看文章 的时候能抱着同情的心情,自备纸巾。有钱的捧个钱场,没钱的借钱捧个钱场并双击666。
下面进入正题,Android消息机制,相信每个android开发者在平时工作中都会经常用到的。Handler是一个耳熟能详的词,也是在工作中需要交互的一个类。消息机制的存在就是为了解决Android系统中子线程中不能更新UI的矛盾,主线程(即UI线程)不能执行耗时的操作,比如经常提到的下载,否则会触发系统的ANR。所以耗时的操作就不得不放到主子线程去执行,如果想把下载的结果体现到View上,又不能直接更新,这时就需要Handler从子线程给主线程发送消息了。先来看一个简单的小“栗子”吧:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.transsion.zuochenzhang.testhandler.MainActivity">
<ProgressBar
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:progress="0"
android:max="100"
style="?android:attr/progressBarStyleHorizontal"/>
<Button
android:id="@+id/bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="模拟进度"
/>
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
private ProgressBar progressBar;
private Button button;
private int count;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
progressBar.setProgress(msg.what);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = (ProgressBar)findViewById(R.id.progress);
button = (Button)findViewById(R.id.bt);
button.setOnClickListener(new TestProgressBar());
}
class TestProgressBar implements View.OnClickListener{
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if(count == 100){
return;
}
count = count + 10;
Message msg = new Message();
msg.what = count;
handler.sendMessage(msg);
}
},0,1000);
}
}).start();
}
}
}
这里我搞了一个ProgressBar和一个Button,当我点击button的时候,使用计时器模拟了一个下载的动作,每隔一秒就去更新progressBar的进度,0-100每次更新10,这个10 就是通过handler从子线程传递给了主线程,然后更新UI。
在整个消息机制的体系下,是通过Handler,Looper以及MessageQueue它们之间相互协作来完成一整套工作的。首先请欣赏来自灵魂画手的一幅作品
工作原理
ThreadLocal与Looper
首先说一下ThreadLocal不是线程,它是保证线程之间互不干扰的保存数据。理论上在使用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) {
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;
}
在handler的构造方法当中可以看到,会判断Looper.myLooper是不是空,如果空的话就会抛异常
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
这里能看到looper是从threadlocal中获取出来的,那么肯定会地方会set
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 void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
在looper的prepare方法里看到了set和具体的set实现,这里能看出来在set同时也获取了当前的线程,根据当前线程获取到了一个ThreadLocalMap,这样就能保证不同线程之间保存的数据互不干扰。最后map.set
时候key就是当前的threadlocal对象,values就是要保存的数据,这里指的就是looper对象。所以其实使用handler的正确姿势应该是这样的:
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
可是平时在使用handler的时候为什么没有这么做呢,这时候要去ActivityThread.java中去寻找答案了
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
...省略部分
Looper.prepareMainLooper();
}
在ActivityThread的main方法中已经准备了一个looper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
到这里又看到了myLooper方法,那么它自然是从threadlocal中获取出来的,所以在UI线程android系统已经为我们准备了looper。如果是在子线程中,就要老老实实的自己准备looper
MessageQueue
MessageQueue即为消息队列,准备好了looper,looper的作用是不断轮询MessageQueue的,那下面就看下轮询的时候MQ(之后MessageQueue简称为MQ)都做了什么。MQ虽然叫作消息队列,但是它的实现其实是用链表来完成的。因为MQ主要就是两个功能,一个是插入数据,一个是删除数据,那么自然链表在这两个功能上效率是高的。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
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;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
enqueueMessage即为消息插入到MQ中的操作
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方法里面的for循环是个无限循环,如果有新消息就return消息并从链表中删除此条消息,如果没有就阻塞
工作流程
了解完他们各自的工作原理,最后来梳理一下handler整个的流程。首先Looper.prepare去准备了一个looper,将创建的looper存到了threadlocal当中。然后我们可以创建Handler,在new Handler时候取出了looper,给MQ赋值等,参考最上面的源码。最后执行Loop.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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
省略之后代码...
}
}
loop方法中,不断循环MQ,判断是否有新消息,如果有最后调用msg.target.dispatchMessage();这里要注意,这句代码就是真正调用发送消息的代码,是在Looper.java里执行的,而不是在Handler.java里,msg.target就是handler对象。当我们执行handler.sendMessage()的时候,MQ中就是有新消息,执行msg.target.dispatchMessage();
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
从这里回调了handleMessage(msg),就可以接受发送的消息,然后处理自己的业务逻辑。再提一句就是无论以哪个方法来放松消息最后都会调用sendMessageAtTime方法来插入到MQ中,如果不相信读者可以自己点一点源码看看
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;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
以上说的就是Android消息机制的整个一套原理和工作流程,欢迎各位读者大神们多提意见。写的头晕眼花,要去吃饭恢复体能了。