Android高工面:Activity创建到View呈现中间发生了什么?子线程到底能不能更新UI?

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
}else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
//省略代码。。。
}

private void installDecor() {
if (mDecor == null) {
//初始化DecorView
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//省略代码。。。
}
}

如果mContentParent为null,会执行installDecor()方法。generateDecor()源代码很长,但是它的逻辑很简单主要是根据Activity Theme的不同,初始化不同的布局。DecorView的布局虽然不同,但它们都一个Id为R.id.content的FrameLayout。Activity.setContentView()就是在这个FrameLayout中添加子View。

handleResumeActivity

performLaunchActivity()执行完后,紧接着执行handleResumeActivity()。

final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {

//处理Activity的onRestart onResume生命周期。
ActivityClientRecord r = performResumeActivity(token, clearHide);

if (r != null) {
if (r.window == null && !a.mFinished) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
//设置DecorView不可见
decor.setVisibility(View.INVISIBLE);

ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();

if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//利用WindowManager添加DecorView。
wm.addView(decor, l);
}
}
if (!r.activity.mFinished && r.activity.mDecor != null) {
if (r.activity.mVisibleFromClient) {
//设置DecorView可见。
r.activity.makeVisible();
}
}

//IPC调用,通知AMS Activity启动完成。
ActivityManagerNative.getDefault().activityResumed(token);

} else {
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null, false);
} catch (RemoteException ex) {
}
}
}

handleResumeActivity()处理的事情比较多。我总结为以下几个过程:

  1. 通过performResumeActivity()处理Activity的onRestart onResume的生命周期。
  2. 将DecorView设置为InVisible。
  3. 通过WindowManager.addView()将DecorView绘制完成。
  4. 将DecorView设置为Visiable。
  5. 通知AMS Activity启动完成。

WindowManger.addView()

前面说过,WindowManger是一个抽象类,它的实现类是WindowManagerImpl。WindowManager.addView()封装了View绘制的细节。我们着重看一下。

public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Display mDisplay;
private final Window mParentWindow;

private IBinder mDefaultToken;

public WindowManagerImpl(Display display) {
this(display, null);
}

private WindowManagerImpl(Display display, Window parentWindow) {
mDisplay = display;
mParentWindow = parentWindow;
}

public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}

@Override
//这里的View是PhoneWindow内创建的DecorView。
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}

private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
if (mDefaultToken != null && mParentWindow == null) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
}

WindowManagerImpl.addView会调用WindowManagerGlobal.addView()。在WindowManagerGlobal.addView()方法执行之前,会先执行applyDefaultToken()方法。这个方法其实是给传进来的DecorView加一个身份标识,表示这个DecorView属于哪个Activity。这样系统(WindowManagerService)才会知道要把DecorView绘制到哪个Activity。

我们继续追踪WindowManagerGlobal.addView(),伪代码如下:

private final ArrayList mViews = new ArrayList();
private final ArrayList mRoots = new ArrayList();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

//这里的View是PhoneWindow内创建的DecorView。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//省略代码…

root = new ViewRootImpl(view.getContext(), display);

//省略代码…

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

root.setView(view, wparams, panelParentView);
}

首先会创建ViewRootImpl,随后把View、ViewRootImpl、LayoutParams都保存在List中,以供将来更新UI使用。它们的index值相同,这样就三者就对应起来了。最后,调用ViewRootImpl.setView()方法。

ViewRootImpl.setView()

public class ViewRootImpl{

View mView;
//这里的View是PhoneWindow内创建的DecorView。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;

//省略代码。。。

// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();

//省略代码。。。

//IPC通信,通知WMS渲染。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);

//省略代码。。。
}
}
}

@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查当前执行的线程是不是UI线程
checkThread();
mLayoutRequested = true;
//处理DecorView的measure、layout、draw。
scheduleTraversals();
}
}

void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
“Only the original thread that created a view hierarchy can touch its views.”);
}
}
}

ViewRootImpl.setView()的伪代码有两句,但我们只关心requestLayout。因为mWindowSession.addToDisplay()就是通过IPC通知WMS去渲染,我们再去分析WMS意义已经不大了。requestLayout()方法首先会检查当前执行的线程是不是UI线程,随后调用scheduleTraversals()。scheduleTraversals会把本次请求封装成一个TraversalRunnable对象,这个对象最后会交给Handler去处理。最后ViewRootImpl.performTraversals()被调用。调用链如下:

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//省略代码。。。
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//省略代码。。。
}
}

final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

//处理DecorView的measure、layout、draw。
performTraversals();

if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

performTraversals()主要是处理View树的measure、layout、draw等流程。不清楚的同学可以去看《Android开发艺术探索》第四章,我在这里就不继续深入了。

总结

下面我回答文章前言部分提出的几个问题。

  • 为什么要有设计Window?

要理解Window需要从面向对象的角度出发。

  1. 假如没有Window,那Window管理View树的代码必然会放到Activity中。这样Activity就变得十分庞大,这与我们前面说的Activity指挥官的角色相违背。
  2. 把View树的管理工作封装到Window后,在调用Dialog.show()、Dialog.hide()等Window切换时,Activity只需要负责Window的显示和隐藏即可。
  3. View的测量、布局、绘制只是在View树内进行的,把一个View树封装在一个Window中方便视图管理。
  • 子线程真的不能更新UI吗?

更新视图时,线程检查是在ViewRootImpl的checkThread()中。ViewRootImpl的初始化是在Activity的onResume()方法之后。因此,如果有子线程在onResume之前更新UI是可以成功的。当然还有一种Hook ViewRootImpl的mThread的方法也可以更新UI。这里不做介绍了。

  • Activity的onCreate方法为什么无法获取View的宽和高?

这个问题和子线程不能更新UI的问题很像,也是方法执行时机的一个问题。View的measure、layout、draw。发生在Activity.onResume()之后,因此在onResume()之前都是无法获取View的宽、高等信息的。

文末

对文章有何见解,或者有何技术问题,都可以在评论区一起留言讨论,一定会回复的。
也欢迎大家来我的B站找我玩,各类Android架构师进阶技术难点的视频讲解,任君白嫖~

最后

都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。

技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值