編輯:關於Android編程
PS一句:當初你所逃避的問題終會在未來的某一天重新出現在你面前,因此,當你第一次遇到它時,請不要逃避。
相信很多初學者對XML布局怎麼加載到Activity上並且顯示在手機屏幕上很好奇吧?今天我們就從經常使用的方法
setContentView來從源碼分析一下XML布局是怎麼加載到當前Activity上的。
我們知道,Activity是在onCreate方法中使用setContentView方法來加載布局的,那麼它內部的源碼是怎麼實現的呢?
處於好奇,我們進入了Activity的源碼,找到setContentView方法如下:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
setContentView方法實現很簡單,裡面調用兩個方法,我們這裡分析第一個方法:
getWindow().setContentView(layoutResID);
getWindow()得到一個Window對象 mWindow ,Window是一個抽象類,用來描述Activity視圖最頂端的窗口顯示和行為操作。Window是
一個抽象類,那麼裡面的setContentView(layoutResID)是一個抽象方法,並沒有具體的實現,既然Window是一個抽象類,那麼
在Activity裡面就有一個Window抽象類的實現,我們查找代碼發現 mWindow對象賦值方法如下:
mWindow = PolicyManager.makeNewWindow(this);
Look the fuck resoure code,繼續看看 PolicyManager類
public final class More PolicyManager {
30 private static final String POLICY_IMPL_CLASS_NAME =
31 "com.android.internal.policy.impl.Policy";
32
33 private static final IPolicy sPolicy;
34
35 static {
36 // Pull in the actual implementation of the policy at run-time
37 try {
38 Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
39 sPolicy = (IPolicy)policyClass.newInstance();
40 } catch (ClassNotFoundException ex) {
41 throw new RuntimeException(
42 POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
43 } catch (InstantiationException ex) {
44 throw new RuntimeException(
45 POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
46 } catch (IllegalAccessException ex) {
47 throw new RuntimeException(
48 POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
49 }
50 }
51
52 // Cannot instantiate this class
53 private More PolicyManager() {}
54
55 // The static methods to spawn new policy-specific objects
56 public static Window More makeNewWindow(Context context) {
57 return sPolicy.makeNewWindow(context);
58 }
地 57 行 sPolicy對象是有第 38,39行通過類路徑”com.android.internal.policy.impl.Policy“生成的,那麼我們在源碼中找到 Policy類,在此類中找到了如下方法:
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
由此可見,我們終於找到Activity類中的 mWindow對象的實現類了,就是PhoneWindow類。
從源碼發現,PhoneWindow類就是抽象類Window的實現類。那麼 setContentView方法的實現也在這個類裡面了,
到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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
首先判斷mContentParent是否為null,mContentParent是什麼呢?接下來會分析,一開始條件成立,進入installDecor()方法。
到installDecor()方法裡,實現如下:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
mDecorContentParent.setUiOptions(mUiOptions);
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
(mIconRes != 0 && !mDecorContentParent.hasIcon())) {
mDecorContentParent.setIcon(mIconRes);
} else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
mIconRes == 0 && !mDecorContentParent.hasIcon()) {
mDecorContentParent.setIcon(
getContext().getPackageManager().getDefaultActivityIcon());
mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
}
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
(mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
mDecorContentParent.setLogo(mLogoRes);
}
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!isDestroyed() && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
} else {
mTitleView = (TextView)findViewById(R.id.title);
if (mTitleView != null) {
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(
R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
// Only inflate or create a new TransitionManager if the caller hasn't
// already set a custom one.
if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
if (mTransitionManager == null) {
final int transitionRes = getWindowStyle().getResourceId(
R.styleable.Window_windowContentTransitionManager,
0);
if (transitionRes != 0) {
final TransitionInflater inflater = TransitionInflater.from(getContext());
mTransitionManager = inflater.inflateTransitionManager(transitionRes,
mContentParent);
} else {
mTransitionManager = new TransitionManager();
}
}
mEnterTransition = getTransition(mEnterTransition, null,
R.styleable.Window_windowEnterTransition);
mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
R.styleable.Window_windowReturnTransition);
mExitTransition = getTransition(mExitTransition, null,
R.styleable.Window_windowExitTransition);
mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
R.styleable.Window_windowReenterTransition);
mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
R.styleable.Window_windowSharedElementEnterTransition);
mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
USE_DEFAULT_TRANSITION,
R.styleable.Window_windowSharedElementReturnTransition);
mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
R.styleable.Window_windowSharedElementExitTransition);
mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
USE_DEFAULT_TRANSITION,
R.styleable.Window_windowSharedElementReenterTransition);
if (mAllowEnterTransitionOverlap == null) {
mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
R.styleable.Window_windowAllowEnterTransitionOverlap, true);
}
if (mAllowReturnTransitionOverlap == null) {
mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
R.styleable.Window_windowAllowReturnTransitionOverlap, true);
}
if (mBackgroundFadeDurationMillis < 0) {
mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
R.styleable.Window_windowTransitionBackgroundFadeDuration,
DEFAULT_BACKGROUND_FADE_DURATION_MS);
}
if (mSharedElementsUseOverlay == null) {
mSharedElementsUseOverlay = getWindowStyle().getBoolean(
R.styleable.Window_windowSharedElementsUseOverlay, true);
}
}
}
}
在代碼的第 3 行我們看到 mDecor = generateDecor();方法調用,繼續跳進 generateDecor()方法:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
這裡生成一個DecorView對象,DecorView是PhoneWindow類的內部類,繼承自FrameLayout。到目前為止,setContentView
方法裡生成一個FrameLayout類型的DecorView組件。
繼續分析代碼,看第 11 行:
mContentParent = generateLayout(mDecor);
把 DecorView 對象 mDecor 作為參數傳遞給 generateLayout方法得到 mContentParent。generateLayout()方法中的代碼實現如下:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
.........
/**以下這些是Activity 窗口屬性特征的設置*/
//窗口是否浮動,一般用於Dialog窗口是否浮動:是否顯示在布局的正中間。
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
//設置窗口是否支持標題欄,隱藏顯示標題欄操作在此處。
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
//ActionBar導航欄是否不占布局空間疊加顯示在當前窗口之上。
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
requestFeature(FEATURE_SWIPE_TO_DISMISS);
}
//當前Activity是否支持全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
................
//設置狀態欄的顏色
if (!mForcedStatusBarColor) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
//設置導航欄的顏色
if (!mForcedNavigationBarColor) {
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
}
if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB) {
if (a.getBoolean(
R.styleable.Window_windowCloseOnTouchOutside,
false)) {
setCloseOnTouchOutsideIfNotSet(true);
}
}
WindowManager.LayoutParams params = getAttributes();
//設置輸入法的狀態
if (!hasSoftInputMode()) {
params.softInputMode = a.getInt(
R.styleable.Window_windowSoftInputMode,
params.softInputMode);
}
if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
mIsFloating)) {
/* All dialogs should have the window dimmed */
if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
}
if (!haveDimAmount()) {
params.dimAmount = a.getFloat(
android.R.styleable.Window_backgroundDimAmount, 0.5f);
}
}
//設置當前Activity的出現動畫效果
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
R.styleable.Window_windowAnimationStyle, 0);
}
// The rest are only done if this window is not embedded; otherwise,
// the values are inherited from our container.
if (getContainer() == null) {
if (mBackgroundDrawable == null) {
if (mBackgroundResource == 0) {
mBackgroundResource = a.getResourceId(
R.styleable.Window_windowBackground, 0);
}
if (mFrameResource == 0) {
mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
}
mBackgroundFallbackResource = a.getResourceId(
R.styleable.Window_windowBackgroundFallback, 0);
if (false) {
System.out.println("Background: "
+ Integer.toHexString(mBackgroundResource) + " Frame: "
+ Integer.toHexString(mFrameResource));
}
}
mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
}
//以下代碼為當前Activity窗口添加 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();
//通過布局添加器LayoutInflater獲取layoutResource布局,
View in = mLayoutInflater.inflate(layoutResource, null);
//將XML資源為layoutResource的布局添加到decor容器裡面,至此PhoneWindow 內部類DecorView就添加了之布局
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//此處很重要,通過findViewById找到 contentParent容器,也是該方法的返回值。
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.
//以下代碼設置Activity窗口的背景,標題等
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;
}
以上代碼代比較復雜,主要做了以下幾件事情。
從第 8 行到第110行,主要是初始化窗口的特征,是否顯示標題欄,是否全屏,是否支持ActionBar浮動等等。 第112-187行,主要是給容器DecorView添加id為layoutResource布局的根布局。待會分析layoutResource布局。 第178行,通過LayoutInflater 將xml布局轉換成VIEW. 第180行將找到的View添加到DecorView根布局當中。 第184行,從根布局中找到id為R.id.content的 contentParent 容器。也就是當前方法的返回值。
<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>
原來我們的DecorView根布局裡面添加了類似上面的布局,線性布局LinearLaout裡包含兩個組件,ViewStub是懶加載,默認不顯示,FrameLayout是什麼呢?看看id=content,就是我們184行找到的父容器 contentParent。那麼這個父容器 contentParent有什麼作用呢?
我們回到 Step4 的第 11行,mContentParent = generateLayout(mDecor); 獲得 父容器 mContentParent。我們再次回到 Step3步的第17行, mLayoutInflater.inflate(layoutResID, mContentParent); 這裡通過LayoutInflater將 setContentView(layoutResID)傳進來的布局id加載到 父容器mContentParent中,至此,setContentView就將布局添加到Activity裡面了。
現在我們來梳理一下流程:
Activity setContentView—>Window setContentView—>PhoneWindow setContentView—->PhoneWindow installDecor—–>PhoneWindow generateLayout——>PhoneWindow mLayoutInflater.inflate(layoutResID, mContentParent);
Activity 類中有一個Window抽象類的實現PhoneWindow類,該類中有個內部類DecorView,繼承自FrameLayout,在DecorView容器中添加了根布局,根布局中包含了一個id為 contnet的FrameLayout 內容布局,我們的Activity加載的布局xml最後添加到 id為content的FrameLayout布局當中了。用一個圖來描述,如下:
vcC4o6zI57n70OjSqsils/21vLq9wLijrMTjv8nS1M2ouf3I58/CtPrC66O6Z2V0U3VwcG9ydEFjdGlvbkJhcigpLmhpZGUoKTvAtNL+sti1vLq9wLihozwvcD4NCjxwPjIucmVxdWVzdFdpbmRvd0ZlYXR1cmUoV2luZG93LkZFQVRVUkVfTk9fVElUTEUpO7e9t6jQ6NKq1Nogc2V0Q29udGVudFZpZXe3vbeo1q7HsMq508OjrNPJyc/D5iBTdGVwNbfWzva/ybXDo6zJ6NbDQWN0aXZpdHkgV2luZG93IMzY1ffKx9Tac2V0Q29udGVudFZpZXe3vbeo1tDJ6NbDtcSjrNLytMujrMjnufvQ6NKquMSx5EFjdGl2aXR5IFdpbmRvd7Swv9rM2NX3o6zQ6NKq1NpzZXRDb250ZW50Vmlld7e9t6jWrsewoaPG5Mq11eLA79PQ0snOyqO/o7+jv86qyrLDtMno1sPIq8bBtcS3vbeoPC9wPg0KPHByZSBjbGFzcz0="brush:java;">
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
可以在setContentView之後呢???求解。
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
前面文章介紹了Android利用麥克風采集並顯示模擬信號的實現方法,這種采集手段適用於無IO控制、單純讀取信號的情況。如果傳感器本身需要包含控制電路(例如采集血氧信號需要
目錄概述StickHeaderItemDecoration是用於顯示固定頭部的item裝飾類,擴展來自系統的ItemDecoration.本文參考了一部分sticky-h
android gridview畫分割線,如圖:1。先上圖:2.具體實現代碼:public class LineGridView extends GridView {
Fragment和Activity的交互 一個Fragment的實例總是和包含它的Activity直接相關。 fragment可以通過getActivity