SoFunction
Updated on 2025-03-11

Android's principles and flaws

Many developers know this knowledge point: in ActivityonCreateIn the method, we cannot directly obtain the width and height information of the View, but through(Runnable)This method is OK. Have you ever understood the specific reasons behind it?

Readers can try the following. Can be found, except through(Runnable)This way can obtain the true width and height of the View, and the values ​​obtained by other methods are 0

/**
  * Author: leavesC
  * Time: 2020/03/14 11:05
  * describe:
  * GitHub:/leavesC
  */
class MainActivity : AppCompatActivity() {

 private val view by lazy {
  findViewById<View>()
 }

 override fun onCreate(savedInstanceState: Bundle?) {
  (savedInstanceState)
  setContentView(.activity_main)
  getWidthHeight("onCreate")
   {
   getWidthHeight("")
  }
  Handler().post {
   getWidthHeight("handler")
  }
 }

 override fun onResume() {
  ()
  getWidthHeight("onResume")
 }

 private fun getWidthHeight(tag: String) {
  (tag, "width: " + )
  (tag, "height: " + )
 }

}
 E/onCreate: width: 0
 E/onCreate: height: 0
 E/onResume: width: 0
 E/onResume: height: 0
 E/handler: width: 0
 E/handler: height: 0
 E/: width: 263
 E/: height: 263

This leads to several questions:

  • (Runnable)Why can you get the true width and height of the View
  • (Runnable)and(Runnable)What's the difference
  • existonCreateonResumeWhy can't you directly get the true width and height of the View in the function?
  • (Runnable)Who executes the Runnable in it? Can it be guaranteed to be executed?

Let’s answer these questions one by one. This article is based on Android API 30 for analysis.

1. (Runnable)

Take a look(Runnable)The method signature of Runnable can be seen in two types:

  • ifmAttachInfoNot null, then the Runnable is handed over tomAttachInfoInternal Handler handles
  • ifmAttachInfonull, the Runnable is handed over to HandlerActionQueue for processing
 public boolean post(Runnable action) {
  final AttachInfo attachInfo = mAttachInfo;
  if (attachInfo != null) {
   return (action);
  }
  // Postpone the runnable until we know on which thread it needs to run.
  // Assume that the runnable will be successfully placed after attach.
  getRunQueue().post(action);
  return true;
 }

 private HandlerActionQueue getRunQueue() {
  if (mRunQueue == null) {
   mRunQueue = new HandlerActionQueue();
  }
  return mRunQueue;
 }

1、AttachInfo

Let's take a look first(Runnable)The first processing logic

AttachInfo is a static class inside the View, which holds a Handler object inside. From the comments, it can be seen that it is provided by ViewRootImpl.

final static class AttachInfo {
 
  /**
   * A Handler supplied by a view's {@link }. This
   * handler can be used to pump events in the UI events queue.
   */
  @UnsupportedAppUsage
  final Handler mHandler;

 	AttachInfo(IWindowSession session, IWindow window, Display display,
    ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
    Context context) {
   ···
   mHandler = handler;
   ···
 	}
 
 	···
}

FindmAttachInfoThe assignment time can be traced to the ViewdispatchAttachedToWindowMethod, the method is called means that the View has been Attached to the Window

	@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
  mAttachInfo = info;
  ···
 }

Search againdispatchAttachedToWindowThe method is called and can be tracked to the ViewRootImpl class. ViewRootImpl contains a Handler objectmHandler, and in the constructormHandlerInitialize as one of the constructor parametersmAttachInfo. ViewRootImpl'sperformTraversals()The method will call the dispatchAttachedToWindow method of DecorView and pass in mAttachInfo, thereby calling all Views in the entire view tree layer by layerdispatchAttachedToWindowMethod so that all childViews can be obtainedmAttachInfoObject

	final ViewRootHandler mHandler = new ViewRootHandler();

 public ViewRootImpl(Context context, Display display, IWindowSession session,
      boolean useSfChoreographer) {
  ···
  mAttachInfo = new (mWindowSession, mWindow, display, this, mHandler, this,
    context);
  ···
 }

 private void performTraversals() {
  ···
  if (mFirst) {
   ···
   (mAttachInfo, 0);
 	 ···
  }
  ···
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  performLayout(lp, mWidth, mHeight);
  performDraw();
  ···
 }

