Android原始碼解析Window系列第(一)篇---Window的基本認識和Activity的Window建立過程
您可能聽說過View ,ViewManager,Window,PhoneWindow,WindowManager,WindowManagerService,可是你知道這幾個類是什麼關係,幹嘛用的。概括的來說,View是放在Window中的,Window是一個抽象類,它的具體實現是PhoneWindow,PhoneWindow還有個內部類DecorView,WindowManager是一個interface,繼承自ViewManager,它是外界訪問Window的入口,,提供了add/remove/updata的方法操作View,WindowManager與WindowManagerSerice是個跨程序的過程,WindowManagerService的職責是對系統中的所有視窗進行管理。如果您不太清楚,建議往下看,否則就不要看了。
1、Window的型別
Android系統的Window有很多種,大體上來說,Framework定義了三種視窗型別;
應用程式Window
所謂應用視窗指的就是該視窗對應一個Activity,因此,要建立應用視窗就必須在Activity中完成了。本節後面會分析Activity對應的Window的建立過程。子Window
所謂的子Window,是說這個Window必須要有一個父窗體,比如PopWindow,Dialog。系統Window
常見的系統Window有哪些呢?比如在手機電量低的時候,會有一個提示電量低的Window,我們輸入文字的時候,會彈出輸入法Window,還有搜尋條Window,來電顯示Window,Toast對應的Window,可以總結出來,系統Window是獨立與我們的應用程式的,對於應用程式而言,我們理論上是無法建立系統Window,因為沒有許可權,這個許可權只有系統程序有。
這就是Framework定義了三種視窗型別,這三種類型定義在WindowManager的內部類LayoutParams中,WindowManager講這三種類型 進行了細化,把每一種型別都用一個int常量來表示,這些常量代表視窗所在的層,WindowManagerService在進行視窗疊加的時候,會按照常量的大小分配不同的層,常量值越大,代表位置越靠上面,**所以我們可以猜想一下,應用程式Window的層值常量要小於子Window的層值常量,子Window的層值常量要小於系統Window的層值常量。**Window的層級關係如下所示。
實際上應用程式的Window的層級範圍是1~99,子Window的層級範圍是1000~1999,系統Window的層級範圍是2000~2999,這些值對應著WindowManager.LayoutParams的type引數,如果我們想視窗處在上面,那麼只要採用層級比較大的type就行了。OK,到此我們對Window有了一個初步的認識。
2、怎麼去描述一個Window
上面說了Window分為三種,用Window的type區分,在搞清楚Window的建立之前,我們需要知道怎麼去描述一個Window,我們就把Window當做一個實體類,給我的感覺,它必須要下面幾個欄位。
width:描述視窗的寬度
height:描述視窗的高度
type:這是哪一種型別的Window
實際上WindowManager.LayoutParams對Window有很詳細的定義。
public interface WindowManager extends ViewManager {
...........
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable {
//視窗的起點座標
public int x;
public int y;
//以下定義都是描述視窗的型別
public int type;
//第一個應用視窗
public static final int FIRST_APPLICATION_WINDOW = 1;
//所有程式視窗的base視窗,其他應用程式視窗都顯示在它上面
public static final int TYPE_BASE_APPLICATION = 1;
//所有Activity的視窗
public static final int TYPE_APPLICATION = 2;
//目標應用視窗未啟動之前的那個視窗
public static final int TYPE_APPLICATION_STARTING = 3;
//最後一個應用視窗
public static final int LAST_APPLICATION_WINDOW = 99;
//第一個子視窗
public static final int FIRST_SUB_WINDOW = 1000;
// 面板視窗,顯示於宿主視窗的上層
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
// 媒體視窗(例如視訊),顯示於宿主視窗下層
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;
// 應用程式視窗的子面板,顯示於所有面板視窗的上層
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
//對話方塊視窗
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
//
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4;
//最後一個子視窗
public static final int LAST_SUB_WINDOW = 1999;
//系統視窗,非應用程式建立
public static final int FIRST_SYSTEM_WINDOW = 2000;
//狀態列,只能有一個狀態列,位於螢幕頂端,其他視窗都位於它下方
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
//搜尋欄,只能有一個搜尋欄,位於螢幕上方
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
//電話視窗,它用於電話互動(特別是呼入),置於所有應用程式之上,狀態列之下
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
//系統警告提示視窗,出現在應用程式視窗之上
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
//鎖屏視窗
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//資訊視窗,用於顯示Toast
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
//系統頂層視窗,顯示在其他一切內容之上,此視窗不能獲得輸入焦點,否則影響鎖屏
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
//電話優先,當鎖屏時顯示,此視窗不能獲得輸入焦點,否則影響鎖屏
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
//系統對話方塊視窗
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
//鎖屏時顯示的對話方塊
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
//系統內部錯誤提示,顯示在任何視窗之上
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
//內部輸入法視窗,顯示於普通UI之上,應用程式可重新佈局以免被此視窗覆蓋
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
//內部輸入法對話方塊,顯示於當前輸入法視窗之上
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
//牆紙視窗
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
//狀態列的滑動面板
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
//安全系統覆蓋視窗,這些窗戶必須不帶輸入焦點,否則會干擾鍵盤
public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
//最後一個系統視窗
public static final int LAST_SYSTEM_WINDOW = 2999;
........
//視窗特徵標記
public int flags;
//當該window對使用者可見的時候,允許鎖屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
//視窗後面的所有內容都變暗
public static final int FLAG_DIM_BEHIND = 0x00000002;
//Flag:視窗後面的所有內容都變模糊
public static final int FLAG_BLUR_BEHIND = 0x00000004;
//視窗不能獲得焦點
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
//視窗不接受觸控式螢幕事件
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
//即使在該window在可獲得焦點情況下,允許該視窗之外的點選事件傳遞到當前視窗後面的的視窗去
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
//當手機處於睡眠狀態時,如果螢幕被按下,那麼該window將第一個收到觸控事件
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
//當該window對使用者可見時,螢幕出於常亮狀態
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
//:讓window佔滿整個手機螢幕,不留任何邊界
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
//允許視窗超出整個手機螢幕
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
//window全屏顯示
public static final int FLAG_FULLSCREEN = 0x00000400;
//恢復window非全屏顯示
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
//開啟視窗抖動
public static final int FLAG_DITHER = 0x00001000;
//安全內容視窗,該視窗顯示時不允許截圖
public static final int FLAG_SECURE = 0x00002000;
//鎖屏時顯示該視窗
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
//系統的牆紙顯示在該視窗之後
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
//當window被顯示的時候,系統將把它當做一個使用者活動事件,以點亮手機螢幕
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
//該視窗顯示,消失鍵盤
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
//當該window在可以接受觸控式螢幕情況下,讓因在該window之外,而傳送到後面的window的觸控式螢幕可以支援split touch
public static final int FLAG_SPLIT_TOUCH = 0x00800000;
//對該window進行硬體加速,該flag必須在Activity或Dialog的Content View之前進行設定
public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
//讓window佔滿整個手機螢幕,不留任何邊界
public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
//透明狀態列
public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
//透明導航欄
public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
..........
//軟輸入法模式
public int softInputMode;
//用於描述軟鍵盤顯示規則的bite的mask
public static final int SOFT_INPUT_MASK_STATE = 0x0f;
//沒有軟鍵盤顯示的約定規則
public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
//可見性狀態softInputMode,請不要改變軟輸入區域的狀態
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
//使用者導航(navigate)到你的視窗時隱藏軟鍵盤
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
//總是隱藏軟鍵盤
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
//使用者導航(navigate)到你的視窗時顯示軟鍵盤
public static final int SOFT_INPUT_STATE_VISIBLE = 4;
//總是顯示軟鍵盤
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
//顯示軟鍵盤時用於表示window調整方式的bite的mask
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
//不指定顯示軟體盤時,window的調整方式
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
//當顯示軟鍵盤時,調整window內的控制元件大小以便顯示軟鍵盤
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
//當顯示軟鍵盤時,調整window的空白區域來顯示軟鍵盤,即使調整空白區域,軟鍵盤還是有可能遮擋一些有內容區域,這時使用者就只有退出軟鍵盤才能看到這些被遮擋區域並進行
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
//當顯示軟鍵盤時,不調整window的佈局
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
//使用者導航(navigate)到了你的window
public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
//視窗的對齊方式
public int gravity;
//期望的點陣圖格式,預設為不透明,參考android.graphics.PixelFormat
public int format;
//視窗所使用的動畫設定,它必須是一個系統資源而不是應用程式資源,因為視窗管理器不能訪問應用程式
public int windowAnimations;
//整個視窗的半透明值,1.0表示不透明,0.0表示全透明
public float alpha = 1.0f;
//當FLAG_DIM_BEHIND設定後生效,該變數指示後面的視窗變暗的程度,1.0表示完全不透明,0.0表示沒有變暗
public float dimAmount = 1.0f;
public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
//用來覆蓋使用者設定的螢幕亮度,表示應用使用者設定的螢幕亮度,從0到1調整亮度從暗到最亮發生變化
public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;
public static final int ROTATION_ANIMATION_ROTATE = 0;
public static final int ROTATION_ANIMATION_CROSSFADE = 1;
public static final int ROTATION_ANIMATION_JUMPCUT = 2;
//螢幕旋轉動畫
public int rotationAnimation = ROTATION_ANIMATION_ROTATE;
//視窗的標示符
public IBinder token = null;
//此視窗所在應用的包名
public String packageName = null;
//視窗螢幕方向
public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
//控制status bar是否可見,兩種賦值 View#STATUS_BAR_VISIBLE;View#STATUS_BAR_HIDDEN
public int systemUiVisibility;
......
}
}
提取幾個重要的引數
- width:描述視窗的寬度,該變數是父類ViewGroup.LayoutParams的成員變數。
- height:描述視窗的高度,該變數同樣是父類ViewGroup.LayoutParams的成員變數。
- x:描述視窗的起點X軸的座標。
- y:描述視窗起點Y軸的座標。
- type:視窗的型別,分為三個大型別:應用視窗,子視窗,系統視窗。
- flag:視窗特徵標記,比如是否全屏,是否隱藏標題欄等。
- gravity:視窗的對齊方式,居中還是置頂或者置底等等。
Window是一個是一個抽象的概念,千萬不要認為我們所看到的就是Window,我們平時所看到的是檢視,每一個Window都對應著一個View,View和Window通過ViewRootImpl來建立聯絡。有了View,Window的存在意義在哪裡呢,因為View不能單獨存在,它必須依附著Window,所以有檢視的地方就有Window,比如Activity,一個Dialog,一個PopWindow,一個選單,一個Toast等等。
3、Window的建立過程與顯示過程
通過上面我們知道檢視和Window的關係,那麼有一個問題,是先有檢視,還是先有Window。這個答案只有在原始碼中找了。應用程式的入口類是ActivityThread,在ActivityThread中有performLaunchActivity來啟動Activity,這個performLaunchActivity方法內部會建立一個Activity。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
//通過反射機制建立一個Activity
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) {
...
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
//這個裡面建立了Window物件
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);
}
activity.mCalled = false;
if (r.isPersistable()) {
//呼叫Activity的onCreate方法
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
mActivities.put(r.token, r);
...
} catch (Exception e) {
...
}
return activity;
}
如果activity不為null,就會呼叫attach,在attach方法中通過PolicyManager建立了Window物件,並且給Window設定了回撥介面。
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);/設定回撥函式,使得Activity可以處理一些事件
PolicyManager的實現類是Policy
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
這樣Window就創建出來了,所以先有檢視,後有Window,檢視依賴Window存在,再說一說檢視(Activity)為Window設定的回撥介面。
/**
* API from a Window back to its caller. This allows the client to
* intercept key dispatching, panels and menus, etc.
*/
public interface Callback {
public boolean dispatchKeyEvent(KeyEvent event);
public boolean dispatchKeyShortcutEvent(KeyEvent event);
public boolean dispatchTouchEvent(MotionEvent event);
public boolean dispatchTrackballEvent(MotionEvent event);
public boolean dispatchGenericMotionEvent(MotionEvent event);
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
public View onCreatePanelView(int featureId);
public boolean onCreatePanelMenu(int featureId, Menu menu);
public boolean onPreparePanel(int featureId, View view, Menu menu);
public boolean onMenuOpened(int featureId, Menu menu);
public boolean onMenuItemSelected(int featureId, MenuItem item);
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);
public void onContentChanged();
public void onWindowFocusChanged(boolean hasFocus);
public void onAttachedToWindow();
public void onDetachedFromWindow();
public void onPanelClosed(int featureId, Menu menu);
public boolean onSearchRequested();
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback);
public void onActionModeStarted(ActionMode mode);
public void onActionModeFinished(ActionMode mode);
}
Activity實現了這個回撥介面,當Window的狀態發生變化的時候,就會回撥Activity中實現的這些介面,有些回撥介面我們還是熟悉的,dispatchTouchEvent,onAttachedToWindow,onDetachedFromWindow等。
下面分析view是如何附屬到window上的,通過上面可以看到,在attach之後就要執行callActivityOnCreate,在onCreate中我們會呼叫setContentView方法。
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow獲取了Window物件,Window的具體實現類是PhoneWindow,所以要看PhoneWindow的setContentView方法。
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) {
//第一步,構建DecroView
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 {
//第二步,將View新增到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//第三步,回撥Activity的onContentChanged方法,通知檢視發生了改變
cb.onContentChanged();
}
}
這裡涉及到一個mContentParent變數,他是一個DecorView的一部分,DecorView是PhoneWindow的一個內部類,我先介紹一下關於DecorView的知識。
DecorView是Activity的頂級VIew,DecorView繼承自FrameLayout,在DecorView中有上下兩個部分,上面是標題欄,下面是內容欄,我們通過PhoneWindow的setContentView所設定的佈局檔案是加到內容欄(mContentParent)裡面的,View層的事件都是先經過DecorView在傳遞給我們的View的。
OK在回到setContentView的原始碼分析,我們可以得到Activity的Window建立需要三步。
- 1、 如果沒有DecorView,在installDecor中建立DecorView。
- 2、將View新增到decorview中的mContentParent中。
- 3、回撥Activity的onContentChanged介面。
先看看第一步,installDecor的原始碼
...
if (mDecor == null) {
mDecor = generateDecor();
...
}
...
installDecor中呼叫了generateDecor,繼續看
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
直接給new一個DecorView,有了DecorView之後,就可以載入具體的佈局檔案到DecorView中了,具體的佈局檔案和系統和主題有關係。
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
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");
}
在看第二步,將View新增到decorview中的mContentParent中。
mLayoutInflater.inflate(layoutResID, mContentParent);
直接將Activity檢視加到DecorView的mContentParent中,最後一步,回撥Activity的onContentChanged介面。在Activity中尋找onContentChanged方法,它是個空實現,我們可以在子Activity中處理。
public void onContentChanged() {}
到此DecorView被建立完畢,我們一開始從Thread中的handleLaunchActivity方法開始分析,首先載入Activity的位元組碼檔案,利用反射的方式建立一個Activity物件,呼叫Activity物件的attach方法,在attach方法中,建立系統需要的Window併為設定回撥,這個回撥定義在Window之中,由Activity實現,當Window的狀態發生變化的時候,就會回撥Activity實現的這些回撥方法。呼叫attach方法之後,Window被建立完成,這時候需要關聯我們的檢視,在handleLaunchActivity中的attach執行之後就要執行handleLaunchActivity中的callActivityOnCreate,在onCreate中我們會呼叫setContentView方法。通過setContentView,建立了Activity的頂級View—DecorView,DecorView的內容欄(mContentParent)用來顯示我們的佈局。這個是我們上面分析得到了一個大致流程,走到這裡,這只是新增的過程,還要有一個顯示的過程,顯示的過程就要呼叫handleLaunchActivity中的handleResumeActivity方法了。最後會呼叫makeVisible方法。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
這裡面首先拿到WindowManager物件,用tWindowManager 的父介面ViewManager接收,ViewManager可以
最後呼叫 mDecor.setVisibility(View.VISIBLE)設定mDecor可見。到此,我們終於明白一個Activity是怎麼顯示在我們的面前了。
參考連結:
http://blog.csdn.net/feiduclear_up/article/details/49201357