1.前言
更多内容请查看android生态之应用篇
新人报道,这是我在CSDN发布的第一篇文章,欢迎大家技术交流。如需转载,请标明出处!
2.引言
先提出一个问题:下载网络图片,并用imageview控件展示需要怎么做?
针对这个问题,需这样分析,由于涉及到访问网络,并有io读写操作,这个过程是比较耗时的,为了不造成程序卡顿(会有ANR风险),我们肯定不能直接在主线程中访问网络并下载图片,因此我们新开一个子线程做这个耗时操作。我们在子线程中下载好了网络图片,由于更新UI操作需要在主线程中进行(子线程更新UI不安全,如果两个子线程同时更新同一个UI,就会出问题),那就需要解决在子线程通知主线程更新UI的问题。我刚好认识线程通信的桥梁专家Handler,下面就将他介绍给大家。
2.1补充
子线程可以更新实现UI更新,但一般不建议在子线程直接执行更新UI操作。不仅使用需要适当的时机,还有可能会导致UI更新混乱问题。更新混乱可能都知道,这里提一下如果在子线程直接更新UI的时机。
用WindowManagerImpl可以在子线程中显示一个布局,或者在Activity中从onCreate直到onResume(包括onResume),都可以在子线程里面实现UI更新,并且不会有报异常,但如果在之后的生命周期中使用线程直接更新UI的话就会报CalledFromWrongThreadException的异常。
异常出现的原因跟一个叫ViewRootImpl的类有关,这个类有一个Thread类的属性mThread,该属性的值就是创建ViewRootImpl对象的线程,在执行某些方法的时候会检查当前线程和创建ViewRootImpl对象所在的线程是否为同一线程。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
Activity中ViewRootImpl对象的创建都是在UI线程中,所以mThread指向的就是main线程对象,并且Activity的ViewRootImpl对象的创建是在执行完Activity的onResume方法之后,所以在onResume之前(包括onResume),都可以在子线程操作UI,因为此时ViewRootImpl对象还没有创建,在onResume方法之后,子线程操作UI就会报异常了。
2.2小结
Android的单线程模型有两条规则:
1、不要阻塞UI线程
2、不要在非UI线程中操作UI控件。
3.Handler简单使用
第一种方式,采用sendEmptyMessage方法发送消息,然后在 handleMessage处理消息,并更新UI:
final Handler mhandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//得到这个消息标记开始处理消息
if (msg.what == 1001)
textView.setText("我更新了!");
}
};
new Thread(new Runnable() {
@Override
public void run() {
//发送消息
mhandler.sendEmptyMessage(1001);
}
}).start();
第二种方式,采用post方法发送消息,并直接在Runnable中设置UI更新操作:
mhandler.post(new Runnable() {
@Override
public void run() {
textView.setText("我更新了!");
}
});
4.Handler源码分析
第一节
我们按照第一种Handler使用方式分析。看sendEmptyMessage函数实现,代码路径为:
./frameworks/base/core/java/android/os/Handler.java
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
继续往下:我们看一下sendEmptyMessageDelayed函数的实现,代码文件为:
./frameworks/base/core/java/android/os/Handler.java
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
再往下看sendMessageDelayed函数的实现,代码路径为:
./frameworks/base/core/java/android/os/Handler.java
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
快好了,再耐心看下去,我们看sendMessageAtTime函数的实现,代码路径为:
./frameworks/base/core/java/android/os/Handler.java
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);
}
从这里我们可以看到,我们将消息送入队列了,我们再看看enqueueMessage函数,代码路径为:
./frameworks/base/core/java/android/os/Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
再往下看enqueueMessage的函数实现,代码路径为:
./frameworks/base/core/java/android/os/MessageQueue.java:
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.
/* *
*如果p为空,表明消息队列中没有消息,那么msg将是第一个消息,needWake
* 需要根据mBlocked的情况考虑是否触发
*/
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 (;;) {
// 如果p不为空,表明消息队列中还有剩余消息,需要将新的msg加到消息尾
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
// 因为消息队列之前还剩余有消息,所以这里不用调用nativeWakeup
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;
}
到了这里我们从源码得知我们将消息送入消息队列中了,但是主线程中如何从消息队列中取出消息呢?请看第二节说明。
第二节
我们从一切的源头开始讲起走,需要知道,当应用起来的时候会走到在ActivityThread.java main函数中,即所谓的主线程中,代码路径为:
./frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
再让我们走进loop函数去瞧瞧,代码路径为:
./frameworks/base/core/java/android/os/Looper.java:
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);
}
}
if (slowDispatchThresholdMs > 0) {
final long time = end - start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "Dispatch took " + time + "ms on "
+ Thread.currentThread().getName() + ", h=" +
msg.target + " cb=" + msg.callback + " msg=" + msg.what);
}
}
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();
}
}
从上面的代码中我们可以发现里面有个for循环,这里不断从消息队列中获取消息,然后将消息传递给dispatchMessage函数,我们再来看一下这个dispatchMessage函数的代码链接,代码路径为:
/frameworks/base/core/java/android/os/Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
当msg.callback不为空的情况下,我们走handleCallback(msg)分支,即现象post(Runnable)的run方法,为空的情况就是走handleMessage(msg)分支,即回调handleMessage方法,执行里面的函数,由于现在还是在主线程中,所以在回调的函数里,可以实现UI更新操作。分析msg.callback是否为空的话,可以看自行去看handler 的post(Runnable)方式,代码路径为:
./frameworks/base/core/java/android/os/Handler.java:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
可以发现post方法与sendEmptyMessage异曲同工之妙,不同的是前者是带getPostMessage®参数,后者是带常量what参数,我们再去看getPostMessage®做了什么,代码路径 ./frameworks/base/core/java/android/os/Handler.java:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
看到了这里懂了吧,这里给message添加callback属性,所以使用post方式的话msg.callback不为空。
5.Hanlder常见问题
问题提出
在下面代码我们可以看到loop函数中是个死循环,试问,为啥不会造成卡顿?
public static void main(String[] args) {
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
问题解决
请看这行代码:
throw new RuntimeException("Main thread loop unexpectedly exited");
如果loop不一直循环,那么执行完loop就会抛异常,导致程序退出。所以loop函数中的for循环有存在的必要性!我们继续看loop函数的代码,这里只展示里面部分代码:
public static void loop() {
....
for (;;) {
/**
*queue.next()中会判断,如果msg为空,会阻塞在native,线程休眠,等待新的消息唤醒,
*所以不会走在下面msg等于空退出的情况
*/
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
....
}
}
我们可以看到官方给 Message msg = queue.next();这行代码写的might block注释,这里就是为啥不会卡顿奥秘存在。我们追踪一下next函数的实现,为了方便阅读,只留下关键代码,代码路径为:
./frameworks/base/core/java/android/os/MessageQueue.java
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
...
}
}
我们主要看 nativePollOnce(ptr, nextPollTimeoutMillis);这一行代码,需要关心nextPollTimeoutMillis的值,当没有消息的时候是为-1,无限阻塞。这是一个nativie代码,但别担心,我去n源码中把他的实现提出来,代码路径为:
./system/core/libutils/Looper.cpp:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
"fd=%d, events=0x%x, data=%p",
this, ident, fd, events, data);
#endif
if (outFd != NULL) *outFd = fd;
if (outEvents != NULL) *outEvents = events;
if (outData != NULL) *outData = data;
return ident;
}
}
if (result != 0) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
if (outFd != NULL) *outFd = 0;
if (outEvents != NULL) *outEvents = 0;
if (outData != NULL) *outData = NULL;
return result;
}
result = pollInner(timeoutMillis);
}
}
其他先不管,主要看上面最后一行代码的pollInner函数的实现,已提取关键内容:
./system/core/libutils/Looper.cpp:
int Looper::pollInner(int timeoutMillis) {
...
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
}
这里通过epoll_wait调用,当mEpollFd监控的文件描述符没有IO事件发生时,线程会阻塞住且不会被CPU分配时间片了,如果有IO事件发生或事件超时了,该方法会返回了。
我们在看handler的sendmessage向消息队列发送消息,最终会调用到MessageQueue.java的enqueueMessage方法,代码路径为:
./frameworks/base/core/java/android/os/MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
...
if (needWake) {
nativeWake(mPtr);
}
...
}
当线程needWake时,说明当前线程挂起,需要唤醒线程,会调用nativeWake方法,代码路径为:
./system/core/libutils/Looper.cpp
void Looper::wake() {
ssize_t nWrite;
do {
nWrite = write(mWakeWritePipeFd, "W", 1);
} while (nWrite == -1 && errno == EINTR);
if (nWrite != 1) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}
往管道写端写入W,唤醒主线程。
小结:主线程不断的从消息队列中取出消息干活,当没有消息的时候呢,主线程休息。当又有消息的时候,就通过管道告诉主线程起来干活,正所谓劳逸结合,干活不累,哈哈,因此并不会造成程序卡顿。
6.总结
子线程中创建消息,消息会自动进入消息队列,等待主线程从消息队列中去取出消息干活,当消息队列没有消息的时候,主线程就休息去了,当消息队列又来消息的时候,就通过管道叫主线程起来干活。
7.补充
源码:Android 6.0
工具:Android studio
用到技能:java,C,linux shell
下一篇文章:关于Android系统源码下载及编译原理