also,performTraversals()The method is also responsible for starting the Measure, Layout, and Draw processes of the entire view tree, only whenperformLayoutOnly after being called can the View determine its width and height information. andperformTraversals()It is also called by ViewRootHandler, that is, the drawing task of the entire view tree is first inserted into the MessageQueue, and then the main thread takes out the task for execution. Since the messages inserted into the MessageQueue are executed sequentially by the main thread,(action)This ensures that action will not be called after performTraversals is executed, so we can get the true width and height of the View in Runnable

2、HandlerActionQueue

Let's see again(Runnable)The second processing logic

HandlerActionQueue can be regarded as a task queue specially used to store Runnables.mActionsAll Runnables to be executed and the corresponding delay time are stored. twopostThe method is used to save the Runnable object to be executed into mActions.executeActionsBe responsible formActionsAll tasks in the Handler are submitted to the Handler for execution

public class HandlerActionQueue {
 
 private HandlerAction[] mActions;
 private int mCount;

 public void post(Runnable action) {
  postDelayed(action, 0);
 }

 public void postDelayed(Runnable action, long delayMillis) {
  final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
  synchronized (this) {
   if (mActions == null) {
    mActions = new HandlerAction[4];
   }
   mActions = (mActions, mCount, handlerAction);
   mCount++;
  }
 }

 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];
    (, );
   }

   mActions = null;
   mCount = 0;
  }
 }

 private static class HandlerAction {
  final Runnable action;
  final long delay;

  public HandlerAction(Runnable action, long delay) {
    = action;
    = delay;
  }

  public boolean matches(Runnable otherAction) {
   return otherAction == null && action == null
     || action != null && (otherAction);
  }
 }
 
 ···
 
}

So,getRunQueue().post(action)Just save the Runnable object we submitted tomActionsIn the meantime, it also requires active external callsexecuteActionsMethods to execute tasks

This operation that actively executes tasks is also from ViewdispatchAttachedToWindowto complete, so thatmActionsAll tasks in themHandlerIn the MessageQueue, wait until the main thread completes executionperformTraversals() The method will be executed afterwardsmActions, so at this time we can still obtain the true width and height of the View

	@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
  mAttachInfo = info;
  ···
  // Transfer all pending runnables.
  if (mRunQueue != null) {
   ();
   mRunQueue = null;
  }
  ···
 }

2. (Runnable)

(Runnable)and(Runnable)What's the difference?

From the above source code analysis, you can know that(Runnable)The reason why you can get the true width and height of the View is mainly because the operation of obtaining the width and height of the View must be executed after the View is drawn, and(Runnable)The reason why it doesn't work is that it cannot guarantee this

Although these two post(Runnable) operations are inserted into the same MessageQueue, they are eventually handed over to the main thread for execution. However, the task of drawing the view tree is submitted only after onResume is called back, so the task we submit with Handler in onCreate will be earlier than the task of drawing the view tree, so we cannot get the true width and height of the View

3. onCreate & onResume

existonCreateonResumeWhy can't we directly obtain the true width and height of the View in the function?

Referring back to the reason from the result, which shows that when onCreate and onResume are called back, ViewRootImpl'sperformTraversals()The method has not been executed yet, so when is the specific execution time of the performTraversals() method?

This can be found on the call chain ActivityThread -> WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl

First, ActivityThreadhandleResumeActivityThe method is responsible for callback and back activitiesonResumeMethod, and if the current Activity is first started, a DecorView is added to the ViewManager (wm)

	@Override
 public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
   String reason) {
  ···
  //Activity's onResume method  final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
  ···
  if ( == null &amp;&amp; ! &amp;&amp; willBeVisible) {
   ···
   ViewManager wm = ();
   if () {
    if (!) {
      = true;
     //Focus     (decor, l);
    } else {
     (l);
    }
   }
  } else if (!willBeVisible) {
   if (localLOGV) (TAG, "Launch " + r + " mStartedActivity set");
    = true;
  }
		···
 }

