前言:
當(dāng)做Android開發(fā)一段時(shí)間后,對于了解熟悉系統(tǒng)源碼是一個(gè)無法逃避的劫鸣剪,因?yàn)椴还茉谝院笳夜ぷ髅嬖嚵闳兀€是提升自己的技術(shù)水平都能起著(高逼格)不小的作用卿嘲,這里就從Activity的setContentView() 開始慷嗜,一點(diǎn)一點(diǎn)的揭開Android系統(tǒng)源碼的神秘面紗 同時(shí)本文是基于25的版本可能對于低版本源碼有些出入淀弹。
分析:
在分析源碼先來一張個(gè)人整理的思維導(dǎo)圖
都說要帶著問題去分析:
- setContentView到底做了些什么,為什么調(diào)用后就可以顯示出我們想要的布局頁面庆械?
- PhoneWindow倒是什么東西薇溃?Window和它是什么關(guān)系?
- DecorView是干什么用的缭乘?和我們的布局又有什么樣的關(guān)系
- requestFeature為什么要在setContentView之前調(diào)用沐序?
源碼閱讀:
public class MainActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
這段代碼誰都熟悉 沒什么好講的 直接進(jìn)入Activity setContentView()
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
調(diào)用了getWindow().setContentView(layoutResID);
getWindow()
獲取到的是什么呢
public Window getWindow() {
return mWindow;
}
//Window類型的成員變量mWindow 繼續(xù)跟進(jìn)
private Window mWindow;
//找到mWindow賦值的地方
final void attach(...){
//在Activity的attach()生命周期方法內(nèi)創(chuàng)建了一個(gè)PhoneWindow對象
mWindow = new PhoneWindow(this, window);
mWindow.setCallback(this);
}
進(jìn)入PhoneWindow看看與Window是什么關(guān)系
public class PhoneWindow extends Window implements MenuBuilder.Callback {...}
PhoneWindow就是Window的子類 那就是調(diào)用了PhoneWindow的setContentView()
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private ViewGroup mContentParent;
private LayoutInflater mLayoutInflater;
@Override
public void setContentView(int layoutResID) {
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();
}
}
}
分析以上源碼 當(dāng)mContentParent為null時(shí) 會(huì)走 installDecor()
函數(shù)
private DecorView mDecor;
private void installDecor() {
if (mDecor == null) {
//初始化mDecor
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//初始化mContentParent
mContentParent = generateLayout(mDecor);
.......
}
}
在為mContentParent
初始化的時(shí)候 先初始化了DecorView
有必要了解下DecorView
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {...}
DecorView 就是PhoneWindow一個(gè)內(nèi)部類 并且繼承自FrameLayout
再看 mContentParent
是怎么初始化的
protected ViewGroup generateLayout(DecorView decor) {
//獲取Activity的主題
TypedArray a = getWindowStyle();
//主題是否為浮動(dòng)的
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);
}
//NoTitle 沒有tite主題
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
//設(shè)置主題樣式
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
//帶有ACTION_BAR主題
requestFeature(FEATURE_ACTION_BAR);
}
/*... 后面一個(gè)套路 獲取主題樣式并且設(shè)置對應(yīng)的主題樣式...*/
//是否全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
....................................................
//主題樣式 和window樣式都在這一塊設(shè)置 這里先不去講解 有興趣的可以自下了解 同時(shí)也證明一點(diǎn) 調(diào)用requestFeature() 或者需要改變Theme有效 必須在setContentView()的原因就在這里
...................................................
int layoutResource;
//根據(jù)不同的主題設(shè)置不同的根布局
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.
//帶progress的布局文件
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
//帶ACTION_BAR的布局文件
layoutResource = R.layout.screen_custom_title;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
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 {
//帶title的布局
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 {
//默認(rèn)的布局文件 也是最常用的
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
//mLayoutInflater解析布局文件
View in = mLayoutInflater.inflate(layoutResource, null);
//添加到 DecorView中(FrameLayout) 并且寬高屬性設(shè)置為match_parent
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
//初始化mContentRoot為in
mContentRoot = (ViewGroup) in;
//contentParent的值為id為:ID_ANDROID_CONTENT的View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
.....
//將contentParent返回
return contentParent;
}
R.layout.screen_simple
<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>
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
以上操作就是根據(jù)不同的主題獲取不同的根布局文件 并添加到DecorView中 同時(shí)將根布局文件View對象賦值給 mContentRoot
同時(shí)找到布局文件中id為:R.id.content的view 返回賦值給 mContentParent
這時(shí)mContentParent
就是布局文件中的FrameLayout
接著往下走 當(dāng)mContentParent
不為空時(shí)會(huì)將其包含的子View全部刪除
mContentParent.removeAllViews();
最后通LayoutInflater 將布局文件解析并添加到mContentParent
中
mLayoutInflater.inflate(layoutResID, mContentParent);
因?yàn)槠邢?這里先不分析LayoutInflater 是如何解析xml布局文件 并且將view添加到rootView中
到這里Activity中的View樹差不多是這樣子的
最后還有一段代碼
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
//getCallback()
public final Callback getCallback() {
return mCallback;
}
//有g(shù)etCallback() 肯定有setCallback()
public void setCallback(Callback callback) {
mCallback = callback;
}
//這個(gè)Callback 是什么呢
public abstract class Window {
//Window中接口
public interface Callback {
public void onContentChanged();
..............
}
}
//因?yàn)锳ctivity實(shí)現(xiàn)了該接口 并且在attach設(shè)置了setCallback(this)
public class Activity extends ContextThemeWrapper implements Window.Callback{
final void attach(...){
//在Activity的attach()生命周期方法內(nèi)創(chuàng)建了一個(gè)PhoneWindow對象
mWindow = new PhoneWindow(this, window);
//設(shè)置Callback對象
mWindow.setCallback(this);
}
}
由以上分析所以這里得到的是Activity對象
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//調(diào)用Activity的onContentChanged()
cb.onContentChanged();
}
再看Activity的onContentChanged()
//空函數(shù)
public void onContentChanged() {}
該函數(shù)什么都操作 那定義該函數(shù)的意義何在 于是又去看了下 Window
中Callback
接口對該函數(shù)的定義:
//Window中接口
public interface Callback {
/**
* This hook is called whenever the content view of the screen changes
* (due to a call to
* {@link Window#setContentView(View, android.view.ViewGroup.LayoutParams)
* Window.setContentView} or
* {@link Window#addContentView(View, android.view.ViewGroup.LayoutParams)
* Window.addContentView}).
*/
public void onContentChanged();
..............
}
其中大概意思是:
當(dāng)屏幕的內(nèi)容發(fā)生改變完成時(shí)調(diào)用該函數(shù) 而導(dǎo)致屏幕內(nèi)容發(fā)生改變的操作有
Window#setContentView(View, android.view.ViewGroup.LayoutParams)
Window#addContentView(View, android.view.ViewGroup.LayoutParams)
到這里 結(jié)合上面的源碼分析就明白了一個(gè)大概 google留此函數(shù)給我們重寫 用于初始化子控件或者其他操作 因?yàn)樵趏nCreate()做初始化操作 容易因系統(tǒng)某些操作沒完成 造成空指針異常(在Java 5之后有并發(fā)編程)
public class MainActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager;//重力感應(yīng)
private Sensor sensor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 初始化操作
*/
@Override
public void onContentChanged() {
jbox_view = (CollisionView) findViewById(R.id.jbox_view);
initView();
sensorManager= (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor=sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);//重力傳感器
}
這里以 findViewById()函數(shù)為例來分析
public class Activity{
@Nullable
public View findViewById(@IdRes int id) {
//調(diào)用Window的findViewById(id)
return getWindow().findViewById(id);
}
}
public class Window{
@Nullable
public View findViewById(@IdRes int id) {
//getDecorView()顧名思義就是PhoneWindow中的DecorView
return getDecorView().findViewById(id);
}
}
public class PhoneWindow{
@Override
public final View getDecorView() {
if (mDecor == null) {
//這里有看到了熟悉的函數(shù) 里面就是DecorView的初始化操作 前面已經(jīng)分析過了
installDecor();
}
return mDecor;
}
}
經(jīng)過上面分析 在并發(fā)編程的情況下 如果我們在onCreate()中進(jìn)行findViewById操作可能會(huì)與setContentView()一起執(zhí)行 雖然系統(tǒng)中有對null的判斷操作 但是站在程序執(zhí)行流程順序來講findViewById()更適合放在onContentChanged()中去執(zhí)行