This article focuses on how to confirm the size and position of each window after opening an Activity and displaying the Activity. The layout of each view in the window is not involved. An Activity contains a PhoneWindow (window), and a window represents a window.
Activity is not responsible for view control, but for controlling its life cycle and handling events. Window is the one that really controls the view. However, the addition and display of window are closely related to the life cycle.
In android system, besides the interface displayed by Activity on a window interface, there may be at least the following elements: status bar, navigation bar and input method window
1. Before the activity callback onCreate
The first time an Activity gets in touch with Window is when perfomLaunchActivity() of ActivityThread calls the attach() method of the Activity, as shown in the following figure:
Assign a value to the windowattach method, and then create a member in the windowattach method. Since PhoneWindow inherits from Window, it can be associated with WindowManager by calling setWindowManager of Window.
ActvityThread.performLaunchActivity
/** Core implementation of activity launch. */ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ActivityInfo aInfo = r.activityInfo; if (r.packageInfo == null) { r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } ComponentName component = r.intent.getComponent(); if (component == null) { component = r.intent.resolveActivity( mInitialApplication.getPackageManager()); r.intent.setComponent(component); } if (r.activityInfo.targetActivity != null) { component = new ComponentName(r.activityInfo.packageName, r.activityInfo.targetActivity); } ContextImpl appContext = createBaseContextForActivity(r); Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); r.intent.prepareToEnterProcess(); if (r.state != null) { r.state.setClassLoader(cl); } } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e); } } try { Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (r.overrideConfig != null) { config.updateFrom(r.overrideConfig); } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); Window window = null; if (r.mPendingRemoveWindow != null && r.mPreserveWindow) { window = r.mPendingRemoveWindow; r.mPendingRemoveWindow = null; r.mPendingRemoveWindowManager = null; } appContext.setOuterContext(activity); //Call activity Attach method to establish the relationship between activity -window -windowmanager 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); if (customIntent != null) { activity.mIntent = customIntent; } r.lastNonConfigurationInstances = null; checkAndBlockForNetworkAccess(); activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource(); if (theme != 0) { activity.setTheme(theme); } activity.mCalled = false; //Set the status of the activity: onCreate if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()"); } r.activity = activity; } r.setState(ON_CREATE); mActivities.put(r.token, r); } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to start activity " + component + ": " + e.toString(), e); } } return activity; }
Activity.attach
@UnsupportedAppUsage 7703 final void attach(Context context, ActivityThread aThread, 7704 Instrumentation instr, IBinder token, int ident, 7705 Application application, Intent intent, ActivityInfo info, 7706 CharSequence title, Activity parent, String id, 7707 NonConfigurationInstances lastNonConfigurationInstances, 7708 Configuration config, String referrer, IVoiceInteractor voiceInteractor, 7709 Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) { 7710 attachBaseContext(context); 7711 7712 mFragments.attachHost(null /*parent*/); 7713 7714 mWindow = new PhoneWindow(this, window, activityConfigCallback);//Create windowphone object 7715 mWindow.setWindowControllerCallback(this); 7716 mWindow.setCallback(this); 7717 mWindow.setOnWindowDismissedCallback(this); 7718 mWindow.getLayoutInflater().setPrivateFactory(this); 7719 if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { 7720 mWindow.setSoftInputMode(info.softInputMode); 7721 } 7722 if (info.uiOptions != 0) { 7723 mWindow.setUiOptions(info.uiOptions); 7724 } //UI thread 7725 mUiThread = Thread.currentThread(); //Establish the relationship between window and WindowManager 7750 mWindow.setWindowManager( 7751 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 7752 mToken, mComponent.flattenToString(), 7753 (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 7754 if (mParent != null) { 7755 mWindow.setContainer(mParent.getWindow()); 7756 } 7757 mWindowManager = mWindow.getWindowManager(); 7758 mCurrentConfig = config; 7759 7760 mWindow.setColorMode(info.colorMode); 7761 7762 setAutofillOptions(application.getAutofillOptions()); 7763 setContentCaptureOptions(application.getContentCaptureOptions()); 7764 }
Window.setWindowManger:
frameworks/base/core/java/android/view/Window.java 770 public void setWindowManager(WindowManager wm, IBinder appToken, String appName, 771 boolean hardwareAccelerated) { 772 mAppToken = appToken; 773 mAppName = appName; 774 mHardwareAccelerated = hardwareAccelerated; 775 if (wm == null) { 776 wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 777 } 778 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); 779 }
WindowManager is an interface class. Its implementation is in WindowManagerImpl. In the important method of WindowManagerImpl, the real work is handled by WindowManagerGlobal. Both belong to the agent model. WindowManagerGlobal is used to create, manage and delete ViewRoot. The relationship between the three is as follows:
2 calling setContentView in activity oncreate
The perfomLaunchActivity of ActivityThread will call the onCreate method of the Activity after the attach is completed. The application will call setContentView in the onCreate callback to add a layout for the active window.
Activity.setContentView
3325 public void setContentView(@LayoutRes int layoutResID) { 3326 getWindow().setContentView(layoutResID); 3327 initWindowDecorActionBar(); 3328 }
The getWindow () method here obtains the PhoneWindow object instantiated in the previous attach () method. PhoneWindow class is the specific implementation class of Window. Therefore, the next step is to call the setContentView of PhoneWindow for real operation.
PhoneWindow.setContenView
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java //This is where the specific contents of the window are placed. It can be mDecor or a child View of mDecor ViewGroup mContentParent; 422 @Override 423 public void setContentView(int layoutResID) { 424 // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window 425 // decor, when theme attributes and the like are crystalized. Do not check the feature 426 // before this happens. 427 if (mContentParent == null) { 428 installDecor();//Call mContentParent for the first time, and call the installDecor method to create DecorView 429 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 430 mContentParent.removeAllViews();//If mContentParent already exists and animation is not required, clear all view s 431 } 432 433 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 434 final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, 435 getContext()); 436 transitionTo(newScene); 437 } else { //Add the layout file to mContentParent, which is assigned in installDecor 438 mLayoutInflater.inflate(layoutResID, mContentParent); 439 } 440 mContentParent.requestApplyInsets(); 441 final Callback cb = getCallback(); 442 if (cb != null && !isDestroyed()) { 443 cb.onContentChanged(); 444 } 445 mContentParentExplicitlySet = true; 446 }
The meaning of the above code is:
First, make the first judgment. If mContentParent is empty, call installDecor(). When the mContentParent content is not empty and feature is not set_ CONTENT_ When the transitions flag bit, removeAllViews() will be called to empty the mContentParent content. Where feature_ CONTENT_ The transitions flag bit represents the transition animation of content conversion. The default is false. It can be set in the body through the attribute windowscontenttransitions.
The second if judgment is that if feature is set after mContentParent is obtained_ CONTENT_ Transitions adds Scene to start the transition. Otherwise, call mlayoutinflator Inflate (layoutresid, mContentParent) converts the layout file of the application Activity into a View tree through the LayoutInflater object and adds it to the mContentParent View.
PhoneWindow. installDecor
158 // This is the top-level view of the window, containing the window decor. 159 private DecorView mDecor; 2681 private void installDecor() { 2682 mForceDecorInstall = false; 2683 if (mDecor == null) { 2684 mDecor = generateDecor(-1);//1. When DecorView does not exist, call generateDecor 2685 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 2686 mDecor.setIsRootNamespace(true); 2687 if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { 2688 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); 2689 } 2690 } else { 2691 mDecor.setWindow(this);//If decorview already exists, contact phoneindow with decorview 2692 } //At this time, mDecor is not empty, but mContentParent is empty 2693 if (mContentParent == null) { 2694 mContentParent = generateLayout(mDecor);//2. Bind layout for DecorView 2695 } ....... 2814 } 2815
2315 protected DecorView generateDecor(int featureId) { //Get the context of the application 2319 Context context; 2320 if (mUseDecorContext) { 2321 Context applicationContext = getContext().getApplicationContext(); 2322 if (applicationContext == null) { 2323 context = getContext(); 2324 } else { 2325 context = new DecorContext(applicationContext, getContext()); 2326 if (mTheme != -1) { 2327 context.setTheme(mTheme); 2328 } 2329 } 2330 } else { 2331 context = getContext(); 2332 } 2333 return new DecorView(context, featureId, this, getAttributes()); 2334 }
So far, mDecorView has only been instantiated, the layout has not been loaded, and the mContentParent is empty. Call generateLayout to load the layout for decorview and instantiate mContentParent.
mContentParent = generateLayout(mDecor);//2. Bind layout for DecorView
protected ViewGroup generateLayout(DecorView decor) { //Get the theme of the window and set different feature s according to different styles 2339 TypedArray a = getWindowStyle(); 2350 //Is it a floating window 2351 mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false); 2352 int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) 2353 & (~getForcedWindowFlags()); 2354 if (mIsFloating) { 2355 setLayout(WRAP_CONTENT, WRAP_CONTENT); 2356 setFlags(0, flagsToUpdate); 2357 } else { 2358 setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); 2359 } 2360 //Do you need to set the title and actionbar 2361 if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { 2362 requestFeature(FEATURE_NO_TITLE); 2363 } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { 2364 // Don't allow an action bar if there is no title. 2365 requestFeature(FEATURE_ACTION_BAR); 2366 } 2367 //Do you need to set actionbar overlay 2368 if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) { 2369 requestFeature(FEATURE_ACTION_BAR_OVERLAY); 2370 } //Whether the window is in full screen mode 2380 if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) { 2381 setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); 2382 } 2383 //Set the transparent attribute flag of statusbar according to style 2384 if (a.getBoolean(R.styleable.Window_windowTranslucentStatus, 2385 false)) { 2386 setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS 2387 & (~getForcedWindowFlags())); 2388 } 2389 //According to window_ Windowstranslucentnavigation property, set natnavigation flag 2390 if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation, 2391 false)) { 2392 setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION 2393 & (~getForcedWindowFlags())); 2394 } 2395 2396 if (a.getBoolean(R.styleable.Window_windowOverscan, false)) { 2397 setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags())); 2398 } 2399 //Set whether to set wallpaper 2400 if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) { 2401 setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags())); 2402 } 2403 2404 if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch, 2405 getContext().getApplicationInfo().targetSdkVersion 2406 >= android.os.Build.VERSION_CODES.HONEYCOMB)) { 2407 setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags())); 2408 } 2409 ........ 2442 2443 final Context context = getContext(); 2444 final int targetSdk = context.getApplicationInfo().targetSdkVersion; 2445 final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB; 2446 final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; 2447 final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP; 2448 final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q; 2449 final boolean targetHcNeedsOptions = context.getResources().getBoolean( 2450 R.bool.target_honeycomb_needs_options_menu); 2451 final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE); 2452 //Set whether menukey is required 2453 if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) { 2454 setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE); 2455 } else { 2456 setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE); 2457 } 2458 //Set the color of statusbar 2459 if (!mForcedStatusBarColor) { 2460 mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000); 2461 } 2462 if (!mForcedNavigationBarColor) { 2463 mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000); 2464 mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor, 2465 0x00000000); 2466 } 2473 2474 WindowManager.LayoutParams params = getAttributes(); 2475 //Whether to draw the background of statusbar and navigationbar 2478 if (!mIsFloating) { 2479 if (!targetPreL && a.getBoolean( 2480 R.styleable.Window_windowDrawsSystemBarBackgrounds, 2481 false)) { 2482 setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, 2483 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags()); 2484 } 2485 if (mDecor.mForceWindowDrawsBarBackgrounds) { 2486 params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; 2487 } 2488 } //Do you want to draw the statusbar and navigationBar as bright themes 2489 if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) { 2490 decor.setSystemUiVisibility( 2491 decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 2492 } 2493 if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) { 2494 decor.setSystemUiVisibility( 2495 decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); 2496 } //Whether the current mode is banged screen mode and obtain the mode of banged screen 2497 if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) { 2498 int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1); 2499 if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 2500 || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) { 2501 throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: " 2502 + a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode)); 2503 } 2504 params.layoutInDisplayCutoutMode = mode; 2505 } 2506 //Is it currently in input mode 2516 if (!hasSoftInputMode()) { 2517 params.softInputMode = a.getInt( 2518 R.styleable.Window_windowSoftInputMode, 2519 params.softInputMode); 2520 } 2521 //Set the flag of the window_ DIM_ BEHIND 2522 if (a.getBoolean(R.styleable.Window_backgroundDimEnabled, 2523 mIsFloating)) { 2524 /* All dialogs should have the window dimmed */ 2525 if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) { 2526 params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; 2527 } 2528 if (!haveDimAmount()) { 2529 params.dimAmount = a.getFloat( 2530 android.R.styleable.Window_backgroundDimAmount, 0.5f); 2531 } 2532 } 2533 //Animate the window 2534 if (params.windowAnimations == 0) { 2535 params.windowAnimations = a.getResourceId( 2536 R.styleable.Window_windowAnimationStyle, 0); 2537 } 2538 2562 2563 // Inflate the window decor. 2564 //Different layouts can be obtained according to different feature s 2565 int layoutResource; 2566 int features = getLocalFeatures(); 2567 // System.out.println("Features: 0x" + Integer.toHexString(features)); 2568 if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { 2569 layoutResource = R.layout.screen_swipe_dismiss; 2570 setCloseOnSwipeEnabled(true); 2571 } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) 2625 ..... } 2626 mDecor.startChanging(); //Start another thread BackdropFrameRenderer to load related resources, including drawing statusbar and nav 2627 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); 2628 //Get ViewGroup: contentParent 2629 . . . . . 2671 mDecor.finishChanging(); 2672 2673 return contentParent; 2674 }
The main functions of generateLayout are:
1. The first is to obtain the window theme and set different feature flag s according to different theme styles, such as whether there is TitleBar, ActionBar, float window, full screen, Progress Window, etc.
2. Next, judge different features and call onResourcesLoaded to load different layout files. For example, if the Window theme is NO_TITLE and decorview load r.layout screen_ Simple layout. This layout only contains mContentParent. At this time, mDecor is equivalent to mContentParent. If other decorative views are included, mContentParent is the child element of mDecor. Here we explain the above question: mContentParent can be mDecor or a child View of mDecor.
3. Finally, get the mContentParent and return it. From the code point of view, mContentParent is the ID in the mDecor layout_ ANDROID_ Control of content.
The relationship diagram of decorview, mContentParent and mContentRoot (equivalent to decorview) is as follows:
3. In the activity onresume stage, DecorView is added to the Window and displayed
The onCreate stage in the previous section is the creation of DecorView and mContentParent. The real display process needs to call the handlerresumeactivity method of ActivityThead.
@Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { //Execute application on_ Status of resume final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); if (r == null) { // We didn't actually resume the activity, so skipping any follow-up actions. return; } final Activity a = r.activity; ...... if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow();//Get phoneindow View decor = r.window.getDecorView();//Get DecorView decor.setVisibility(View.INVISIBLE);//Decorview starts to be invisible 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; // Normally the ViewRoot sets up callbacks with the Activity // in addView->ViewRootImpl#setView. If we are instead reusing // the decor view we have to notify the view root that the // callbacks may have changed. ViewRootImpl impl = decor.getViewRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } else { // The activity will get a callback for this {@link LayoutParams} change // earlier. However, at that time the decor will not be set (this is set // in this method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l); } } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } ......... r.activity.mVisibleFromServer = true; mNumVisibleActivities++; //When the Activity is visible, Decorview is set to visible status if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } r.nextIdle = mNewActivities; mNewActivities = r; if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r); Looper.myQueue().addIdleHandler(new Idler());
In the handleResumeActivity method of ActivityThread, we only focus on three processes
1. performResumeActivity(): performResumeActivity will call performResume of the activity, performResume will call onResume, and then enter the onResume life cycle of the activity.
2,wm.addView(): call addView of ViewManager to add DecorView and LayoutParams to the window and start the drawing process.
3,activity.makeVisible(): set DecorView visible and display DecorView
3.1 setView method of viewrootimpl
ViewRootImpl public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view;//Assign DecorView to mView //Monitor the change of Display. When the screen is lit or dimmed, or change the resolution mAttachInfo.mDisplayState = mDisplay.getState(); mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); mViewLayoutDirectionInitial = mView.getRawLayoutDirection(); mFallbackEventHandler.setView(view); mWindowAttributes.copyFrom(attrs); if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } attrs = mWindowAttributes; setTag(); if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0 && (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) { Slog.d(mTag, "setView: FLAG_KEEP_SCREEN_ON changed from true to false!"); } // Keep track of the actual window flags supplied by the client. mClientWindowLayoutFlags = attrs.flags; setAccessibilityFocus(null, null); } boolean restore = false; if (mTranslator != null) { mSurface.setCompatibilityTranslator(mTranslator); restore = true; attrs.backup(); mTranslator.translateWindowLayout(attrs); } if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs); mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ // 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. //Request relayout and drawing interface requestLayout(); if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } mForceDecorViewVisibility = (mWindowAttributes.privateFlags & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0; try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); //Call wms to add a window res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } //Processing input events if (mInputChannel != null) { if (mInputQueueCallback != null) { mInputQueue = new InputQueue(); mInputQueueCallback.onInputQueueCreated(mInputQueue); } mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); } ...... // Set up the input pipeline. CharSequence counterSuffix = attrs.getTitle(); mSyntheticInputStage = new SyntheticInputStage(); InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); InputStage imeStage = new ImeInputStage(earlyPostImeStage, "aq:ime:" + counterSuffix); InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage); InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, "aq:native-pre-ime:" + counterSuffix); mFirstInputStage = nativePreImeStage; mFirstPostImeInputStage = earlyPostImeStage; mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix; } } }
From the above methods, we can see that the responsibilities of ViewRootImpl are:
1) The link between WindowManager and DecorView, and more broadly, the link between Window and View. Cooperate with WindowManagerService to manage the application Window of the system.
2) Complete the drawing process of View, including measure, layout and draw.
3) Be responsible for distributing the received event events to View, such as key press, touch screen and other events.
3.2 requestLayout
1441 @Override 1442 public void requestLayout() { 1443 if (!mHandlingLayoutInLayoutRequest) { 1444 checkThread();//Check whether it is currently in the main thread 1445 mLayoutRequested = true;//Set mLayoutRequested 1446 scheduleTraversals();//Call scheduleTraversals 1447 } 1448 }
3.2.1 scheduleTraversals
1712 @UnsupportedAppUsage 1713 void scheduleTraversals() { 1714 if (!mTraversalScheduled) {//It will not be called multiple times in the same frame 1715 mTraversalScheduled = true; //Intercept synchronous message 1716 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //The mChoreographer callback performs the drawing operation 1717 mChoreographer.postCallback( 1718 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 1719 if (!mUnbufferedInputDispatch) { 1720 scheduleConsumeBatchedInput(); 1721 } 1722 notifyRendererOfFramePending(); 1723 pokeDrawLockIfNeeded(); 1724 } 1725 }
Choreographer.choreographer called postCallback. The main function of choreographer is to coordinate the animation, input and drawing time with VSync. It receives timing pulses (such as vertical synchronization) from the display subsystem, and then arranges part of the rendering of the next frame. The general flow is shown in the following figure, which is the process of requesting VSync and receiving VSync respectively.
3.2.2 postCallback
The calling sequence of postCallback is as follows:
Choreographer.java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
/ / current time
final long now = SystemClock.uptimeMillis();
/ / callback execution time, which is the current time plus the delay time
final long dueTime = now + delayMillis;
/ / obtainCallbackLocked(long dueTime, Object action, Object token) this method will pass in three parameters
/ / convert to CallbackRecord, and then add CallbackRecord to the chain according to the type of callbackQueue
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
/ / judge by time
if (dueTime <= now) {
/ / if delayMillis is 0, execute immediately
scheduleFrameLocked(now);
} else {
/ / if the time is not up, send an MSG_ DO_ SCHEDULE_ The callback message is processed when the time is up. The final processing is also processed by calling / / scheduleFrameLocked
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
scheduleFrameLocked
// Enable/disable vsync for animations and drawing. private static final boolean USE_VSYNC = SystemProperties.getBoolean( "debug.choreographer.vsync", true); private void scheduleFrameLocked(long now) { if (!mFrameScheduled) { mFrameScheduled = true; //Determine whether VSYNC is used. This value depends on the system properties if (USE_VSYNC) { if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame on vsync."); } // If running on the Looper thread, then schedule the vsync immediately, // otherwise post a message to schedule the vsync from the UI thread // as soon as possible. if (isRunningOnLooperThreadLocked()) { //The request for Vsync signal will eventually be called to the native layer. After the processing of the native layer is completed, the onVsync of / / FrameDisplayEventReceiver will be triggered, and the callback will eventually be called to void doFrame(long frameTimeNanos, int frame). Here, the subsequent callback will not be executed until the Vsync signal is requested scheduleVsyncLocked(); } else { //Send an MSG directly in the UI thread_ DO_ SCHEDULE_ The Vsync message finally calls scheduleVsyncLocked Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else { final long nextFrameTime = Math.max( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); } //If VSYNC is not used, send MSG_ DO_ The frame message will eventually call the void doFrame(long frameTimeNanos, int frame) method Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); } }
3.2.2.2 process of requesting Vysnc
Next, let's take a look at the process of requesting Vysnc
Choreographer.java private void scheduleVsyncLocked() { mDisplayEventReceiver.scheduleVsync(); }
DisplayEventReceiver.java public void scheduleVsync() { if (mReceiverPtr == 0) { Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event " + "receiver has already been disposed."); } else { //Call the native method through JNI nativeScheduleVsync(mReceiverPtr); } }
mDisplayEventReceiver corresponds to FrameDisplayEventReceiver, which inherits from DisplayEventReceiver and is mainly used to receive synchronization pulse signal Vsync The schedulevsync () method registers with the SurfaceFlinger service through the underlying nativeScheduleVsync, that is, after the next pulse is received, it will call the dispatchVsync() method of DisplayEventReceiver, which is similar to the reader prevention mode, but the method of nativeScheduleVsync calls dispatchVsync every time and only once.
Note: SurfaceFlinger is involved here, and the premise for the application to establish a connection with SurfaceFlinger is to create SurfaceSession. The last analysis of SurfaceSession was created in addwindow. Therefore, although the requestLayout method is called first, the Vsync signal cannot be received at this time. Callbacks cannot be called, so mwindowsession will be executed first Addtodisplay related logic. After receiving Vsync, continue to execute the following logic.
3.2.2.3 VSync signal received
The logical sequence of signals received from VSync is:
When the bottom layer sends VSYNC signal to the application, the java layer receives it through dispatchVsync(), and finally calls back onVsync in FrameDisplayEventReceiver
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { private boolean mHavePendingVsync; private long mTimestampNanos; private int mFrame; @Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { //Automatically ignore VSYNC displayed on the default screen if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) { Log.d(TAG, "Received vsync from secondary display, but we don't support " + "this case yet. Choreographer needs a way to explicitly request " + "vsync for a specific display to ensure it doesn't lose track " + "of its scheduled vsync."); scheduleVsync(); return; } ....... mTimestampNanos = timestampNanos; mFrame = frame; //The callback of this message is the current object framedisbayeventreceiver Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); //mHandle is FrameHandler mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); } @Override public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); } }
It can be seen that onVsync() sends a built-in callback message to the Looper of the main thread through the FrameHandler, and the callback is framedisclayeventreceiver. When the Looper of the main thread executes the message, it calls the run method of framedisbayeventreceiver, and then calls doFrame (question: Handler message processing calls run)?, Print msg
void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { if (!mFrameScheduled) { return; //If mFrameScheduled is false, it will be returned directly } long intendedFrameTimeNanos = frameTimeNanos;//Originally planned frame drawing time point startNanos = System.nanoTime();//Save start time //Because Vsync event processing adopts asynchronous mode, the time between message sending and function call starting is calculated here final long jitterNanos = startNanos - frameTimeNanos; //If the thread processes the message longer than the screen refresh cycle if (jitterNanos >= mFrameIntervalNanos) { //Calculates the number of frames missed during a function call final long skippedFrames = jitterNanos / mFrameIntervalNanos; //When the number of dropped frames exceeds 30, the corresponding log is output if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; //Time interval of aligned frames frameTimeNanos = startNanos - lastFrameOffset; } //If frameTimeNanos is less than one screen refresh cycle, re request the vSync signal if (frameTimeNanos < mLastFrameTimeNanos) { scheduleVsyncLocked(); return; } ..... mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; } try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); //Callback callback respectively_ INPUT,CALLBACK_ANIMATION,CALLBACK_TRAVERSAL,CALLBACK_COMMIT, doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ...... }
When the Vysnc event arrives, the callbacks registered in the CallbackQueue queue corresponding to the four events are executed in sequence
void doCallbacks(int callbackType, long frameTimeNanos) { CallbackRecord callbacks; synchronized (mLock) { final long now = System.nanoTime(); //Finds the callbacks whose execution time has expired from the CallbackQueues queue of the specified type callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; ....... try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]); //Since the callbackQueue is sorted in order, the CallbackRecord that arrives at all times is traversed and executed for (CallbackRecord c = callbacks; c != null; c = c.next) { c.run(frameTimeNanos); } } finally { synchronized (mLock) { mCallbacksRunning = false; do { final CallbackRecord next = callbacks.next; recycleCallbackLocked(callbacks); callbacks = next; } while (callbacks != null); } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
Execute the run method of callbacks in chronological order
private static final class CallbackRecord { public CallbackRecord next; public long dueTime; public Object action; // Runnable or FrameCallback public Object token; public void run(long frameTimeNanos) { if (token == FRAME_CALLBACK_TOKEN) { ((FrameCallback)action).doFrame(frameTimeNanos); } else { ((Runnable)action).run(); } } }
Then go back to viewRootImpl and call scheduleTraversals
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ...... } }
See that the corresponding runnable is mTraversalRunnable
ViewRootImpl.java final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
The run method is called, so doTraversal is executed. In doTraversal, performTraversals is called to start the three processes of measurement, layout and drawing of the View.
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
To summarize briefly, call the postCallback method of Choreographer:
1. Firstly, Choreographer supports four types of events: input, drawing, animation and submission
2. After calling the postCallback method, register the required synchronization Vsync with the SF and wait for the callback
3. After the Choreographer listens to the Vsync signal of the underlying layer, once it receives the callback signal, it will uniformly callback the four types of events of the java layer through the doFrame
4 .Log analysis