文章目录

  • 一、Window和WindowManager
    • 1.1 window
    • 1.2 WindowManager
  • 二、window的内部机制
    • 2.1 window的添加
    • 2.2 window的更新
    • 2.3 window 删除
  • 三、常见Window的创建过程
    • 3.1 Activity的Window创建
    • 3.2 Dialog的window创建
    • 3.3 Toast的window创建

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 2038at 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 类别(层级):系统windowlayoutParams.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);}@Overrideprotected 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的三个方法的实现:

@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}@Overridepublic void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.updateViewLayout(view, params);}@Overridepublic 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个步骤:

  1. 数据检查
  2. 更新各种参数列表
  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 thingstry {// 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.绘制viewrequestLayout();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绘制流程入口。如下所示:

@Overridepublic 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 {@Overridepublic 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() {@Overridepublic 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:

    @Overridepublic 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:

@Overridepublic 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在列表中的indexint 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) {//找到对应的ViewRootViewRootImpl 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) {//记录要删除的viewmDyingViews.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、dyingViewWindowManagerGlobal.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 {//删除windowmWindowSession.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的唯一实现PhoneWindowmWindow = new PhoneWindow(this, window, activityConfigCallback);...//把activity作为回调接口传入window,这样window从外界接受的状态变化都会交给activity//例如:dispatchTouchEvent、onAttachedToWindow、onDetachedFromWindowmWindow.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) {//创建mDecormDecor = 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) {//创建mContentParentmContentParent = 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.addViewwm.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) {...//获取windowManagermWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//实例化PhoneWindowfinal 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.addViewmWindowManager.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或者shortmHandler.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 = 50int 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,是个系统windowparams.type = WindowManager.LayoutParams.TYPE_TOAST;params.setTitle("Toast");//window的flagsparams.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) {@Overridepublic 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*/@Overridepublic void show(IBinder windowToken) {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.obtainMessage(SHOW, windowToken).sendToTarget();}/*** schedule handleHide into the right thread*/@Overridepublic 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 necessaryhandleHide();//mNextView赋值给mViewmView = mNextView;...//1.获取WindowManagermWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);// the layout directionfinal Configuration config = mView.getContext().getResources().getConfiguration();final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());......try {//2.windowManager的addViewmWM.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的removeViewmWM.removeViewImmediate(mView);}...mView = null;}}}

所以,TN才是Toast中真正处理Window创建的地方

好了,Window讲完啦!

参考:
初步理解 Window 体系
Window, WindowManager和WindowManagerService之间的关系

Window和WindowManager--《Android开发艺术探索》阅读笔记——第八章相关推荐

  1. 《Android开发艺术探索》笔记目录

    该笔记以<Android开发艺术探索>为基础,结合Android 9.0代码和官方文档,修正了原书中表述不明确和过时的部分,同时加入了大量的个人理解. 13章,14章,15章是总结性的章节 ...

  2. 《android开发艺术探索》笔记之Bitmap的加载和Cache

    <Android开发艺术探索>笔记之Bitmap的加载和Cache<一> 我放暑假前,就在图书馆借了一本<Android开发艺术探索>,这也是我看到很多人推荐的.之 ...

  3. 《Android 开发艺术探索》笔记2--IPC机制

    <Android 开发艺术探索>笔记2--IPC机制 思维导图 Android IPC简介 Android中的多进程的模式 IPC基础概念 Serializable接口 Parcelabl ...

  4. 记录Android开发艺术探索阅读

    本博客记录从初级android开发工程师阅读android开发艺术探索的学习路程,会对书上的知识点做自己的见解. 持续更新...

  5. Android开发艺术探索 读书笔记

    啥也不说了,@主席的<Android开发艺术探索>真是业界良心之作,不得不看!感谢主席,膜拜主席!主席主席,我要跟你生猴子!(>^ω^<) 读书笔记中若有任何问题请留言告知,谢 ...

  6. Android开发艺术探索读书笔记(一)

    首先向各位严重推荐主席这本书<Android开发艺术探索>. 再感谢主席邀请写这篇读书笔记 + 书评.书已经完整的翻完一遍了,但是还没有细致的品读并run代码,最近有时间正好系统的把整本书 ...

  7. Android开发艺术探索读书笔记

    前言 Android开发艺术(这本书真的是艺术,太崇拜刚哥了,值得每一个做Android开发刷十遍的书) 1,Activity生命周期和启动模式 典型情况下的生命周期分析 onCreate() onS ...

  8. Android 开发艺术探索 - 读书笔记目录

    仅作为读书笔记使用,建议阅读原书. 书中代码部分已和现版本不符,建议对比最新版本学习. 读了这本书,越发认识到和大佬们的差距.嗯,加油吧. 过去の自分が今仆の土台となる 第 1 章 - Activit ...

  9. Android开发艺术探索学习笔记 第二章IPC

    最近将之前工作做本地的学习笔记上传一下 这里是Android艺术开发探索的前三章内容 文章目录 1. android的多进程模式 2. IPC基础概念介绍 2.1 Serializable 2.2Pa ...

  10. Android开发艺术探索读书笔记(二)

    首先感谢大家支持,昨天第一篇写出来之后反响很好,主席本人也非常赞赏(捂脸-),再接再厉,推出第二篇.这篇的主要内容是对两章View的内容进行总结.不得不说,自定义View是很多开发者的痛点,一方面我们 ...

最新文章

  1. android Unable to add window -- token null is n...
  2. python利用opencv自带的颜色查找表(LUT)进行色彩风格变换
  3. LinkedIn:用数据提高视频性能
  4. c++和java哪个难_2020 年 11 月编程语言排行榜,Python 超越 Java ?
  5. 用python来获取Github IP地址
  6. 大型程序是如何开发的_大型小程序如何研发提效
  7. cv图像翻转_涨点技巧!汇集13个Kaggle图像分类项目的性能提升指南
  8. 如何用Pygame写游戏(十六)
  9. java nullexception_Java 中 NullPointerException 的完美解决方案
  10. 获取httpservletrequest所有参数的名称和值
  11. python爬取高德poi数据_高德地图之python爬取POI数据及其边界经纬度
  12. 转:MediaCoder H.264格式编码参数设置及详解
  13. 常见的IC封装形式大全(超详细)
  14. 极坐标格式下的二维傅里叶变换与逆变换推导
  15. 【零基础学JS -2】 适合编写JS的编辑器
  16. 给第一次参加数学建模竞赛的小白的建议
  17. 含泪整理最优质马壁纸素材,你想要的这里都有
  18. 泊松分布分布与Python图解
  19. android键盘坏了怎么办,手机虚拟键盘失灵怎么办
  20. 【python】使用pyautogui进行屏幕捕捉实现自动化操作

热门文章

  1. python元组是什么意思_python元组是什么意思
  2. Olly's Shadow
  3. 经济学人The Economist学习(笔记词汇)Day1
  4. 64位系统装32位计算机,64位电脑装32位系统,教您64位电脑怎么装32位系统
  5. CSS强制图像调整大小并保持纵横比
  6. 开发者如何了解技术前沿? 再也不用看微信公众号的软文了!
  7. 经验分享:SecureCRT远程登录树莓派开发板
  8. artdialog v6强大的模态对话框v6版api
  9. distinct和order by冲突
  10. 阿里的花名,是要抹去员工独立人格?