1.问题
1.xml中的布局是在什么时候渲染到屏幕上的?
2.子线程可以更新ui吗?
2.源码分析
写在前面:以下源码只截取了部分核心部分,sdk部分是java代码(基于android-29,不同版本代码可能有所不同,但是核心逻辑不会有太大变化),手写部分使用kolin语言。
<LinearLayout xmlns:tools="http://schemas.android.com/tools">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
}
虽然从源码点过去是AppCompatActivity,但最终仍然是执行Activity中的setContentView方法,所以我们直接看Activity中的代码
//package android.app.Activity
public void setContentView(@LayoutRes int layoutResID) {
//!!!这里是核心代码
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
Window是一个抽象类,按理来说我们应该找到它具体的实现类,以及初始化的地方,由于Window在Android中实际上只有一个实现类PhoneWindow,所以我们直接进入PhoneWindow。
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
//将activity中传进来的布局添加到mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
private void installDecor() {
if (mDecor == null) {
//创建DecorView对象
mDecor = generateDecor(-1);
}
...
if (mContentParent == null) {
//添加一个系统布局R.layout.screen_simple到DecorView中,并找到id为com.android.internal.R.id.content的布局赋值给mContentParent
mContentParent = generateLayout(mDecor);
}
在PhoneWindow调用了setContentView方法后,会创建一个DecorView对象,同时添加一个系统布局到DecorView中,接着将Activity传进来的布局添加到其中,大致是这样的
到此为止,布局虽然已经全部添加完毕,但是我们知道,要想正常显示到屏幕上,必然是要进行测量、布局、绘制才能正确的渲染到指定的位置。我们知道,所有进程都是从main方法开始执行的,Android进程也不例外,所以我们找到ActivityThread.java的main方法。
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
Looper.loop();
}
这里会进行一些初始化操作,创建application实例,设置Looper等,但是本文重点不在这里,所以也不做过多解释。当一个Activity被启动时,会执行ActivityThread#handleLaunchActivity()
//ActivityThread.java
public Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {
...
final Activity a = performLaunchActivity(r, customIntent);
...
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback,r.assistToken);
}
//Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
...
//创建PhoneWindow对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//创建WindowMananger对象(实际上是WindowManagerImpl对象)
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
...
}
接着会执行ActivityThread#handleResumeActivity(),而真正的绘制也是从这里开始的
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
//这里最终会执行Activity的onResume方法,而此时绘制其实还没有开始
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
}
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//获取activity对应phonewindow中创建的DecorView对象
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//得到上一步中创建的windowManage对象
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;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
...
//将Decorview添加到windowManager对象中,由上一步可知,这里其实是windowManagerImpl对象
wm.addView(decor, l);
于是接着看windowManagerImpl的addView方法做了什么
//android/view/WindowManagerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
//android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//global是作为单例对象使用的,下面用几步用集合保存一些数据信息
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//!!!这里会将ViewRootImpl与DecorView关联起来
root.setView(view, wparams, panelParentView);
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
mWindowAttributesChanged = true;
mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
mAttachInfo.mRootView = view;
mAttachInfo.mScalingRequired = mTranslator != null;
mAttachInfo.mApplicationScale =
mTranslator == null ? 1.0f : mTranslator.applicationScale;
...
//!!!这里即将开始首次绘制流程
requestLayout();
...
setFrame(mTmpFrame);
}
接下来就是我们比较熟悉的绘制过程了
//android/view/ViewRootImpl.java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//这里会检查ViewRootImpl创建时的线程与当前方法执行的线程是否一致(这里也是我们常说的子线程不能更新ui导致报错的地方)
checkThread();
mLayoutRequested = true;
//!!!这里是调度绘制任务的地方
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//!!! mTraversalRunnable是一个异步任务
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
...
performTraversals();
...
}
private void performTraversals() {
...
//!!!这里正是开始测量(可能会执行多次),最终会执行mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//!!!这里开始布局,最终会执行mView.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
performLayout(lp, mWidth, mHeight);
...
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
//通过这个监听去获取view的尺寸是一个比较合适的时机
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
...
//!!!这里开始绘制
performDraw();
}
3.总结
1.xml中的布局是在什么时候渲染到屏幕上的? 答:大概时间是在Activity的生命周期onResume之后
2.子线程可以更新ui吗? 答:子线程是可以更新ui的,只要能绕过ViewRootImpl中的checkThread方法或者满足checkThread的检查条件就可以
本人水平有限,如有错误请留言指正。如果本文对你有一点点帮助,请随手点个赞鼓励一下吧