kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- title: 'Activity 布局' disqus: kyleAlien --- Activity 布局 === ## OverView of Content 以下會追尋 **android 7.1** 源碼,重點部份使用 `@` 標明 [TOC] ## Activity & Window 1. Activity 與我們常常接觸的 Context、Application 很有關係,我們可以從這裡開始下手,最終就會找到與 Window 的關係 2. Window 是抽象類,它的實做類是 PhoneWindow,負責解析 & 放置布局 先上完整的時序圖 > ![](https://i.imgur.com/bVWG5kU.png) ### Activity & Context & Application 創建 * **我們從 ActivityThread 類,其當中的 ++performLaunchActivity 方法開始++**(從頭開始就是從 Activity#main 開始,但是那就涉及到了 Binder 機制,這就不是我們的重點了,會另外挑章節說明,暫且當為一個黑盒子) ```java= /** * ActivityThread.java */ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); ActivityInfo aInfo = r.activityInfo; if (r.packageInfo == null) { r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } // @ 是否有夾帶 Intent 資訊 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); } Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); //1. Activity object // Create Activity, used empty construct activity = mInstrumentation.newActivity( //@ reflection create 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 { // 2. Application Create Application app = r.packageInfo.makeApplication(false, mInstrumentation); //@ get appliction object, also use reflection to create application object ... Log 訊息 if (activity != null) { // 3. Context Create Context appContext = createBaseContextForActivity(r, activity); 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); ... 省略部份,在 Activity 連接 Context Application 時會在解釋 } r.paused = true; // @ put in cache mActivities.put(r.token, r); } /* 省略 catch */ return activity; } ``` 1. **首先會先創建 Activity,這個創建是使用了反射來創建**,創建的 Activity 是使用無參數構造函數 ```java= /** * ActivityThread.java * performLaunchActivity 方法 */ java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( //@ reflection create cl, component.getClassName(), r.intent); /** * Instrumentation.java */ public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { // ClassLoader 讀取 class,再使用反射創建 return (Activity)cl.loadClass(className).newInstance(); } ``` * className 對應到 AndroidManifest Activity 中設定的 Name > ![](https://i.imgur.com/CIovrPt.png) 2. **創建 Application 對象,同樣使用反射創建,創建完立刻呼叫 Application attach 函數。這裡會創建 ^1^ Application 的 Context,^2^ Applicatoin 實體** ```java= /** * ActivityThread.java * performLaunchActivity 方法 */ Application app = r.packageInfo.makeApplication(false, mInstrumentation); /** * LoadedApk.java */ public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { // 已創建 Application 對象就不再創建 if (mApplication != null) { return mApplication; } ... Application app = null; // AndroidManifest 設定的 Application Name String appClass = mApplicationInfo.className; if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; } try { java.lang.ClassLoader cl = getClassLoader(); if (!mPackageName.equals("android")) { ... initializeJavaContextClassLoader(); } // @ Create ContextImpl ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); // @ Create Application // 查看 newApplication 函數 app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { if (!mActivityThread.mInstrumentation.onException(app, e)) { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); throw new RuntimeException( "Unable to instantiate application " + appClass + ": " + e.toString(), e); } } ...省略 return app; } /** * Instrumentation.java */ public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { // ClassLoader load target class return newApplication(cl.loadClass(className), context); } static public Application newApplication(Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { // 根據 class 反射創建 Application 對象 Application app = (Application)clazz.newInstance(); // 呼叫 Application#attach 方法 app.attach(context); return app; } ``` 3. **創建 Context 物件,==Context 是抽象,實體類是 ContextImpl==** ```java= /** * ActivityThread.java * performLaunchActivity 方法 */ Context appContext = createBaseContextForActivity(r, activity); private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) { int displayId = Display.DEFAULT_DISPLAY; try { displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } //@ Create Context // 分析 createActivityContext 方法 ContextImpl appContext = ContextImpl.createActivityContext( this, r.packageInfo, r.token, displayId, r.overrideConfig); appContext.setOuterContext(activity); Context baseContext = appContext; ... 省略 return baseContext; } /** * ContextImpl.java */ static ContextImpl createActivityContext(ActivityThread mainThread, LoadedApk packageInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); return new ContextImpl(null, mainThread, packageInfo, activityToken, null, 0, null, overrideConfiguration, displayId); } ``` > ![](https://i.imgur.com/MJyHOKH.png) ### Activity 連接 & 創建 Window * **將 Activity、Context、Application 連接在一起的是 ==Activity#attch 函數==,並且會在裡面創建 Window 實體對象 (有就是 ++PhoneWindow++)** ```java= /** * ActivityThread.java */ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... 省略,上面以解說 try { ... 省略,上面以解說 (創建 Activity、Application、ContextImpl... 等等對象) if (activity != null) { ... // Window object Window window = null; if (r.mPendingRemoveWindow != null && r.mPreserveWindow) { window = r.mPendingRemoveWindow; r.mPendingRemoveWindow = null; r.mPendingRemoveWindowManager = null; } //@ Create Phone window in {@link attach} function, // find Phonewindows !! activity.attach(appContext, this, getInstrumentation(), r.token, //@ let ^2^appliction & ^3^context bind on ^1^activity r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window); if (customIntent != null) { activity.mIntent = customIntent; } r.lastNonConfigurationInstances = null; activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource(); if (theme != 0) { activity.setTheme(theme); } activity.mCalled = false; // @ 2. 呼叫 Activity 的 onCreate 方法 if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); ... 省略部份 } r.paused = true; // @ put in cache mActivities.put(r.token, r); } /* 省略 catch */ return activity; } /** * Activity.java * attach 方法 */ 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) { // @ 3. 創建 AppCompatDelegateImp 對象 attachBaseContext(context); mFragments.attachHost(null /*parent*/); // @ 1. Creaet Winodw object // 假設當前帶入的 window is null mWindow = new PhoneWindow(this, window); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); ... } ``` 1. 這裡的重點在 Window 是抽象類,**在 Activity#attach 方法中會創建 Window 的實體類,也就是 PhoneWindow** 2. 呼叫完 Activity#attach 方法後,就會呼叫 Activity#onCreate 方法 ```java= /** * Instrumentation.java */ public void callActivityOnCreate(Activity activity, Bundle icicle) { // 前置作業 prePerformCreate(activity); activity.performCreate(icicle); // 後續作業 postPerformCreate(activity); } /** * Activity.java */ final void performCreate(Bundle icicle) { restoreHasCurrentPermissionRequest(icicle); // 這裡就會呼叫我們 Override 的 onCreate 方法 onCreate(icicle); mActivityTransitionState.readState(icicle); performCreateCommon(); } ``` > ![](https://i.imgur.com/bVWG5kU.png) 3. 還有第三點,**attachBaseContext 創建 AppCompatDelegateImp**,**這是下一個小節開始的重點** ## AppCompatActivity AppCompatActivity 是啥?這是 Android 在幫我們創建 Activity 時,預設的父類它的繼承關係如下(你也可以把它當成一個 Activity) ```java= // AppCompatActivity 也是繼承於 Activity public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } ``` > ![](https://i.imgur.com/EtW6SlZ.png) ### [AppCompatActivity](https://github.com/guardianproject/android-support-library/blob/master/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java) & PhoneWindws 關係 * 現在我們要把 `AppCompatActivity` & `PhoneWindow` 串在一起,看看這兩者之間是如何產生關係的,**這跟上面所解說的 Activity & Window 有關係,尤其是在 Activity#attach 函數的 ==attachBaseContext== 有關係** - 以下是完整的時序圖 > ![](https://i.imgur.com/OewvVkr.png) 1. **attachBaseContext**:會創建出 AppCompatDelegateImpl,而傳入的 Window 為 null,所以在這個步驟並不會有 Windows > ![](https://i.imgur.com/Czu1yGG.png) ```java= /** * 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) { // AppCompatActivity 覆寫了這個方法 attachBaseContext(context); ...省略 } /** * AppCompatActivity.java */ public class AppCompatActivity extends FragmentActivity implements AppCompatCallback, TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider { @Override protected void attachBaseContext(Context newBase) { // 追尋 getDelegate 方法 super.attachBaseContext(getDelegate().attachBaseContext2(newBase)); } @NonNull public AppCompatDelegate getDelegate() { // 一開始進來為 null if (mDelegate == null) { // 創建 AppCompatDelegate (AppCompatDelegate 是抽象) mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; } } /** * AppCompatDelegate.java(抽象) */ public abstract class AppCompatDelegate { @NonNull public static AppCompatDelegate create(@NonNull Activity activity, @Nullable AppCompatCallback callback) { // 工廠方法 // AppCompatDelegate 時做類是 AppCompatDelegateImpl return new AppCompatDelegateImpl(activity, callback); } } /** * AppCompateDelegateImpl.java */ class AppCompatDelegateImpl extends AppCompatDelegate implements MenuBuilder.Callback, LayoutInflater.Factory2 { // 建構函數 AppCompatDelegateImpl(Activity activity, AppCompatCallback callback) { // 第二個參數 window is null this(activity, null, callback, activity); } private AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback, Object host) { mContext = context; mAppCompatCallback = callback; mHost = host; // 模式判斷 if (mLocalNightMode == MODE_NIGHT_UNSPECIFIED && mHost instanceof Dialog) { final AppCompatActivity activity = tryUnwrapContext(); if (activity != null) { mLocalNightMode = activity.getDelegate().getLocalNightMode(); } } if (mLocalNightMode == MODE_NIGHT_UNSPECIFIED) { // Try and read the current night mode from our static store final Integer value = sLocalNightModes.get(mHost.getClass().getName()); if (value != null) { mLocalNightMode = value; // Finally remove the value sLocalNightModes.remove(mHost.getClass().getName()); } } // 目前傳入的 Window 是 null if (window != null) { attachToWindow(window); } AppCompatDrawableManager.preload(); } // 連接 Window 對象 private void attachToWindow(@NonNull Window window) { if (mWindow != null) { throw new IllegalStateException( "AppCompat has already installed itself into the Window"); } ... 省略部分 // * 此處為 window 賦值 mWindow = window; } } ``` * Activity#attach 是最終會透過建構 `AppCompatDelegateImpl` 來賦予 window 對象,但是最初傳入的 window 是 null (也空對象),**所以第一步驟目的是建構 AppCompatDelegateImpl 對象,==這個步驟則是在建構 Activity 時就自動做==** 2. onCreate 透過 setContent(),賦予 Window 對象 (可以當成懶加載) > ![](https://i.imgur.com/jf7oSuy.png) ```java= /** * AppCompatActivity.java */ @Override public void setContentView(@LayoutRes int layoutResID) { // @ 先看 getDelegate,再看 setContentView getDelegate().setContentView(layoutResID); } @NonNull public AppCompatDelegate getDelegate() { // @ 這時 mDelegate 就不為空 if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; // 對象就是 AppCompatDelegateImpl } /** * AppCompatDelegateImpl */ @Override public void setContentView(int resId) { // @ 確認 Decor 布局 ensureSubDecor(); ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); contentParent.removeAllViews(); LayoutInflater.from(mContext).inflate(resId, contentParent); mAppCompatWindowCallback.getWrapped().onContentChanged(); } private void ensureSubDecor() { if (!mSubDecorInstalled) { // 建立 mSubDecor View mSubDecor = createSubDecor(); ...省略部分 } } // @ 這裡會檢查很多布局屬性 private ViewGroup createSubDecor() { TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); // 判斷布局屬性 if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) { a.recycle(); throw new IllegalStateException( "You need to use a Theme.AppCompat theme (or descendant) with this activity."); } // @ 追隨 ensureWindow 方法,確認 window 對象 ensureWindow(); mWindow.getDecorView(); ...省略部分 // @ PhoneWindow 的布局分析,分析 PhoneWindow#setContentView 方法 mWindow.setContentView(subDecor); } private void ensureWindow() { // @ 如果對象是 Activity 則 window 就使用 ++懶加載++ if (mWindow == null && mHost instanceof Activity) { // Activity 的 window 就是 PhoneWindow attachToWindow(((Activity) mHost).getWindow()); } if (mWindow == null) { throw new IllegalStateException("We have not been given a Window"); } } /** * Activity.java */ public Window getWindow() { // @ attach 方法就建立了 PhoneWindow return mWindow; } // 回憶 mWindow 是在 attach 方法就賦予的 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) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this, window, activityConfigCallback); ...省略部分 } ``` * 第二部分的重點在 **^1^ Activity 是透過手動在 onCreate 呼叫 setContext 來獲取 Activity 的 Window 對象 (達到懶加載),而這個對象則是在創建 Activity 時就建立好的++ PhoneWindow 對象++,^2^ ==呼叫 PhoneWindow 的 setContentView==** ## Activity 布局分析 我們上面分析到 AppCompatDelegateImpl#createSubDecor 方法,這個方法**最後會掉用到 PhoneWindow 的 setContentView 上** ```java= private ViewGroup createSubDecor() { // @ 確認 window 對象 ensureWindow(); mWindow.getDecorView(); ...省略部分 // @ PhoneWindow 的布局分析 mWindow.setContentView(subDecor); } ``` 有關於布局分析,有興趣可以先看看 [**LayoutInflate**](https://hackmd.io/EPfgb14TS-Kcd-Pv7tDZ-w?view#%E6%95%B4%E9%AB%94%E6%A6%82%E5%BF%B5%E5%9C%96) 這篇文章,這邊稍加帶過 ### PhoneWindow - setContentView 1. **首先會先建構 Decor View** 2. **進行 inflate 分析 ++mContentParent++ 布局** ```java= /** * PhoneWindow.java */ @Override public void setContentView(int layoutResID) { // @ 一開始 mContentParent 就是 null if (mContentParent == null) { // @ 會在 installDecor 才賦予值 // 追尋 ++ installDecor 方法++ installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { ... 有 Scene 轉場動畫,省略 } else { // @ 大部份是直接 inflate 分析 mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; } ``` ### PhoneWindow - installDecor * 如其名 PhoneWindow#installDecor,也就是安裝(建構)DecorView ```java= /** * PhoneWindow.java */ private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { // @ 產生 DecorView mDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { mDecor.setWindow(this); } if (mContentParent == null) { // @ 分析 Window Style 產生對應的 Layout mContentParent = generateLayout(mDecor); ... 省略部份 } } /** * 創建 DecorView & 包裝 Context */ protected DecorView generateDecor(int featureId) { Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, this); if (mTheme != -1) { context.setTheme(mTheme); } } } else { context = getContext(); } // @ 建構 DecorView return new DecorView(context, featureId, this, getAttributes()); } /** * 對應的 ViewGroupdroid.internal.R.id.content; */ public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle(); if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) { requestFeature(FEATURE_ACTION_BAR_OVERLAY); } ...以下有許多類似的判斷省略 int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { ...省略 } else { // 預設 layout 布局 layoutResource = R.layout.screen_simple; } mDecor.startChanging(); // 分析布局 & addView mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 返回 ViewGroup of FrameLayout ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ... 以下省略 return contentParent; } ``` 1. 這個步驟主要目的在建構 DecorView(並對 Context 包裝),並載入預設布局(R.layout.screen_simple) > ![](https://i.imgur.com/Gkb21ub.png) 2. screen_simple 布局可以在源碼中的 Framework 資料夾內找到,這個布局很簡單,只有 ViewStub(填充)、FrameLayout 布局 ```xml= <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" /> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout> ``` 3. 最後它會找到 FrameLayout,並將其作為 **ViewGroup 賦予 mContentParent,==之後將會使用 LayoutInflate 對它進行解析==** ### LayoutInflater 加載 * 更詳細的解析分細在 [**LayoutInflate**](https://hackmd.io/EPfgb14TS-Kcd-Pv7tDZ-w?view#%E6%95%B4%E9%AB%94%E6%A6%82%E5%BF%B5%E5%9C%96) 這篇文章,這邊帶入重點部份,**^1^ 第一個是創建 View,^2^ 產生 LayoutParams 對象,依照使用者傳入的參數決定這個 View 跟 Parent View 的關係** ```java= /** * LayoutInflate.java */ public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { advanceToRootNode(parser); // @ Xml Tag final String name = parser.getName(); ...Debug 資訊 if (TAG_MERGE.equals(name)) { // @ 分析 Merge 標籤 rInflate(parser, root, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { // @ 產生 LayoutParams 對象 params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } ...Debug 資訊 if (root != null && attachToRoot) { root.addView(temp, params); } if (root == null || !attachToRoot) { result = temp; } } } catch(...) { ... 省略 } finally { ... 省略 } } ``` * **創建 View:關注的是 ==createViewFromTag== 方法**,它會 ^1^ 看看使用者是否有自己寫 Factory,^2^ 分析是否是自定義布局,^3^ 反射 View 的第二個 constructor 並將 View 創建 ```java= View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } // Apply a theme wrapper, if allowed and one is specified. if (!ignoreThemeAttr) { final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); } try { // @ 1. 嘗試創建 View View view = tryCreateView(parent, name, context, attrs); // @ 若是上一個步驟仍然沒有創建 View 則 if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { // 2. 分析是否是自定義布局 if (-1 == name.indexOf('.')) { // 原生布局,其實最終還是後呼叫 createView view = onCreateView(context, parent, name, attrs); } else { // 自定義布局 view = createView(context, name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; } catch (...) { throw e; } /** * 創建 View */ public final View createView(@NonNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs) throws ClassNotFoundException, InflateException { Objects.requireNonNull(viewContext); Objects.requireNonNull(name); Constructor<? extends View> constructor = sConstructorMap.get(name); if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Class<? extends View> clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it clazz = Class.forName(prefix != null ? (prefix + name) : name, false, mContext.getClassLoader()).asSubclass(View.class); if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, viewContext, attrs); } } // 3. 找尋符合條件的建構函數,這是 ++兩參++ 的 View 構造函數 constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { ... 省略 } Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = viewContext; Object[] args = mConstructorArgs; args[1] = attrs; try { // @ 創建 View final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } // @ 返回反射創建的 View return view; } finally { mConstructorArgs[0] = lastContext; } } catch (NoSuchMethodException e) { ...以下省略 } ``` 1. 檢查是否有字定義創建 View,也就是這裡可以注入三個類,分別是 Factory、Factory2(第三個 mPrivateFactory 也是 Factory2) ```java= public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { ...省略部份 View view; if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } return view; } ``` 2. 使用反射創建時,會找到規定的建構函數 clazz.getConstructor(mConstructorSignature) ```java= // 這個就是我們自定義 View 時必須覆寫的建構函數 (應該很熟悉?... // 就是自定義 View 時必須寫的建構函數) static final Class<?>[] mConstructorSignature = new Class[] { Context.class, AttributeSet.class}; ``` --- * 再來看看我們關注個第二點,有關於到使用者傳入的參數 **root、attachToRoot**,這會產生以下狀況 | root | attachToRoot | 狀況 | | -------- | -------- | -------- | | null | true / false | 建構的 View 是獨立個體,屬性無效 | | 有 ParentView | true | ParentView 屬性有效,產生的 View 是子布局,使用 addView 實現 | | 有 ParentView | false | ParentView 屬性有效,產生的 View 不是子布局,將 ParentView 的 LayoutParams 設定給 View | ```java= // 如果 ParentView (root) 為空,直接返回該 View if (root == null || !attachToRoot) { result = temp; } // 如果 ParentView (root) 不為空 && attachToRoot true if (root != null && attachToRoot) { // 透過對 ParentView 使用 addView root.addView(temp, params); } // 如果 ParentView (root) 不為空 && attachToRoot false if (root != null) { params = root.generateLayoutParams(attrs); if (!attachToRoot) { // ParentView 的屬性給 View 使用 // params 是 ParentView 的 LayoutParams temp.setLayoutParams(params); } } ``` ## Appendix & FAQ :::info ::: ###### tags: `Android Framework`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully