preface
Some time ago, the company was recruiting people. As the interviewer, I often asked the interviewees to briefly describe the drawing process of View. They can basically explain the process of View measurement, layout, draw and so on. A few others will mention the use of DecorView and ViewRootImp. But when I continued to ask about Windows, almost no one answered. This chapter will take you through Windows, DecorView, and ViewRootImp. In addition, you will find answers to the following questions in this chapter:
- Why design Windows?
- Is it true that child threads can’t update the UI?
- Why can’t we get the width and height of the View in the Activity’s onCreate method?
Illustrate the process of launching an Activity into a View
The above image shows five objects: Activity, Window, WindowManager, DecorView, ViewRootImpl, and I’ll explain what they do.
-
Activity: An Activity is like a commander. It does not handle specific transactions, but directs Windows /WindowManager when appropriate. For example, create a Window object while attaching, notify WindowManager after onResume to add a view.
-
Window: Window is a Window that is the container for the View. Views in Android are organized in the form of a View tree, which must be attached to the Window to work. A Window corresponds to a View tree. A Window is created when the Activity is started and a Window is created when the Dialog is displayed. Therefore, an Activity can have multiple Windows inside it. Since views are measured, laid out, and drawn only within the View tree, changes to a View in one Window do not affect another Window. Window is an abstract class that has only one implementation of PhoneWindow.
-
WindowManager: WindowManager is the administrative class for Windows. It does not operate directly on the Window, but on the DecorView inside the Window. WindowManager is an interface. Its concrete implementation class is WindowManagerImpl.
public interface WindowManager{ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); } Copy the code
-
DecorView is the top-level View of the View tree, which is a subclass of FrameLayout. The DecorView has a different layout depending on the Theme set to the Activity. But no matter how the layout changes, the DecorView has a FrameLayout with Id R.I.D.C tent. The activity.setContentView () method adds a child View to this FrameLayout.
-
The ViewRootImpl is the link between Windows Manager and the DecorView, and all three of the View’s processes are done through ViewRootImpl.
The source code to decrypt
In the Android plug-in for Starting an Activity, I described the process of starting an Activity. The handleLaunchActivity() method is the core method to start the Activity. This section takes it as a starting point for analysis.
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// omit code...
//performLaunchActivity
Activity a = performLaunchActivity(r, customIntent);
if(a ! =null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
//handleResumeActivity
handleResumeActivity(r.token, false, r.isForward,! r.activity.mFinished && ! r.startsNotResumed);// omit code...}}Copy the code
HandleLaunchActivity () mainly calls two methods: performLaunchActivity() and handleResumeActivity().
-
PerformLaunchActivity: Completes the creation of the Activity and calls the Activity’s onCreate() and onStart() methods.
-
HandleResumeActivity: Calls the Activity’s onResume() method to handle the rendering of the View.
performLaunchActivity
We enter the performLaunchActivity() method with the following core code:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
ComponentName component = r.intent.getComponent();
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
/ / create the Activity
Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if(activity ! =null) {
/ / create the Context
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
// Call activity.attach.
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);
// omit code...
// Call the activity.oncreate () method.
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
r.activity = activity;
if(! r.activity.mFinished) {// Call the activity.onstart () method.
activity.performStart();
}
}
r.paused = true;
mActivities.put(r.token, r);
return activity;
}
Copy the code
PerformLaunchActivity () does the following:
- Create the Activity.
- To create a Context.
- Call Activity.attach() to create the Window and associate it with WindowManager.
- Call the Activity. The onCreate ().
- Call the Activity. The onStart ().
attach
As mentioned above, the Window is created when the activity.attach () method is executed and the Window is associated with a WindowManager. Let’s take a look at the pseudocode.
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) {
attachBaseContext(context);
/ / create the Window
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if(info.softInputMode ! = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); }if(info.uiOptions ! =0) {
mWindow.setUiOptions(info.uiOptions);
}
// Set up the UI thread
mUiThread = Thread.currentThread();
mMainThread = aThread;
/ / associated WindowManagermWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) ! =0);
if(mParent ! =null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
}
Copy the code
setContentView
Following the flowchart, the activity.setContentView () method is called. Activity. The setContentView () is called directly again PhoneWindow. The setContentView (). We see PhoneWindow directly. The setContentView () in the source code.
private DecorView mDecor;
// The View passed by setContentView is added to mContentParent. The Id of the mContentParent is R.D.C. tent.
private ViewGroup mContentParent;
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
}else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }// omit code...
}
private void installDecor(a) {
if (mDecor == null) {
// Initialize the 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);
// omit code...}}Copy the code
If mContentParent is null, the installDecor() method is executed. The generateDecor() source code is quite long, but its logic is very simple and mainly depends on the Activity Theme to initialize different layouts. The layout of the DecorView is different, but they all have a FrameLayout with Id R.I.D.C tent. Activity.setcontentview () adds a child View to this FrameLayout.
handleResumeActivity
PerformLaunchActivity () is followed by handleResumeActivity().
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// Process the Activity's onRestart onResume lifecycle.
ActivityClientRecord r = performResumeActivity(token, clearHide);
if(r ! =null) {
if (r.window == null && !a.mFinished) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
// Make the DecorView invisible
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
// Add a DecorView using WindowManager.wm.addView(decor, l); }}if(! r.activity.mFinished && r.activity.mDecor ! =null) {
if (r.activity.mVisibleFromClient) {
// Set DecorView visibility.r.activity.makeVisible(); }}// an IPC call notifies AMS Activity that it has started.
ActivityManagerNative.getDefault().activityResumed(token);
} else {
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null.false);
} catch (RemoteException ex) {
}
}
}
Copy the code
HandleResumeActivity () handles a lot of things. I summarize the following processes:
- PerformResumeActivity () handles the Activity’s onRestart onResume lifecycle.
- Set DecorView to InVisible.
- The DecorView is drawn with WindowManager.addView().
- Set DecorView to Unavailable.
- Notifies AMS Activity that it has started.
WindowManger.addView()
As mentioned earlier, windowmanager is an abstract class whose implementation class is WindowManagerImpl. Windowmanager.addview () encapsulates the details of how the View is drawn. Let’s focus on that.
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
// The View here is a DecorView created in PhoneWindow.
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; }}}}Copy the code
WindowManagerImpl. Will call addView WindowManagerGlobal. AddView (). In WindowManagerGlobal. AddView () method before execution, will first perform applyDefaultToken () method. This method essentially adds an identity to the passed DecorView, indicating which Activity the DecorView belongs to. This way the system (WindowManagerService) knows which Activity to draw the DecorView to.
We continue to track WindowManagerGlobal. AddView (), pseudo code is as follows:
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
// The View here is a DecorView created in PhoneWindow.
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
// omit the code....
root = new ViewRootImpl(view.getContext(), display);
// omit code...
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
}
Copy the code
The ViewRootImpl is created first, then the View, ViewRootImpl, and LayoutParams are saved in the List for future UI updates. They have the same index, so they correspond to each other. Finally, call the viewrootimpl.setView () method.
ViewRootImpl.setView()
public class ViewRootImpl{
View mView;
// The View here is a DecorView created in PhoneWindow.
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// omit code...
// 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();
// omit code...
//IPC communication notifies WMS of rendering.
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
// omit code...}}}@Override
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) {// Check whether the currently executing thread is a UI thread
checkThread();
mLayoutRequested = true;
// Process the measure, layout, and draw of the DecorView.scheduleTraversals(); }}void checkThread(a) {
if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."); }}}Copy the code
Viewrootimpl.setview () has two lines of pseudocode, but we only care about requestLayout. Because mWindowSession. AddToDisplay inform WMS to render () is through the IPC, we’ll analyze WMS has little meaning. The requestLayout() method first checks whether the currently executing thread is a UI thread and then calls scheduleTraversals(). ScheduleTraversals encapsulates the request into a TraversalRunnable object, which is then passed to a Handler. Finally ViewRootImpl. PerformTraversals () is invoked. The call chain is as follows:
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled =true;
// omit code...
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
// omit code...}}final class TraversalRunnable implements Runnable {
@Override
public void run(a) { doTraversal(); }}void doTraversal(a) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// Process the measure, layout, and draw of the DecorView.
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false; }}}Copy the code
PerformTraversals () mainly deals with measure, layout, draw and other processes of View tree. If you don’t know, you can go to Chapter 4 of Android Development Art Exploration, but I won’t go into it here.
conclusion
Let me answer some of the questions raised in the preface.
-
Why design Windows?
To understand Windows, you need to start from an object-oriented perspective.
- If there is no Window, the Window management View tree code must be placed in the Activity. This makes the Activity very large, which goes against the role of the Activity commander we discussed earlier.
- After encapsulating the management of the View tree to the Window, the Activity only needs to show and hide the Window when calling dialog.show (), dialog.hide () and other Window switches.
- View measurement, layout and drawing are only carried out in the View tree, and a View tree is encapsulated in a Window to facilitate View management.
-
Is it true that child threads can’t update the UI?
When updating a view, thread checking is done in the ViewRootImpl’s checkThread(). The ViewRootImpl is initialized after the Activity’s onResume() method. Therefore, if a child thread updates the UI before onResume, it can succeed. There is also a Hook viewrotimPL mThread method to update the UI. No more introductions here.
-
Why can’t the Activity’s onCreate method get the width and height of the View?
This problem is similar to the problem of child threads not being able to update the UI, and is also an issue of method execution timing. View measure, layout, draw. Occurs after activity.onresume (), so the View’s width and height are not available until onResume().