The specific implementation class of ViewManager here is WindowManagerImpl. WindowManagerImpl will forward the operation to WindowManagerGlobal

 @UnsupportedAppUsage
 private final WindowManagerGlobal mGlobal = ();

	@Override
 public void addView(@NonNull View view, @NonNull  params) {
  applyDefaultToken(params);
  (view, params, (), mParentWindow,
    ());
 }

WindowManagerGlobal will complete the initialization of ViewRootImpl and call itsetViewMethod, the method will be called againperformTraversalsMethod starts the drawing process of view tree

public void addView(View view,  params,
   Display display, Window parentWindow, int userId) {
  ···
  ViewRootImpl root;
  View panelParentView = null;
  synchronized (mLock) {
   ···
   root = new ViewRootImpl((), display);
   (wparams);
   (view);
   (root);
   (wparams);
   // do this last because it fires off messages to start doing things
   try {
    (view, wparams, panelParentView, userId);
   } catch (RuntimeException e) {
    // BadTokenException or InvalidDisplayException, clean up.
    if (index >= 0) {
     removeViewLocked(index, true);
    }
    throw e;
   }
  }
 }

So,performTraversalsThe method is called atonResumeAfter the method, weonCreateandonResumeThe actual width and height of the View cannot be obtained in the function. Of course, when the Activity is called the second time in a single lifecycleonResumeWhen you are using the method, you can naturally obtain the width and height attribute of the View

4. (Runnable) compatibility

From the above analysis, we can draw a conclusion:(Runnable)In the end, tasks are inserted into the MessageQueue associated with the main thread and finally executed sequentially by the main thread, so even if we call in the child thread(Runnable), you can finally get the correct width and height value of the View

However, this conclusion is only valid on API 24 and later versions.(Runnable)The method also has a version compatibility problem, and there are different implementation methods on API 23 and previous versions.

	//Android API 24 and later versions	public boolean post(Runnable action) {
  final AttachInfo attachInfo = mAttachInfo;
  if (attachInfo != null) {
   return (action);
  }
  // Postpone the runnable until we know on which thread it needs to run.
  // Assume that the runnable will be successfully placed after attach.
  getRunQueue().post(action);
  return true;
 }

	//Android API 23 and previous versions	public boolean post(Runnable action) {
  final AttachInfo attachInfo = mAttachInfo;
  if (attachInfo != null) {
   return (action);
  }
  // Assume that post will succeed later
  ().post(action);
  return true;
 }

On Android API 23 and previous versions,attachInfoWhen null, the Runnable is saved to a static member variable inside ViewRootImplsRunQueuesmiddle. andsRunQueuesThe RunQueue is saved internally through ThreadLocal, which means that the RunQueue obtained by different threads are different objects, which also means that if we call it in the child thread(Runnable)If the method is used, the Runnable will never be executed because the main thread cannot get the RunQueue of the child thread at all.

 static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();

	static RunQueue getRunQueue() {
  RunQueue rq = ();
  if (rq != null) {
   return rq;
  }
  rq = new RunQueue();
  (rq);
  return rq;
 }

In addition, sincesRunQueues It is a static member variable, and the main thread will always correspond to the same RunQueue object. If we call it in the main threadIf the (Runnable) method is used, the Runnable will be added to the RunQueue associated with the main thread, and the subsequent main thread will take out the Runnable to execute

Even if the View is an object we directly new (like the following example), the above conclusion still takes effect. When the system needs to draw other views, the task will be taken out, and it will usually be executed soon. Of course, since the View does not have AttachedToWindow at this time, the width and height value obtained must be 0

  val view = View(Context)
   {
   getWidthHeight("")
  }

right(Runnable)The compatibility issues of the method are summarized:

  • When API < 24, if it is called in the main thread, the submitted Runnable will be executed regardless of whether the View has AttachedToWindow. But the true width and height of the View can only be obtained if the View is AttachedToWindow
  • When API < 24, if it is called in the child thread, the submitted Runnable will never be executed regardless of whether the View has AttachedToWindow or not
  • When API >= 24, no matter whether it is called in the main thread or the child thread, as long as the View is AttachedToWindow, the submitted Runnable will be executed, and the true width and height value of the View can be obtained. If it is not AttachedToWindow, Runnable will never be executed

The above is the detailed content of the principles and defects of Android. For more information about Android, please follow my other related articles!