(1)主线程为什么不能进行耗时的操作。
大家都知道在Android中,主线程中不能进行耗时的操作,比如请求网络,可能会报NetworkOnMainThreadException。这是为什么呢?
这是因为:
一些耗时的操作,比如(访问网络,下载数据,查询数据库等),很容易造成主线程的阻塞,导致事件停止分发(包括绘制事件)。轻则降低用户体验,更坏的情况是,如果主线程阻塞超过5秒,就会导致ANR。
(2)子线程为什么不能更新UI?
在子线程中更新UI,会抛出:
Only the original thread that created a view hierarchy can touch its views.
从at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7905)这个异常路径,我们可以看出是在ViewRootImpl这个类中做了判断,
我们一起来看一下这个类中是怎样判断的?
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
我们看到在ViewRootImpl的requestLayout方法中调用了checkThread()方法:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
那么我再向上追溯一下,看这个checkThread到底是在什么调用的:
我们都知道一个App进程在启动的时候会创建一个主线程,在ActivityThread类里面管理这个主线程。
里面有一个handleResumeActivity方法:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
........
ViewManager wm = a.getWindowManager();
..........
wm.addView(decor, l);
}
这里我们可以看到通过activity获取得到Activity里面的WindowManger,而WindowManager类是一个接口,它的实现类是WindowManagerImpl:
public interface WindowManager extends ViewManager {
}
所以这里的wm.addView(decor,l)实际上调用的是WindowManagerImpl类里面的addView方法,我们来看一下WindowManagerImpl类里面的addView方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
发现这里又调用了WindowManagerGlobal中的addView方法,我们看一下WindowManagerGlobal中的addView方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
.........
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
.........
}
我们发现在WindowManagerGlobal这个类的addView方法中创建了ViewRootImpl这个类的实例,并调用了ViewRootImpl中的setView方法,来看一下这个方法的实现:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
.........
requestLayout();
.........
}
方法很长,我们只看里面的关键代码,调用了requestLayout这个方法,好了,到这就和我们上面分析的对上了。:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
由此,我们也可以看出,子线程也可以更新UI,只要在Activity的onResume之前修改就不会抛出这个异常了。Android UI操作并不是线程安全的,并且这些操作必须在UI线程执行。
(3)Looper在主线程中死循环为什么没有导致界面的卡死?
1.导致卡死的是在UI线程执行耗时操作导致页面出现掉帧,设置ANR,Looper.loop()这个操作本身不会导致这个情况。
2.有人可能会说,我在点击事件中设置死循环会导致页面卡死,同样是死循环,不都一样的吗?Looper会在没有消息的时候阻塞当前线程,释放CPU资源,等到有消息的时候,再唤醒主线程。
3.App进程中是需要死循环的,如果循环都结束了的话,APP进程也就结束了。
(4)Handler是如何做到延迟发送的?
我们以handler.sendMessageAtTime();发送消息的方法为例,来说一下Handler是怎么样做到延迟发送的:
public boolean sendMessageAtTime(@NonNull 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);
}
Handler里面的sendMessageAtTime最终是调用了handler的enqueueMessage方法,并把延迟的时间当做参数传给了这个方法:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
而handler的enqueueMessage方法最终又调用了MessageQueue里面的enqueueMessage方法,并把延迟的时间也传给了这个方法:
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;
}
我们发现,这里是把这个延迟的时间赋值给了Message的when。这个是消息入队的方法。
再看一下从消息对列中取消息的方法:Looper.looper
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;
................
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
}
}
发现从队列中取Message是调用了MessageQueue的next方法:
Message next() {
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 {
.....
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
......
}
}
注意这里的nativePollOnce(ptr, nextPollTimeoutMillis);这里是一个 for 死循环,唤醒后计算出根据now 和msg.when,如果now < msg.when 则计算出nextPollTimeoutMillis,下一个循环赋值给nativePollOnce(ptr,nextPollTimeoutMillis),告诉阻塞时间:
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 {
.....
}
注意:Handler 只是利用到了 native 层的 NativeMessageQueue和 native 层的 Looper 来完成阻塞和唤醒工作。消息队列的插入和消息的读取都是在 Java 层完成的。
网友评论