Android Interview Progression Guide directory
Computer network
- HTTP quick
Android
- Interviewer: Task stack? Return stack? Boot mode? Can’t you tell the difference?
- Interviewer: the life cycle of the chatter Activity
- Interviewer: Tell me about Context
- Interviewer: Why can’t you display a Dialog using Application Context?
- Interviewer: Can outofMemoryErrors be try caught?
- Interviewer: Why did activity.Finish () wait 10 seconds before onDestroy?
- Interviewer: How do you monitor the FPS of your application?
- Interviewer: Why can view. post get the width and height of the View?
directory
- Quiz: Where can I get the width and height of a View?
- At what point in time is the View measured?
- Explaining the post ()
- How else can I get the view width and height?
- The last
Quiz: Where can I get the width and height of a View?
Today’s article will be more relaxed, compared to the previous few not so large section of the source code to gnaw. To get the width and height of the View, let’s test the code:
class MainActivity : BaseLifecycleActivity(a){
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// Get the width and height in onCreate()
Log.e("measure"."measure in onCreate: width=${window.decorView.width}, height=${window.decorView.height}")
// Get the width in the view.post () callback
binding.activity.post {
Log.e("measure"."measure in View.post: width=${window.decorView.width}, height=${window.decorView.height}")}}override fun onResume(a) {
super.onResume()
// Get the width in the onResume() callback
Log.e("measure"."measure in onResume: width=${window.decorView.width}, height=${window.decorView.height}")}}Copy the code
Most people can give a straightforward answer:
E/measure: measure in onCreate: width=0, height=0
E/measure: measure in onResume: width=0, height=0
E/measure: measure in View.post: width=1080, height=2340
Copy the code
The width and height are not available in onCreate() and onResume(), but in the view.post () callback. As you can see from the log print order, the print statement in the view.post () callback is executed last.
Let’s think about this for a moment. When can I get the width and height of a View? Of course, at least after the View is measured. From the results above, onCreate() and onResume() occur before this point, and the callback to viet.post () occurs after this point. All we have to do is figure out the timing, and the problem will be solved.
At what point in time is the View measured?
Everyone knows that the View drawing process takes place in the onResume process (not the activity.onResume () callback), but I decided to start at the beginning as a refresher.
When an app is launched in cold (the application process does not exist), it first establishes a socket connection with Zygote and sends the parameters needed to create the process to Zygote. Zygote server receives a parameter called after ZygoteConnection. ProcessOneCommand () processing parameters, and the fork out of the application process. Finally, findStaticMain() finds the Main() method of the ActivityThread class and executes, and the application process starts.
ActivityThread is not a thread class, but it runs on the main thread, so it doesn’t matter if you consider it the main thread. In the main() method, you create an ActivityThread object, call its Attach () method, and start the main thread message loop, and the event-based message queuing mechanism comes into play.
In the ActivityThread.attach() method, Binder calls ams.attachapplication (), which does two things:
- Binds the current process to AMS. Binder then calls back to the application process
ApplicationThread.bindApplication()
Method, prepare the client, create the Context, create the Application, and so on mStackSupervisor.attachApplicationLocked(app)
, and finally called torealStartActivityLocked()
Start the Activity
The startup process of the Activity will not be described. I have written an article about paoding ding ox Activity startup process before, if you are interested, you can take a look. Here jump straight to ActivityThread. PerformLaunchActivity () method.
> ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {.../ / get the ComponentNameComponentName component = r.intent.getComponent(); .// Create ContextImpl objectContextImpl appContext = createBaseContextForActivity(r); .Reflection creates an Activity objectactivity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); .// Create an Application object
Application app = r.packageInfo.makeApplication(false, mInstrumentation); .The PhoneWindow object is created in the attach method
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);
/ / execution onCreate ()
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else{ mInstrumentation.callActivityOnCreate(activity, r.state); }...return activity;
}
Copy the code
MInstrumentation. CallActivityOnCreate () method will eventually callback Activity. The onCreate () method. At this point, the setContentView() method is executed. The setContentView() logic is complicated, but what it does is straightforward. Create the DecorView, then parse the XML based on the layout file ID we passed in, and stuff the resulting view into the DecorView. Notice that so far all we have is an empty shell View tree, which is not added to the screen, and can’t be. So, getting the view width in the onCreate() callback is obviously not desirable.
After onCreate(), we skip onStart(), there’s nothing too important going on and go straight to onResume().
Note: The Activity lifecycle is scheduled by the ClientLifecycleManager class. See the source code for the Activity lifecycle.
> ActivityThread.java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {...// 1. Callback onResume
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
···
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
// 2. Add decorView to WindowManagerwm.addView(decor, l); . }Copy the code
Two things, call back onResume and add DecorView to WindowManager. So, getting the width and height of the view in the onResume() callback is no different from getting the width and height of the view in the onCreate() callback.
Wm. AddView (decor, l) final call to WindowManagerGlobal. The addView ().
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {...// 1. Key, initialize ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
// 2. Key, initiate drawing and display on screen
root.setView(view, wparams, panelParentView);
Copy the code
Both lines of code are important here. Take a look at the ViewRootImpl constructor in comment 1.
public ViewRootImpl(Context context, Display display) {...// 1. IWindowSession proxy object, which communicates with WMS with BindermWindowSession = WindowManagerGlobal.getWindowSession(); ./ / 2.
mWidth = -1;
mHeight = -1; .// 3. Initialize AttachInfo
// Remember that mAttachInfo is initialized here
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); .// 4. Initialize Choreographer with Threadlocal storage
mChoreographer = Choreographer.getInstance();
}
Copy the code
- Initializes mWindowSession, which can communicate with Binder WMS
- Here you can see that the width and height have not been assigned
- Initializing AttachInfo is important here and will be covered later
- Starting Choreographer, Previous Article Interviewer: How do I monitor my application’s FPS? In detail
Look again at the viewrootimpl.setView () method in comment 2.
> ViewRootImpl.java
// The view argument is a DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// 1. Initiate the first drawing
requestLayout();
// 2. Binder calls session.addtodisplay () to add window to the screen
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
// 3. Assign the parent of decorView to ViewRootImpl
view.assignParent(this); }}}Copy the code
The requestLayout() method initiates the first drawing.
> ViewRootImpl.java
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) {// Check the thread
checkThread();
mLayoutRequested = true;
/ / the keyscheduleTraversals(); }}Copy the code
ViewRootImpl. ScheduleTraversals () method is introduced in detail in the article, here to summarize:
ViewRootImpl.scheduleTraversals()
Method creates a synchronization barrier to prioritize asynchronous messages. throughChoreographer.postCallback()
Method submits the taskmTraversalRunnable
, this task is responsible for View measurement, layout, drawing.Choreographer.postCallback()
Methods byDisplayEventReceiver.nativeScheduleVsync()
Method registers the next time with the underlying systemvsync
Signal monitoring. The next timevsync
When it comes, the system will call it backdispatchVsync()
Method, the final callbackFrameDisplayEventReceiver.onVsync()
Methods.FrameDisplayEventReceiver.onVsync()
Method to retrieve the previously committedmTraversalRunnable
And executed. This completes the drawing process.
The doTraversal() method is performed in mTraversalRunnable.
> ViewRootImpl.java
void doTraversal(a) {
if (mTraversalScheduled) {
// 1. MTraversalScheduled set to false
mTraversalScheduled = false;
// 2. Remove the synchronization barrier
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 3. Start the layout, measurement and drawing processperformTraversals(); . }Copy the code
> ViewRootImpl.java
private void performTraversals(a) {...// 1. Bind Window
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
getRunQueue().executeActions(mAttachInfo.mHandler);
// 2. Request WMS to calculate window size
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
/ / 3. Measurement
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
4 / / layout
performLayout(lp, mWidth, mHeight);
/ / 5. Paint
performDraw();
}
Copy the code
The logic of the performTraversals() method is quite complex, so a few important method calls are condensed here. At this point, the overall drawing process of the View is complete, and there is no doubt that the width and height can be obtained at this point.
The time for the View to be measured has been found. Now let’s verify that view.post () executes the callback at this point.
Explaining the post ()
> View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if(attachInfo ! =null) {
// 1. AttachInfo is not empty and is sent through mHandler
return attachInfo.mHandler.post(action);
}
// 2. If attachInfo is empty, add it to the queue
getRunQueue().post(action);
return true;
}
Copy the code
The key here is whether attachInfo is empty. As mentioned in the previous section, let’s review:
attachInfo
Is in theViewRootImpl
Constructor of the,ViewRootImpl
Is in theWindowManagerGlobal.addView()
To create theWindowManagerGlobal.addView()
ActivityThreadhandleResumeActivity()
Is called in, but inActivity.onResume()
After the callback
So, if attachInfo is not empty, you are at least in the middle of the message processing for the view drawing. Send the Runnable to the post() Handler. When the Message containing the Runnable is executed, it must be able to get the View’s width and height.
In the onCreate() and onResume() callbacks, attachInfo must be empty and getRunQueue().post(action) is relied on. The principle is simple: store the Runnable to be executed by post() in a queue and pull it out at the appropriate time (the View has been measured). Let’s first see what queue getRunQueue() gets.
> View.java
private HandlerActionQueue getRunQueue(a) {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
Copy the code
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
public void post(Runnable action) {
postDelayed(action, 0);
}
// Send the task
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++; }}// Execute the task
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; }}...private static class HandlerAction {
final Runnable action;
final long delay;
public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}
public boolean matches(Runnable otherAction) {
return otherAction == null && action == null|| action ! =null&& action.equals(otherAction); }}}Copy the code
The HandlerActionQueue is an array of handlerActions with an initial size of 4. HandlerAction has two member variables, the Runnable to execute and the time to delay execution.
The execution logic of the queue is in the executeActions(Handler) method, where the task is distributed through an incoming handler. Now we just need to find when to call executeActions(). This can be found in view.java, where tasks are distributed in the dispatchAttachedToWindow() method.
void dispatchAttachedToWindow(AttachInfo info, int visibility) {...if(mRunQueue ! =null) {
// Assign tasks
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
/ / callback onAttachedToWindow ()
onAttachedToWindow();
}
Copy the code
For dispatchAttachedToWindow(), you can do a global search with Ctrl + F in this article. We saw this in the last video, and I’ll remind you to remember it, in the performTraversals() method.
> ViewRootImpl.java
private void performTraversals(a) {...// 1. Look here
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
getRunQueue().executeActions(mAttachInfo.mHandler);
// 2. Request WMS to calculate window size
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
/ / 3. Measurement
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
4 / / layout
performLayout(lp, mWidth, mHeight);
/ / 5. Paint
performDraw();
}
Copy the code
Note note 1. You might be a little confused by this. The width and height of the View can be obtained from dispatchAttachedToWindow().
First, you need to know that performTraversals() is performed during a message processing in the main thread message queue, Mrunqueue.executeactions (), which is indirectly called by dispatchAttachedToWindow(), also sends tasks to the main thread message queue via the Handler. It must then be executed after the performTraversals() method. So, there is no problem getting the width and height of the View here.
So here, the whole closed loop is formed, just to summarize.
View.post() performs different logic depending on whether the ViewRootImpl is already created. If the ViewRootImpl has been created, that is, mAttachInfo has been initialized, send a message directly to the Handler to perform the task. If the ViewRootImpl is not created, that is, the View hasn’t started drawing yet, it will save the task as a HandlerAction, and it will be stored in the HandlerActionQueue until the View starts drawing, When the performTraversal() method is executed, the task temporarily stored in the HandlerActionQueue is distributed by the Handler in the dispatchAttachedToWindow() method.
Also note that the View drawing takes place in a Meesage process, and the view.post () task takes place in a Message process, so they must be sequential.
How else can I get the view width and height?
In addition to getting the width and height of a View through view.post (), there are two recommended ways to do this.
The first, onWindowFocusChanged().
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if(hasFocus){ ... }}Copy the code
The second, OnGlobalLayoutListener.
binding.dialog.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener{
override fun onGlobalLayout(a) {
binding.dialog.viewTreeObserver.removeOnGlobalLayoutListener(this)... }})Copy the code
Either method can be called multiple times. OnWindowFocusChanged is called both when the Activity gains focus and when it loses focus. OnGlobalLayoutListener is also called multiple times when the state of the View tree changes, and listeners can be removed as needed.
The last
As an aside, the Android Interview Progression Guide is a paid column that I maintain at The Little Column and already has some paying users. This is the ninth article, in order to protect the rights of paying users, there is no way to synchronize all articles. If you’re interested in this column, poke in and take a look.
In recent years, several articles have introduced the relevant knowledge of View display principle and drawing process piecemeal. After that, a whole issue is prepared, which can be expected to be a smelly and long “foot-binding” article.
That’s it for this issue. I mean, I’ll see you next time.
In addition, you can also search my public account Bingxin said TM, follow more of my articles.