文章目錄
Window,表示一個窗口的抽象的概念;同時也是一個抽象類,唯一的實現是PhoneWindow。在PhoneWindow中有一個頂級View—DecorView,繼承自FrameLayout,我們可以通過getDecorView()獲得它,當我們調用Activity的setContentView時,其實最終會調用Window的setContentView,當我們調用Activity的findViewById時,其實最終調用的是Window的findViewById,這也間接的說明了Window是View的直接管理者。
但是Window並不是真實存在的,它更多的表示一種抽象的功能集合,View纔是Android中的視圖呈現形式,繪製到屏幕上的是View不是Window,但是View不能單獨存在,它必需依附在Window這個抽象的概念上面,Android中需要依賴Window提供視圖的有Activity,Dialog,Toast,PopupWindow,StatusBarWindow(系統狀態欄),輸入法窗口等,因此Activity,Dialog等視圖都對應着一個Window。
創建Window,通過WindowManager即可完成。WindowManager是操作Window的入口,Window的具體實現是在WindowManagerService中。WindowManager和WindowManagerService交互是IPC(跨進程通信)過程。
Window是View的管理者,當我們說創建Window時,一方面指實例化這個管理者,一方面指 用WindowManager.addView()添加view,以view的形式來呈現Window這個概念。
一、Window和WindowManager
1.1 window
先看創建window的代碼
WindowManager windowManager = getWindowManager();
Button view = new Button(this);
view.setText("添加到window中的button");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
layoutParams.format = PixelFormat.TRANSPARENT;
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
layoutParams.x = 100;
layoutParams.y = 100;
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
windowManager.addView(view, layoutParams);
實際就只有一句windowManager.addView(view, layoutParams),這樣就添加了一個Window,這個window只有一個button。看下LayoutParams的兩個不太認識的屬性,flags、type。
flags,決定window的顯示特性,有很多值,看下常用的:
FLAG_NOT_FOCUSABLE,不需要獲取焦點、不需要 輸入事件,同時會自定開啓FLAG_NOT_TOUCH_MODAL,最終事件會傳遞給下層具有焦點的window。
FLAG_NOT_TOUCH_MODAL,window區域以外的單擊事件會傳遞給下層window,window範圍內的事件自己處理。一般需要開啓此標記,否則其他window不能收到事件。
FLAG_SHOW_WHEN_LOCKED,開啓後 可以讓window顯示在鎖屏的界面上。
type參數表示window的類型。window有三種類型,應用window、子window、系統window。應用window對應activity;子window要依附在父window上,如dialog;系統window需要申明權限才能創建,比如toast、系統狀態欄。
window是分層的,每個window都有對應的z-ordered,層級大的在層級小的上層。應用window的層級範圍是1-99,子window是1000-19999=,系統window是2000-2999,即type的值。
如果想window位於所有window頂層,那就用系統window。可以設置layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,並且,要申明使用權限,且6.0以後要讓用戶手動打開權限。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
否則會報錯:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@305c3bc -- permission denied for window type 2038
at android.view.ViewRootImpl.setView(ViewRootImpl.java:958)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:398)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:131)
at com.hfy.demo01.MainActivity.initCustomWindow(MainActivity.java:266)
at com.hfy.demo01.MainActivity.initView(MainActivity.java:170)
at com.hfy.demo01.MainActivity.onCreate(MainActivity.java:116)
at android.app.Activity.performCreate(Activity.java:7458)
at android.app.Activity.performCreate(Activity.java:7448)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1286)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3409)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3614)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:86)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2199)
at android.os.Handler.dispatchMessage(Handler.java:112)
at android.os.Looper.loop(Looper.java:216)
at android.app.ActivityThread.main(ActivityThread.java:7625)
使用系統window的完整代碼:
private void initCustomWindow() {
//6.0以上需要用戶手動打開權限
// (SYSTEM_ALERT_WINDOW and WRITE_SETTINGS, 這兩個權限比較特殊,
// 不能通過代碼申請方式獲取,必須得用戶打開軟件設置頁手動打開,才能授權。Manifest申請該權限是無效的。)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
if (!Settings.canDrawOverlays(this)) {
//打開設置頁,讓用戶打開設置
Toast.makeText(this, "can not DrawOverlays", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + MainActivity.this.getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
}else {
//已經打開了權限
handleAddWindow();
}
}else {
//6.0以下直接 Manifest申請該權限 就行。
handleAddWindow();
}
}
private void handleAddWindow() {
Button view = new Button(this);
view.setText("添加到window中的button");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0,
PixelFormat.TRANSPARENT
);
// flag 設置 Window 屬性
layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
// type 設置 Window 類別(層級):系統window
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
layoutParams.x = 100;
layoutParams.y = 100;
WindowManager windowManager = getWindowManager();
windowManager.addView(view, layoutParams);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case OVERLAY_PERMISSION_REQ_CODE:
if (Settings.canDrawOverlays(this)) {
//打開了權限
handleAddWindow();
}else {
Toast.makeText(this, "can not DrawOverlays", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
按home鍵後效果:
1.2 WindowManager
WindowManager是個接口,繼承自ViewManager:
public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
所以,windowManager就是 添加、更新、刪除 view,實際使用的就是這三個方法,上面創建window的例子用的就是addView方法。所以,操作window就是操作view。
二、window的內部機制
window是抽象的概念,在視圖中不是實際存在,它以view的形式呈現。一個window就對應一個view,window操作view實際是通過ViewRootImpl實現。使用中是通過WindowManager對的操作,無法直接訪問window。下面就看看WindowManager的三個方法。
2.1 window的添加
WindowManager的實現類是WindowManagerImpl,那麼看看操作view的三個方法的實現:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
可以看到,全都交給mGlobal處理了,那看下mGlobal,是個單例對象:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
那麼來看下mGlobal.addView,具體簡要概括爲3個步驟:
- 數據檢查
- 更新各種參數列表
- RootViewImpl添加view(含window的添加)
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//1、數據檢查
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
...
//創建viewRoot(一個window對應一個viewRoot)
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//2、更新各種參數列:所有window的--view的列表、rootView的列表、view參數的列表
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 3、RootViewImpl添加view(含window的添加)
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
接着看ViewRootImpl的setView:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
//1.繪製view
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//2.通過session與WMS建立通信:完成window的添加
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
}
...
}
兩個步驟:1、調用requestLayout()異步刷新view,2、mWindowSession.addToDisplay()完成window的添加。
requestLayout()內部最後走到performTraversals(),我們知道這是view繪製流程入口。如下所示:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 繪製流程
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
至於mWindowSession.addToDisplay(),先看mWindowSession,類型是IWindowSession,是個Binder對象,具體是com.android.server.wm.Session,所以window的添加是一個IPC過程。
mWindowSessionde 是在ViewRootImpl創建時獲取,由WindowManagerGlobal通過獲取WindowManagerService來爲 每個應用創建一個單獨的session。
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
...
}
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
if (sWindowManagerService != null) {
ValueAnimator.setDurationScale(
sWindowManagerService.getCurrentAnimatorScale());
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowManagerService;
}
}
然後是WindowManagerService的openSession:
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
Session session = new Session(this, callback, client, inputContext);
return session;
}
接着看Session的addToDisplay:
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
}
window的添加就交給WindowManagerService了。
WindowManagerService主要作用:
窗口管理: 是先進行窗口的權限檢查,因爲系統窗口需要聲明權限,然後根據相關的Display信息以及窗口信息對窗口進行校對,再然後獲取對應的WindowToken,再根據不同的窗口類型檢查窗口的有效性,如果上面一系列步驟都通過了,就會爲該窗口創建一個WindowState對象,以維護窗口的狀態和根據適當的時機調整窗口狀態,最後就會通過WindowState的attach方法與SurfaceFlinger通信。因此SurfaceFlinger能使用這些Window信息來合成surfaces,並渲染輸出到顯示設備。
輸入事件的中轉站:當我們的觸摸屏幕時就會產生輸入事件,在Android中負責管理事件的輸入是InputManagerService,它裏面有一個InputManager,在啓動IMS的同時會創建InputManager,在創建InputManager同時創建InputReader和InputDispatcher,InputReader會不斷的從設備節點中讀取輸入事件,InputReader將這些原始輸入事件加工後就交給InputDispatcher,而InputDispatcher它會尋找一個最合適的窗口來處理輸入事件,WMS是窗口的管理者,WMS會把所有窗口的信息更新到InputDispatcher中,這樣InputDispatcher就可以將輸入事件派發給合適的Window,Window就會把這個輸入事件傳給頂級View,然後就會涉及我們熟悉的事件分發機制。
2.2 window的更新
直接看mGlobal.updateViewLayout(view, params):
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//1、參數檢查
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
//2、更新layoutParams及參數列表列表
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
//3、RootViewImpl更新佈局
root.setLayoutParams(wparams, false);
}
}
再看ViewRootIml.setLayoutParams()中會調用scheduleTraversals() 重新繪製佈局,其中也會調用mWindowSession.relayout來更新window ,也是IPC過程。
2.3 window 刪除
直接看mGlobal.removeView(view, false):
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
//找到要移除view在列表中的index
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
//移除
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
再看removeViewLocked(index, immediate):
private void removeViewLocked(int index, boolean immediate) {
//找到對應的ViewRoot
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
//ViewRoot用die來刪除
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
//記錄要刪除的view
mDyingViews.add(view);
}
}
}
繼續看root.die(immediate):
boolean die(boolean immediate) {
// 如果是立刻刪除,直接調doDie()
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
//不是立刻刪,就放入隊列
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
繼續看doeDie():
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved)
return;
}
mRemoved = true;
if (mAdded) {
//刪除操作
dispatchDetachedFromWindow();
}
...
//移除對應列表中的root、view、param、dyingView
WindowManagerGlobal.getInstance().doRemoveView(this);
}
看下dispatchDetachedFromWindow():
void dispatchDetachedFromWindow() {
mFirstInputStage.onDetachedFromWindow();
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
//回調view的dispatchDetachedFromWindow方法,意思是view要從window中移除了。一般可在其中做一些資源回收工作,如 停止動畫等。
mView.dispatchDetachedFromWindow();
}
//移除各種回調
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
setAccessibilityFocus(null, null);
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mSurface.release();
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
try {
//刪除window
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
// Dispose the input channel after removing the window so the Window Manager
// doesn't interpret the input channel being closed as an abnormal termination.
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
mDisplayManager.unregisterDisplayListener(mDisplayListener);
unscheduleTraversals();
}
好了,window的三個view操作就這些了。
三、常見Window的創建過程
View依附於Window這個抽象概念,有Activity、Dialog、Toast、PopupWindow等。
3.1 Activity的Window創建
Activity的啓動略複雜,這裏先看ActivityThread裏的performLaunchActivity():
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
//創建activity實例:通過類加載器創建
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
//調用Activity的attach方法--關聯上下文環境變量
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, window, r.configCallback);
...
}
接着看activity.attach方法:
//實例化window,就是Window的唯一實現PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
//把activity作爲回調接口傳入window,這樣window從外界接受的狀態變化都會交給activity
//例如:dispatchTouchEvent、onAttachedToWindow、onDetachedFromWindow
mWindow.setCallback(this);
...
//設置windowManager,實際就是WindowManagerImpl的實例,在activity中getWindowManager()獲取的就是這個實例
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
mWindowManager = mWindow.getWindowManager();
OK,activity視圖的管理者window已創建,那麼什麼時候用windowManager.addView() 來把activity的視圖依附在window上呢?
先看Activity的setContentView方法,我們activity的視圖由此方法設置:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
接着看PhonrWindow的setContentView:
public void setContentView(int layoutResID) {
// mContentParent爲空,就調installDecor(),猜想installDecor()裏面創建了mContentParent。且從名字看出mContentParent就是內容視圖的容器
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 {
//這裏看到,確實把我們的視圖加載到mContentParent了
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
那就看installDecor():
private void installDecor() {
if (mDecor == null) {
//創建mDecor
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//創建mContentParent
mContentParent = generateLayout(mDecor);
...
}
看下generateDecor(-1),就是new了個DecorView:
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
繼續看generateLayout(mDecor):
// Apply data from current theme.
TypedArray a = getWindowStyle();
...
// 這裏下面一堆代碼是 根據主題,獲取DecorView的佈局資源
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0)
...
//把佈局給到mDecor,這樣mDecor就有視圖了。
mDecor.onResourceLoaded(mLayoutInflater, layoutResource)
//findViewById就是getDecorView().findViewById(id);
//所以從DecorView中找到id爲ID_ANDROID_CONTENT = com.android.internal.R.id.content 的容器,就用用來存放我們activity中設置的視圖的。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
好了,通過以上流程,就清楚了activity中通過setContentView設置的佈局實際是加載到DecorView的id爲com.android.internal.R.id.content容器中。我們查看DecorView所有的主題的佈局,發現都有這個id的容器,且是FrameLayout。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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/title_container"
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
android:transitionName="android:title"
style="?android:attr/windowTitleBackgroundStyle">
</FrameLayout>
//這個容器
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
最後一步,就是windowManager.addView了,在哪呢?
在ActivityThred的handleResumeActivity()中:
r.activity.makeVisible();
再看activity.makeVisible():
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
//1、windowManager.addView
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
//2、Decor可見
mDecor.setVisibility(View.VISIBLE);
}
好了,activity的window加載過程就這樣了。
3.2 Dialog的window創建
先看Dialog的構造方法:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
...
//獲取windowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//實例化PhoneWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
//設置回調
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
...
}
接着看setContentView,和activity類似,把內容視圖放入DecorView:
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
再看下show方法:
public void show() {
...
mDecor = mWindow.getDecorView();
...
WindowManager.LayoutParams l = mWindow.getAttributes();
boolean restoreSoftInputMode = false;
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
l.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
restoreSoftInputMode = true;
}
//使用WindowManager.addView
mWindowManager.addView(mDecor, l);
...
}
注意,一般創建dialog時 傳入的context必須是Activity。如果要傳Application,那麼要dialog.getWindow().setType(),設置系統window的type。
3.3 Toast的window創建
使用Toast方式:
Toast.makeText(this, "hehe", Toast.LENGTH_SHORT).show();
看makeText(),就是new一個Toast,設置mNextView爲TextView、mDuration:
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
return makeText(context, null, text, duration);
}
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
//實例化
Toast result = new Toast(context, looper);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
//設置視圖、時間
result.mNextView = v;
result.mDuration = duration;
return result;
}
Toast構造方法:
public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
//有個TN,是個Binder對象
mTN = new TN(context.getPackageName(), looper);
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
實際也可以用setView()自定義視圖:
public void setView(View view) {
mNextView = view;
}
再看show():
public void show() {
//沒有視圖不行
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
//IPC過程:NotificationManagerServcice.enqueueToast(),爲啥要IPC過程呢?(注意這裏的tn就是Toast構造方法裏的new的TN)
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
看下NotificationManagerServcice.enqueueToast():
//創建ToastRecord,callback就是傳進來的TN
record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
...
if (index == 0) {
//這裏看起來是show方法
showNextToastLocked();
}
看不showNextToastLocked():
void showNextToastLocked() {
//取出第一個record,這裏爲啥第0個?
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
//這裏跑到TN的show方法了,顯然是系統服務NotificationManagerServcice向我們的APP發起IPC過程,完成最終的show。這個保留疑問後面再看~
record.callback.show(record.token);
//這個就是 定時 調TN的hide方法,時間就是我們的toast的設置的show時間?爲啥這麼說,往下看~
scheduleDurationReachedLocked(record);
return;
}
...
}
}
看下scheduleDurationReachedLocked(record):
private void scheduleDurationReachedLocked(ToastRecord r)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
//handler發送定時任務MESSAGE_DURATION_REACHED,看名字就是隱藏toast,時間就是我們的long或者short
mHandler.sendMessageDelayed(m, delay);
}
這個mHandler就是NMS中的handler,找到上面任務的處理方法:
private void handleDurationReached(ToastRecord record)
{
synchronized (mToastQueue) {
int index = indexOfToastLocked(record.pkg, record.callback);
if (index >= 0) {
cancelToastLocked(index);
}
}
}
接着看:
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
//果然,是TN的hide方法,哈哈
record.callback.hide();
} catch (RemoteException e)
...
ToastRecord lastToast = mToastQueue.remove(index);
if (mToastQueue.size() > 0) {
// 開始下一個~~~
showNextToastLocked();
}
}
總結下NotificationManagerServcice.enqueueToast()這個IPC的作用:使用NMS中的mHandler 處理隊列中的ToastRecord,具體就是通過IPC調用Toast中的TN的show(),然後在定時調用TN的hide()。就是說,系統來保證toast的循序排隊,及展示時間。
另外還一點,對非系統應用,隊列中最多同時又50個ToastRecord:
// limit the number of outstanding notificationrecords an app can have
//MAX_PACKAGE_NOTIFICATIONS = 50
int count = getNotificationCountLocked(pkg, userId, id, tag);
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
mUsageStats.registerOverCountQuota(pkg);
Slog.e(TAG, "Package has already posted or enqueued " + count
+ " notifications. Not showing more. package=" + pkg);
return false;
}
好了,系統進程看完了。接着看實例化Toast時的創建的TN,我們在上面分析,猜測 這裏纔是我們想要的WIndow的創建過程,那麼往下看吧:
private static class TN extends ITransientNotification.Stub {
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
private static final int SHOW = 0;
private static final int HIDE = 1;
private static final int CANCEL = 2;
final Handler mHandler;
...
static final long SHORT_DURATION_TIMEOUT = 4000;
static final long LONG_DURATION_TIMEOUT = 7000;
TN(String packageName, @Nullable Looper looper) {
final WindowManager.LayoutParams params = mParams;
...
//window的type:TYPE_TOAST = FIRST_SYSTEM_WINDOW+5,是個系統window
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
//window的flags
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mPackageName = packageName;
//這裏可知,必須在有looper的線程才能new Toast,爲啥呢?因爲前面分析NMS中調用TN的show、Hide,因爲是IPC過程,實際在App這邊執行是在Bind線程池中進行的,所以需要切換到當前發Toast的線程
if (looper == null) {
// Use Looper.myLooper() if looper is not specified.
looper = Looper.myLooper();
if (looper == null) {
throw new RuntimeException(
"Can't toast on a thread that has not called Looper.prepare()");
}
}
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
case HIDE: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
break;
}
case CANCEL: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
try {
getService().cancelToast(mPackageName, TN.this);
} catch (RemoteException e) {
}
break;
}
}
}
};
}
/**
* schedule handleShow into the right thread
*/
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.obtainMessage(HIDE).sendToTarget();
}
public void cancel() {
if (localLOGV) Log.v(TAG, "CANCEL: " + this);
mHandler.obtainMessage(CANCEL).sendToTarget();
}
public void handleShow(IBinder windowToken) {
...
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
//mNextView賦值給mView
mView = mNextView;
...
//1.獲取WindowManager
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// the layout direction
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
...
...
try {
//2.windowManager的addView
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
}
}
public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
if (mView != null) {
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
//windowManager的removeView
mWM.removeViewImmediate(mView);
}
...
mView = null;
}
}
}
所以,TN纔是Toast中真正處理Window創建的地方。
好了,Window講完啦!
參考:
初步理解 Window 體系
Window, WindowManager和WindowManagerService之間的關係