編輯:關於Android編程
好吧,終於要開始講講Activity的布局加載流程了,大家都知道在Android體系中Activity扮演了一個界面展示的角色,這也是它與android中另外一個很重要的組件Service最大的不同,但是這個展示的界面的功能是Activity直接控制的麼?界面的布局文件是如何加載到內存並被Activity管理的?android中的View是一個怎樣的概念?加載到內存中的布局文件是如何繪制出來的?
要想回答這些問題,我們就需要對android的界面加載與繪制流程有所了解,這裡我們先來學習一下Activity的布局加載的流程。而至於Acitivty的布局繪制流程我們在下一篇中在做介紹。
其實Activity對界面布局的管理是都是通過Window對象來實現的,Window對象,顧名思義就是一個窗口對象,而Activity從用戶角度就是一個個的窗口實例,因此不難想象每個Activity中都對應著一個Window對象,而這個Window對象就是負責加載顯示界面的。至於window對象是如何展示不同的界面的,那是通過定義不同的View組件實現不同的界面展示。
廢話不多說了,不知道大家是否還記得我們講過的Activity的啟動流程麼?這裡我們可以下其具體實現:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); mSomeActivitiesChanged = true; if (r.profilerInfo != null) { mProfiler.setProfiler(r.profilerInfo); mProfiler.startProfiling(); } // Make sure we are running with the most recent config. handleConfigurationChanged(null, null); if (localLOGV) Slog.v( TAG, "Handling launch of " + r); // Initialize before creating the activity WindowManagerGlobal.initialize(); Activity a = performLaunchActivity(r, customIntent); if (a != null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed); if (!r.activity.mFinished && r.startsNotResumed) { // The activity manager actually wants this one to start out // paused, because it needs to be visible but isn't in the // foreground. We accomplish this by going through the // normal startup (because activities expect to go through // onResume() the first time they run, before their window // is displayed), and then pausing it. However, in this case // we do -not- need to do the full pause cycle (of freezing // and such) because the activity manager assumes it can just // retain the current state it has. try { r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); // We need to keep around the original state, in case // we need to be created again. But we only do this // for pre-Honeycomb apps, which always save their state // when pausing, so we can not have them save their state // when restarting from a paused state. For HC and later, // we want to (and can) let the state be saved as the normal // part of stopping the activity. if (r.isPreHoneycomb()) { r.state = oldState; } if (!r.activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onPause()"); } } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to pause activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); } } r.paused = true; } } else { // If there was an error, for any reason, tell the activity // manager to stop us. try { ActivityManagerNative.getDefault() .finishActivity(r.token, Activity.RESULT_CANCELED, null, false); } catch (RemoteException ex) { // Ignore } } } 可以發現這裡的handleLauncherActivity方法內部調用了performLaunchActivity方法,這個方法也是具體啟動Activity的方法,我們來看一下它的具體實現邏輯: private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.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); } } ... 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) { Context appContext = createBaseContextForActivity(r, activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); 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); ... return activity; }
可以發現這裡的handleLauncherActivity方法內部調用了performLaunchActivity方法,這個方法也是具體啟動Activity的方法,我們來看一下它的具體實現邏輯:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.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);
}
}
...
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) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
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);
...
return activity;
}
從代碼中可以看到這裡是通過反射的機制創建的Activity,並調用了Activity的attach方法,那麼這裡的attach方法是做什麼的呢?我們繼續來看一下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) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); mMainThread = aThread; mInstrumentation = instr; mToken = token; mIdent = ident; mApplication = application; mIntent = intent; mReferrer = referrer; mComponent = intent.getComponent(); mActivityInfo = info; mTitle = title; mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; if (voiceInteractor != null) { if (lastNonConfigurationInstances != null) { mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor; } else { mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, Looper.myLooper()); } } mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }
可以看到在attach方法這裡初始化了一些Activity的成員變量,主要是mWindow對象,並且mWindow的成員實例是PhoneWindow實例,這樣也從側面說明了一個Activity對應著一個Window對象。除了window對象還初始化了一些Activity的其他成員變量,這裡不再做討論,繼續回到我們的performLaunchActivity方法,在調用了Activity的attach方法之後又調用了:
mInstrumentation.callActivityOnCreate(activity, r.state);
這裡的mInstrumentation是類Instrumentation,每個應用進程對應著一個Instrumentation和一個ActivityThread,Instrumentation就是具體操作Activity回調其生命周期方法的,我們這裡看一下它的callActivityOnCreate方法的實現:
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
這裡代碼比較簡潔,preOerformCreate方法和postPerformCreate方法我們這裡暫時不管,主要的執行邏輯是調用了activity.performCreate方法,我們來看一下Activity的performCreate方法的實現:
final void performCreate(Bundle icicle) {
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
原來onCreate的生命周期方法是在這裡回調的,其實這裡的邏輯在前面幾篇文章中有講述,也可以參考前面的文章。
至此我們就回調到了我們Activity的onCreate方法,大家平時在重寫onCreate方法的時候,怎麼加載布局文件的呢?這裡看一下我們的onCreate方法的典型寫法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
無論我們怎麼變化,我們的onCreate方法一般都是會調用這兩句話的吧?那麼這裡的兩段代碼分辨是什麼含義呢?我們首先看一下super.onCreate方法的實現邏輯,由於我們的Activity類繼承與Activity,所以這裡的super.onCreate方法,就是調用的Activity.onCreate方法,好吧,既然這樣我們來看一下Activity的onCreate方法:
protected void onCreate(@Nullable Bundle savedInstanceState) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState); if (mLastNonConfigurationInstances != null) { mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders); } if (mActivityInfo.parentActivityName != null) { if (mActionBar == null) { mEnableDefaultActionBarUp = true; } else { mActionBar.setDefaultDisplayHomeAsUpEnabled(true); } } if (savedInstanceState != null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.fragments : null); } mFragments.dispatchCreate(); getApplication().dispatchActivityCreated(this, savedInstanceState); if (mVoiceInteractor != null) { mVoiceInteractor.attachActivity(this); } mCalled = true; }
可以發現,Activity的onCreate方法主要是做了一些Acitivty的初始化操作,那麼如果我們不在自己的Activity調用super.onCreate方法呢?好吧,嘗試之後,AndroidStudio在打開的Acitivty的onCreate方法中如果不調用super.onCreate方法的話,會報錯。。。有木有搞錯。。。
FATAL EXCEPTION: main Process: com.example.aaron.helloworld, PID: 18001 android.util.SuperNotCalledException: Activity {com.example.aaron.helloworld/com.example.aaron.helloworld.SecondActivity} did not call through to super.onCreate() at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2422) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2528) at android.app.ActivityThread.access$800(ActivityThread.java:169) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1421) at android.os.Handler.dispatchMessage(Handler.java:111) at android.os.Looper.loop(Looper.java:194) at android.app.ActivityThread.main(ActivityThread.java:5552) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759)
可以看到如果不調用super.onCreate方法的話,會在Activity的performLaunchActivity中報錯,我們知道這裡的performLaunchActivity方法就是我們啟動Activity的時候回回調的方法,我們找找方法體實現中throws的Exception。。。
activity.mCalled = false; 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()"); }
在Activity的performLaunchActivity方法中,我們在調用了Activity的onCreate方法之後會執行一個判斷邏輯,若Activity的mCalled為false,則會拋出我們剛剛捕獲的異常,那麼這個mCalled成員變量是在什麼時候被賦值的呢?好吧,就是在Activity的onCreate方法賦值的,所以我們在實現自己的Activity的時候只有調用了super.onCreate方法才不會拋出這個異常,反過來說,我們實現自己的Actiivty,那麼一定要在onCreate方法中調用super.onCreate方法。
然後我們在看一下onCreate中的setContentView方法,這裡的參數就是一個Layout布局文件,可以發現這裡的setContentView方法就是Acitivty中的setContentView,好吧我們來看一下Activity中setContentView的實現:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
這裡的getWindow方法就是獲取Acitivty的mWindow成員變量,從剛剛我們在Activity.attach方法我們知道這裡的mWindow的實例是PhoneWindow,所以這裡調用的其實是PhoneWindow的setConentView方法,然後我們看一下PhoneWindow的setContentView是如何實現的。
@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } 這裡的mContentParent對象是一個View對象,由於第一次mContentParent為空,所以執行installerDector方法,這裡我們看一下installerDector方法的具體實現: private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } ... }
這裡的mContentParent對象是一個View對象,由於第一次mContentParent為空,所以執行installerDector方法,這裡我們看一下installerDector方法的具體實現:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
...
}
這裡的mDector是一個DectorView對象,而DectorView繼承與FrameLayout,所以這裡的mDector其實就是一個FrameLayout對象,並通過調用generateDector()方法初始化,我們繼續看一下generateDector方法的具體實現:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
好吧,就是通過new的方式創建了一個DectorView對象,然後我們繼續看installDector方法:
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
這裡初始化了mContentParent對象,這是一個View對象,我們調用了generateLayout方法,好吧,來看一下generateLayout方法的具體實現:
protected ViewGroup generateLayout(DecorView decor) { ... // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = R.layout.screen_progress; // System.out.println("Progress!"); } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { // Special case for a window with a custom title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { // If no other features and not embedded, only need a title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { layoutResource = a.getResourceId( R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar); } else { layoutResource = R.layout.screen_title; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = R.layout.screen_simple_overlay_action_mode; } else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // System.out.println("Simple!"); } mDecor.startChanging(); View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { ProgressBar progress = getCircularProgressBar(false); if (progress != null) { progress.setIndeterminate(true); } } if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { registerSwipeCallbacks(); } // Remaining setup -- of background and title -- that only applies // to top-level windows. if (getContainer() == null) { final Drawable background; if (mBackgroundResource != 0) { background = getContext().getDrawable(mBackgroundResource); } else { background = mBackgroundDrawable; } mDecor.setWindowBackground(background); final Drawable frame; if (mFrameResource != 0) { frame = getContext().getDrawable(mFrameResource); } else { frame = null; } mDecor.setWindowFrame(frame); mDecor.setElevation(mElevation); mDecor.setClipToOutline(mClipToOutline); if (mTitle != null) { setTitle(mTitle); } if (mTitleColor == 0) { mTitleColor = mTextColor; } setTitleColor(mTitleColor); } mDecor.finishChanging(); return contentParent; }
可以發現這裡就是通過調用LayoutInflater.inflate方法來加載布局文件到內存中,關於LayoutInflater.inflater是如何加載布局文件的,並且,通過對代碼的分析,我們發現PhoneWindow中的幾個成員變量:mDector,mContentRoot,mContentParent的關系
mDector –> mContentRoot –> mContentParent(包含)
並且我們來看一下典型的布局文件:
<framelayout android:foreground="?android:attr/windowContentOverlay" android:foregroundgravity="fill_horizontal|top" android:foregroundinsidepadding="false" android:id="@android:id/content" android:layout_height="match_parent" android:layout_width="match_parent">
</framelayout>
這裡就是整個Activity加載的跟布局文件:screen_simple.xml,其中ViewStub對應著Activity中的titleBar而這裡的FrameLayout裡面主要用於填充內容。
然後我們具體看一下LayoutInflater.inflater方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
這裡調用了inflate的重載方法。。。
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 { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); } if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException(" can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } 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) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied 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); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (Exception e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } Trace.traceEnd(Trace.TRACE_TAG_VIEW); return result; } }
通過分析源碼,不難發現,主要是通過循環解析xml文件並將信息解析到內存View對象,布局文件中定義的一個個組件都被順序的解析到了內存中並被父子View的形式組織起來,這樣通過給定的一個root View就可以將整個布局文件中定義的組件全部解析。分析完解析布局文件,回到我們的setContentVIew方法,在調用了installDector方法之後,又調用了:
mLayoutInflater.inflate(layoutResID, mContentParent);
這個方法的含義就是將我們傳遞的客戶端的layoutId對應的布局文件作為mContentParent的子View加載到內存中,這樣我們的layoutId作為mContentParent的子View,而mContentParent又是mContentRoot的子View,mContentRoot又是mDector的子View,通過LayoutInflater的inflate方法逐步加載到了內存中,而我們的Activity又持有自身的PhoneWindow的引用,這就相當於我們的Activity持有了我們定義的布局文件的引用,因而Activity的布局文件被加載到了內存中。
總結:
Activity的展示界面的特性是通過Window對象來控制的;
每個Activity對象都對應這個一個Window對象,並且Window對象的初始化在啟動Activity的時候完成,在執行Activity的onCreate方法之前;
每個Window對象內部都存在一個FrameLayout類型的mDector對象,它是Acitivty界面的root view;
Activity中的window對象的實例是PhoneWindow對象,PhoneWindow對象中的幾個成員變量mDector,mContentRoot,mContentParent都是View組件,它們的關系是:mDector –> mContentRoot –> mContentParent –> 自定義layoutView
LayoutInflater.inflate主要用於將布局文件加載到內存View組件中,也可以設定加載到某一個父組件中;
典型的Activity的onCreate方法中需要調用super.onCreate方法和setContentView方法,若不調用super.onCreate方法,執行啟動該Activity的邏輯會報錯,若不執行setContentView的方法,該Activity只會顯示一個空頁面。
一、漏洞分析今天我們來看一下Android中的屏幕錄制功能帶來的一個漏洞問題,在之前的一篇文章中介紹了關於Android5.0新增的Api來進行錄制屏幕視頻,不了解的同學
左右切換圖片控件大家都用ViewPager, ViewFipper比較多吧,我之前也用ViewPager實現了,使用ViewPager實現左右循環滑動圖片,有興趣的可以去
經常玩手機的朋友都知道。微信朋友圈是是我們經常逛的地方,大家知道只要微信朋友圈有消息更新,我們微信界面上就會相信一個紅點提示,有強迫症的朋友可能就著急了,下
SlidingMenu簡介: SlidingMenu的是一種比較新的設置界面或配置界面效果,在主界面左滑或者右滑出現設置界面,能方便的進行各種操作.目前有大量的應用都在使