据CNBC报道,社交网络巨头Facebook已承认向61家科技公司提供了其用户数据的特殊访问权限,此前该公司曾在2015年公开表示限制此类访问。Facebook在上周五晚些时候提交给美国国会的747页文件中承认,该公司在2015年5月宣布限制上述做法后,继续与61家硬件和软件制造商分享用户信息。
本篇来自 豌豆射手_BiuBiu的投稿,分享了Android源码分析(View的绘制流程),一起来看看!希望大家喜欢。
豌豆射手_BiuBiu的博客地址:
https://www.jianshu.com/u/a58eb984bda4
源码基于安卓8.0分析结果
View是何时开始绘制的?Activity走了onCreate方法吗?这篇文章就是从程序的入口ActivityThread入口程序,去解释View中的measure()方法、View中的layout、View中的draw怎么开始调用的,非常有意思!虽然好多的技术文档,在半个月前已经做好了,这篇文章,对我自己来讲的话,是个很好的复习~~
为了更好地阐述着这篇文章,我这里就直接抛出结论了,为啥会这样的,在下篇文章会讲到,这里就记住一点,在Activity onResume后,调用了View onAttachedToWindow 才会开始View measure
Activity的生命周期和View的生命周期.jpg
为什么会这样子?先看ActivityThread类里面有个内部private class H extends Handler这就是系统的Handler,具体分析请看Android源码分析(Handler机制)
https://www.jianshu.com/p/a2c53e96cae6
里面有个case RESUME_ACTIVITY,获取焦点
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
handleResumeActivity()方法,这里只截取了关键的代码
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
}
mSomeActivitiesChanged = true;
//在这里执行performResumeActivity的方法中会执行Activity的onResume()方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManager.getService().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
//PhoneWindow在这里获取到
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//DecorView在这里获取到
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取ViewManager对象,在这里getWindowManager()实质上获取的是ViewManager的子类对象WindowManager
// TODO: 2018/5/24 WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
//获取ViewRootImpl对象
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
}
}
第一点分析得出performResumeActivity()肯定先于wm.addView(decor, l);执行的~这也是为啥我们 Activity先获取焦点了,才去绘制View。
performResumeActivity(),可以得出调用的是r.activity.performResume();
关于r.activity.performResume();;这里也可以,看出,在activity 中的fragment获取焦点要晚于activity,虽然这是常识。注意这个方法mInstrumentation.callActivityOnResume(this);;然后才会执行onPostResume;这也就是为什么,Activity先获取焦点,后执行onPostResume();
final void performResume() {
performRestart();
mInstrumentation.callActivityOnResume(this);
mCalled = false;
//这里也可以,看出,在activity 中的fragment获取焦点要晚于activity,虽然这是常识
mFragments.dispatchResume();
mFragments.execPendingActions();
onPostResume();
}
关注这个方法mInstrumentation.callActivityOnResume(this);果然不出所料,这里执行了activity.onResume();
既然在上面知道了,activity 获取焦点,会在上面执行,那么View的绘制就会在下面的函数中进行。
获取PhoneWindow; activity.getWindow(),Window类的唯一子类
获取window.getDecorView();DecorView,PhoneWindow的内部类,private final class DecorView extends FrameLayout ,安卓的事件分发和它密切相关Android源码分析(事件传递),也就是从Activity 传递到 ViewGroup的过程~~
获取ViewManager wm = a.getWindowManager();,其实也就是activity.getWindowManager(),也就是获取的是ViewManager的子类对象WindowManager,这里的知道WindowManager其实也是一个接口.
wm.addView(decor, l);,也就是到这里来了,WindowManager.addView(decor,l).
//PhoneWindow在这里获取到
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//DecorView在这里获取到
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取ViewManager对象,在这里getWindowManager()实质上获取的是ViewManager的子类对象WindowManager
// TODO: 2018/5/24 WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
//获取ViewRootImpl对象
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//在这里WindowManager将DecorView添加到PhoneWindow中
wm.addView(decor, l);
}
}
分析到这里来了,会通过WindowManager.addView(decor,l).我们需要去找WindowManager的实现。WindowManagerImpl;
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}
去寻找WindowManagerGlobal的addView()方法。这里有个单利模式,在源码好多地方使用的单利模式都是这样,并没有进行双重判断,在老牌的图片加载框架ImageLoader也是这样获取单利对象,如果想了解更多设计模式的姿势,可以看这片文章二十三种设计模式.
https://www.jianshu.com/p/4e01479b6a2c
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
在这里!就是WindowManagerGlobal.addView()的关键的方法,我做了两个注释,一个是view.setLayoutParams(wparams);,这个方法非常有意思,最近在研究ViewGroup的源码,发现不论什么情况下,View或者是ViewGroup都会有两次测量,这里是根本的原因,我先给结论。
api26:执行2次onMeasure、1次onLayout、1次onDraw。
api25-24:执行2次onMeasure、2次onLayout、1次onDraw,
api23-21:执行3次onMeasure、2次onLayout、1次onDraw,
api19-16:执行2次onMeasure、2次onLayout、1次onDraw,
API等级24:Android 7.0 Nougat
API等级25:Android 7.1 Nougat
API等级26:Android 8.0 Oreo
API等级27:Android 8.1 Oreo
后续我会做一篇文章详细解释下,为什么会这样,这里不过多的解释了,自提一句,非常有意思的代码!以前还会有两次的layout,说明谷歌也在优化安卓 framework。todo
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
//view setLLayoutParams()在这里
view.setLayoutParams(wparams);
try {
// TODO: 2018/6/4 这里呢?就是ViewRootImpl 调用的setView的方法,就在这里
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
ok,现在继续的关注这个方法ViewRootImpl.setView(view, wparams, panelParentView)
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) { try {
// TODO: 2018/6/4 这里传入的attrs 决定了View 或者是ViewGroup是否会onMeasure 两次
mWindowAttributes.copyFrom(attrs);
} catch (RemoteException e) {
// TODO: 2018/5/24 就会调动这里的来
unscheduleTraversals();
} finally {
if (restore) {
attrs.restore();
} if (res < WindowManagerGlobal.ADD_OKAY) {
// TODO: 2018/5/24 就会调动这里的来
unscheduleTraversals();}
}
unscheduleTraversals(),没有Activity获取焦点的时候,这个方法肯定会执行
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
关注mTraversalRunnable对象
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
doTraversal()方法,Traversal翻译过来就是遍历的意思~~
// TODO: 2018/5/24 到这里来了 ----> Traversal 遍历
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals这里就是整个View绘制的开始,所有的绘制,都会从这里开始,虽然这个方法代码有点多,但是关键的地方我都做了注释,下面一步一步的分析
如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要的窗口的宽度和高度
// 如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要的窗口的宽度和高度
//就是除了状态栏
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
//否者顶层视图DecorView所需要的窗口的宽度和高度就是整个屏幕的宽度
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
获得view宽高的测量规格
// TODO: 2018/5/25 //获得view宽高的测量规格,
// TODO: 2018/5/25 mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
注意这个对象WindowManager.LayoutParams lp ,如果说lp.horizontalWeight > 0.0f或者是lp.verticalWeight > 0.0f,那么measureAgain =true;horizontalWeight这个标记大概是这个意思指示额外空间的多少将被水平分配。如果视图指定0不应被拉伸。否则额外像素将被优先评估。在所有重量大于0的视图中。一般都指示出还有多少的水平的空间将要被分配。
/**
* 这个WindowMananger 这里标记了 是
*/
// TODO: 2018/6/4
WindowManager.LayoutParams lp = mWindowAttributes;
// TODO: 2018/5/25 这里是第一步的 执行测量的操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
如果这个measureAgain=true的话,就会再次调用performMeasure(),通过代码可以发现这就调用了两次performMeasure;
其实我这里犯了一个错误,不是这样的子,这个标记不一定是为true。
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
关于performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);方法,其实就是调用的是mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);,也就是View第一步是测量。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
第一次绘制的时候,这个标记一定是didLayout一定是true,一定会走到这个方法里面去performLayout(lp, mWidth, mHeight);
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// TODO: 2018/5/25 执行布局操作
performLayout(lp, mWidth, mHeight);
}
}
关于performLayout这个方法,直接会调用host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());,也就是View的layout的方法。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//测试层级
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
这两个标记也是!cancelDraw && !newSurface为true,那么就会走到performDraw();
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// TODO: 2018/5/25 执行绘制的操作
performDraw();
}
关于performDraw();方法,直接调用的是draw(fullRedrawNeeded);
private void performDraw() {
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
关于draw(fullRedrawNeeded);,会调用到这里来drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)
private void draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
关于这个方法drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)。官方的解释为如果绘图成功,如果发生错误,则为false。返回false的,程序就发生了异常,也就是程序GG掉了,绘制失败,这里仅仅贴出关键的代码~~~,这样字,就调用到了mView.draw(canvas);
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
// TODO: 2018/5/25 调用了View里面的draw方法
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
}
最后做了两张图(点击左下角的阅读原文可以看到清晰大图)
说明几点
如果感兴趣的,一定要去打个断点看一下这个流程
限于作者水平有限,一定会存在有些错误,还望指出,谢谢
欢迎长按下图 -> 识别图中二维码
或者 扫一扫 关注我的公众号