前幾天凱子哥寫的Framework層的解析文章《Activity啓動過程全解析》,反響還不錯,這說明"寫讓你們都能看懂的Framework解析文章"的思路是基本正確的。
我我的以爲,深刻分析的文章必不可少,可是更多的Android開發者——即只想作應用層開發,不想了解底層實現細節——來講,"總體上把握,重要環節深刻"是更好的學習方式。所以這樣既能夠有完整的知識體系,又不會在好漢的源碼世界裏迷失興趣和方向。
因此呢,今天凱子哥又帶來一篇文章,接着上一篇的結尾,終點介紹Activity開啓後,Android系統對界面的一些操做及相關知識。java
Windowandroid
PhoneWindowweb
WindowManagerwindows
WindowManagerImpl設計模式
WindowManagerGlobalapp
RootViewImplide
DecorView函數
Dialogoop
PopWindow佈局
Toast
瞭解Android中Activity界面顯示的流程,涉及到的關鍵類,以及關鍵流程
解決在開發中常常遇到的問題,並在源碼的角度弄清其緣由
瞭解Framework層與Window相關的一些概念和細節
老樣子,我們仍是和上次同樣,採用一問一答的方式進行學習,畢竟"帶着問題學習"纔是比較高效的學習方式。
話說,在上次的文章中,咱們解析到了從手機開機第一個zygote進程開啓,到App的第一個Activity的onCreate()結束,那麼咱們這裏就接着上次留下的茬,從第一個Activity的onCreate()開始提及。
一個最簡單的onCreate()以下:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
經過上面幾行簡單的代碼,咱們的App就能夠顯示在activity_main.xml文件中設計的界面了,那麼這一切究竟是怎麼作到的呢?
咱們跟蹤一下源碼,而後就在Activity的源碼中找到了3個setContentView()的重載函數:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
咱們上面用到的就是第一個方法。雖然setContentView()的重載函數有3種,可是咱們能夠發現,內部作的事情都是基本同樣的。首先調用getWindow()獲取到一個對象,而後調用這個對象的相關方法。
我們先來看一下,getWindow()到底獲取到了什麼對象。
private Window mWindow;
public Window getWindow() {
return mWindow;
}
喔,原來是一個Window對象,你如今可能不知道Window究竟是個什麼萬一,可是不要緊,你只要能猜到它確定和我們的界面實現有關係就得了,畢竟叫"Window"麼,Windows系統的桌面不是叫"Windows"桌面麼,差很少的東西,反正是用來顯示界面的就得了。
那麼initWindowDecorActionBar()函數作什麼的呢?
寫了這麼多程序,看名字也應該能猜出八九不離十了,init是初始化,Window是窗口,Decor是裝飾,ActionBar就更不用說了,因此這個方法應該就是"初始化裝飾在窗口上的ActionBar",來,我們看一下代碼實現:
/** * Creates a new ActionBar, locates the inflated ActionBarView, * initializes the ActionBar with the view, and sets mActionBar. */
private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
喲,沒想到這裏第一行代碼就又調用了getWindow(),接着往下調用了window.getDecorView(),從註釋中咱們知道,在調用這個方法以後,Window的特徵標誌就被初始化了,還記得如何讓Activity全屏嗎?
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FILL_PARENT, WindowManager.LayoutParams.FILL_PARENT);
setContentView(R.layout.activity_main);
}
並且這兩行代碼必須在setContentView()以前調用,知道爲啥了把?由於在這裏就把Window的相關特徵標誌給初始化了,在setContentView()以後調用就不起做用了!
若是你還不肯定的話,咱們能夠再看下window.getDecorView()的部分註釋:
/** * Note that calling this function for the first time "locks in" * various window characteristics as described in * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} */
public abstract View getDecorView();
"注意,這個方法第一次調用的時候,會鎖定在setContentView()中描述的各類Window特徵"
因此說,這也一樣解釋了爲何在setContentView()以後設置Window的一些特徵標誌,會不起做用。若是之後遇到相似問題,能夠往這方面想一下。
在上一個問題裏面,我們提到了一個很重要的類——Window,下面先簡單看一下這個類的幾個方法:
public abstract class Window {
public abstract void setContentView(int layoutResID);
public abstract void setContentView(View view);
public abstract void setContentView(View view, ViewGroup.LayoutParams params);
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
}
哇塞,有個好眼熟的方法,findViewById()~!
是的,在你每次在Activity中用的這個方法,其實間接的調用了Window類裏面的方法!
public View findViewById(int id) {
return getWindow().findViewById(id);
}
不過,findViewById()的最終實現是在View及其子類裏面的,因此getDecorView()獲取到的確定是一個View對象或View的子類對象:
public abstract View getDecorView();
Activity、Window中的findViewById()最終調用的,實際上是View的findViewById()。
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
public final View findViewById(int id) {
if (id < 0) {
return null;
}
return findViewTraversal(id);
}
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
return null;
}
}
可是,很顯然,最終調用的確定不是View類裏面的findViewTraversal(),由於這個方法指揮返回自身。
並且,findViewById()是final修飾的,不可被重寫,因此說,確定是調用的被子類重寫的findViewTraversal(),再聯想到,咱們的界面上有不少的View,那麼既能做爲View的容器,又是View的子類的類是什麼呢?很顯然,是ViewGroup!
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
}
}
因此說,在onCreate()中調用findViewById()對控件進行綁定的操做,實質上是經過在某個View中查找子View實現的,這裏你先記住,這個View叫作DecorView,並且它位於用戶窗口的最下面一層。
話說,我們前面介紹Window的時候,只是簡單的介紹了下findViewById(),尚未詳細的介紹下這個類,下面我們一塊兒學習一下。
前面提到過,Window是一個抽象類,抽象類確定是不能實例化的,因此我們須要使用的是它的實現類,Window的實現類有哪些呢?我們從Dash中看下Window類的文檔
在每一個Activity中都一個Window類型的對象mWindow,那麼是何時初始化的呢?
是在attach()的時候。
還記得attach()是何時調用的嗎?是在ActivityThread.performLaunchActivity()的時候:
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);
} catch (Exception e) {
...ignore some code...
}
try {
...ignore some code...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.voiceInteractor);
...ignore some code...
} catch (Exception e) { }
return activity;
}
在attach()裏面作了些什麼呢?
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback {
private Window mWindow;
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, IVoiceInteractor voiceInteractor) {
...ignore some code...
//就是在這裏實例化了Window對象
mWindow = PolicyManager.makeNewWindow(this);
//設置各類回調
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//這就是傳說中的UI線程,也就是ActivityThread所在的,開啓了消息循環機制的線程,因此在Actiivty所在線程中使用Handler不須要使用Loop開啓消息循環。
mUiThread = Thread.currentThread();
...ignore some code...
//終於見到了前面提到的WindowManager,能夠看到,WindowManager屬於一種系統服務
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();
}
}
attach()是Activity實例化以後,調用的第一個函數,在這個時候,就實例化了Window。那麼這個PolicyManager是什麼玩意?
mWindow = PolicyManager.makeNewWindow(this);
來來來,我們一塊兒RTFSC(Read The Fucking Source Code)!
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
static {
// Pull in the actual implementation of the policy at run-time
try {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
} catch (InstantiationException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
}
}
private PolicyManager() {}
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
}
"Policy"是"策略"的意思,因此就是一個策略管理器,採用了策略設計模式。而sPolicy是一個IPolicy類型,IPolicy其實是一個接口
public interface IPolicy {}
因此說,sPolicy的實際類型是在靜態代碼塊裏面,利用反射進行實例化的Policy類型。靜態代碼塊中的代碼在類文件加載進類加載器以後就會執行,sPolicy就實現了實例化。
那咱們卡下在Policy裏面其實是作了什麼
public class Policy implements IPolicy {
//看見PhoneWindow眼熟麼?還有DecorView,眼熟麼?這就是前面所說的那個位於最下面的View,findViewById()就是在它裏面找的
private static final String[] preload_classes = {
"com.android.internal.policy.impl.PhoneLayoutInflater",
"com.android.internal.policy.impl.PhoneWindow",
"com.android.internal.policy.impl.PhoneWindow$1",
"com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback",
"com.android.internal.policy.impl.PhoneWindow$DecorView",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
};
//因爲性能方面的緣由,在當前Policy類加載的時候,會預加載一些特定的類
static {
for (String s : preload_classes) {
try {
Class.forName(s);
} catch (ClassNotFoundException ex) {
Log.e(TAG, "Could not preload class for phone policy: " + s);
}
}
}
//終於找到PhoneWindow了,我沒騙你吧,前面我們所說的Window終於能夠換成PhoneWindow了~
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
}
上面說了這麼多,實際上只是追蹤到了PhoneWindow.setContentView(),下面看一下到底在這裏執行了什麼:
@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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
當咱們第一次調用setContentView()的時候,mContentParent是沒有進行初始化的,因此會調用installDecor()。
爲何能肯定mContentParent是沒有初始化的呢?由於mContentParent就是在installDecor()裏面賦值的
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
在generateDecor()作了什麼?返回了一個DecorView對象。
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
還記得前面推斷出的,DecorView是一個ViewGroup的結論嗎?看下面,DecorView繼承自FrameLayout,因此我們的推論是徹底正確的。並且DecorView是PhoneWindow的私有內部類,這兩個類關係緊密!
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {}
}
我們再看一下在對mContentParent賦值generateLayout(mDecor)作了什麼
protected ViewGroup generateLayout(DecorView decor) {
...判斷並設置了一堆的標誌位...
//這個是咱們的界面將要採用的基礎佈局xml文件的id
int layoutResource;
//根據標誌位,給layoutResource賦值
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
}
...咱們設置不一樣的主題以及樣式,會採用不一樣的佈局文件...
else {
//咱們在下面代碼驗證的時候,就會用到這個佈局,記住它哦
layoutResource = R.layout.screen_simple;
}
//要開始更改mDecor啦~
mDecor.startChanging();
//將xml文件解析成View對象,至於LayoutInflater是如何將xml解析成View的,我們後面再說
View in = mLayoutInflater.inflate(layoutResource, null);
//decor和mDecor其實是同一個對象,一個是形參,一個是成員變量
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//這裏的常量ID_ANDROID_CONTENT就是 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
//並且,因爲是直接執行的findViewById(),因此本質上仍是調用的mDecor.findViewById()。而在上面的decor.addView()執行以前,decor裏面是空白的,因此咱們能夠判定,layoutResource所指向的xml佈局文件內部,必定存在一個叫作"content"的ViewGroup
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
......
mDecor.finishChanging();
//最後把id爲content的一個ViewGroup返回了
return contentParent;
}
當上面的代碼執行以後,mDecor和mContentParent就初始化了,往下就會執行下面的代碼。利用LayoutInflater把我們傳進來的layoutResID轉化成View對象,而後添加到id爲content的mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
因此到目前爲止,我們已經知道了如下幾個事實,我們總結一下:
DecorView是PhoneWindow的內部類,繼承自FrameLayout,是最底層的界面
PhoneWindow是Window的惟一子類,他們的做用就是提供標準UI,標題,背景和按鍵操做
在DecorView中會根據用戶選擇的不一樣標誌,選擇不一樣的xml文件,而且這些佈局會被添加大DecorView中
在DecorView中,必定存在一個叫作"content"的ViewGroup,並且咱們在xml文件中聲明的佈局文件,會被添加進去
既然是事實,那麼怎麼樣才能驗證一下呢?
我們下篇再說~
做者:凱子哥
連接:http://blog.csdn.net/zhaokaiqiang1992