本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
前言
在开发的过程中,经常会遇到或者使用如下的代码,代码很稀松平常,感觉不到有什么可说的,但是隐藏在这些普通的方法之下,却又包含了很多的知识点需要我们注意,希望通过这篇文章来重新认识一下这几个方法,同时可以带来一些新的启发。
handler.post(new Runnable() {
@Override
public void run() {
System.out.println("Handler.post===");
}
});
// 代码2
textView.post(new Runnable() {
@Override
public void run() {
System.out.println("View.post===");
}
});
Handler.post()
Handler机制中有很多知识点是很重要的,比如说IdleHandler,同步屏障等等,当我们在通过handler发送消息时,除了sendMessage(),还可以通过post()的方式来发送消息。我们看一下post方法的源码是怎么实现的。
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
通过构造一个Message消息m,然后给消息m的callback属性赋值。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
在处理消息的时候,先判断callback属性是否为空,如果不为空,则执行callback的run方法,否则去执行handleMessage方法进行消息处理。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
//执行callback的run方法
private static void handleCallback(Message message) {
message.callback.run();
}
这样一看,handler的post方法原理其实也很简单,只是在构造message消息的时候,做了一些不同的处理而已。
View.post()
问题
首先想一个问题,当我们想在Activity的onCreate()方法中获取view的宽高时,应该怎么操作呢?我们知道要想获取一个view的宽高,首先view需要经过测量,也就onMeasure()之后,view才有了宽和高,这个方法是在哪里调用的呢?是到了onResume()方法以后才会调用。也就是说onCreate的时候,view实际上是没有宽高的。
view的测量时机在哪里
今天我们就从头开始扒一扒view从创建到测量的方法栈,我们知道ActivityThread的main()函数是整个App的入口,activity的onResume方法的源头大概似乎也应该在这里找一找,为什么找onResume,因为view是从onResume之后才开始进行绘制,测量方法的源头大概似乎也应该在这里找一找,为什么找onResume,因为view是从onResume之后才开始进行绘制的。
ActivityThread.java
//resume方法的源头
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// TODO Push resumeArgs into the activity for consideration
//调用activity的onResume方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
if (r.window == null && !a.mFinished && willBeVisible) {
...
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//添加decor到WindowManager中,这里会调用其实现类WindowManagerImpl的add方法
wm.addView(decor, l);
}
}
...
}
找到了handleResumeActivity这个方法,并在其中调用了ViewManager的addView方法,ViewManger是一个接口,通过其实现类WindowManagerImpl的addView方法来具体实现
WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
然后桥接给WindowManagerGlobla,执行WindowManagerGlobla的addView方法。
WindowManagerGlobla.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
//声明ViewRootImpl
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
...
// do this last because it fires off messages to start doing things
try {
//调用viewRootImpl的setView方法
root.setView(view, wparams, panelParentView);
}
...
}
}
用一张图来直观的看一下调用的线路:
从下边开始,就开始要进入界面的绘制渲染阶段了
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
//view的绘制流程
requestLayout();
//创建InputChannel
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
//通过windowSession进行IPC调用,将view添加到window上,
//同时通过InputChannel接收触摸事件回调
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
...
//处理触摸事件回调
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
...
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查是否是主线程
checkThread();
mLayoutRequested = true;
//执行绘制的操作
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//添加同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//向mChoreographer发送一个回调TraversalRunnable,等待接收vsync信号执行回调
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
//具体的回调
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//等待执行run方法
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
//进行绘制的
performTraversals();
...
}
}
private void performTraversals() {
...
//测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//布局
performLayout(lp, mWidth, mHeight);
//绘制
performDraw();
...
}
以上就是绘制的具体流程,只有当执行完performMeasure测量以后,view才有了宽高。我们接着上一个图来继续捋一捋。将一个调用的全过程简略的画一下。
view.post()源码分析
下面来看看view.post的具体实现,
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
//如果有attachInfo的话,就会调用handler的post方法,跟普通的消息没有什么区别
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
//如果attachInfo没有值,则执行下面的代码,放入队列中
getRunQueue().post(action);
return true;
}
//得到一个HandlerActionQueue的对象
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
//执行任务,发送任务都在这个类里了
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
//调用postDelayed方法,延迟时间为0
public void post(Runnable action) {
postDelayed(action, 0);
}
//发送一个任务
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
//执行任务
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
view的post方法很简单,没有多少代码,最终会通过postDelayed发送一个任务,通过executeActions执行一个任务,所以下一步就来看看executeActions这个方法在哪里执行就可以了。点进去发现在dispatchAttachedToWindow这个方法中会有调用,这个方法又在ViewRootImpl中有调用,我们再回头看看这个performTraversals方法。
ViewRootImpl.java
private void performTraversals() {
...
//执行任务的源头
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
//测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//布局
performLayout(lp, mWidth, mHeight);
//绘制
performDraw();
...
}
好了,我们已经找到源头了,在ViewRootImpl的performTraversals这个方法中,但是是在performMeasure这个方法之前调用的啊,为什么可以拿到宽高呢?
我们仔细看一下executeActions这个方法的实现
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
//通过handler发送
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
通过hendler发送消息的方式,最终将消息插入到消息队列中来执行。所以执行任务的方法最终执行的时机是在measure之后的,消息在队列中总要有个先来后到不是么。
结语
本文,我们分析了view的绘制流程、handler的post、view的post方法,通过这些分析,对view的整体绘制流程应该有了一个宏观的印象了吧,当然view的绘制牵涉到很多其他的知识,我只是将其中的一部分拿出来说明而已,为什么可以在view的post方法中可以拿到宽高,相信你也有了答案。这些源码其实并不难,只要花费精力去研究它,就会有所收货和启发。