引言
Window
是类似悬浮窗的东西WM
参与Window
的Create
和 管理,WMS
和WM
共同完成Window
的IPC
交互Window
是View
直接 管理者Activity
的setContenView
底层是由PhoneWindow
的installDecor
绘制的.
1. 如何使用 WM 添加一个 Window?
// 将一个Button添加到屏幕位置(100,300)的位置 Button button = new Button(this); button.setText("CrazyDailyQuestion"); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSLUCENT); // 通过Flags控制Window的显示特性 layoutParams.flags = // 此模式下,系统会将当前Window区域外的单击事件,传递给底层Window,当前Window区域外的点击事件传递给底层Window,当前Window区域以内的事件则自己处理,如果不开启该事件可能出现其他Window无法捕捉到单击事件 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | // Window 不需要获取任何焦点,也不需要接收各种输入事件,此标记会同时开启 FLAG_NOT_TOUCH_MODAL WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN | // Window可以显示在锁屏的界面上 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; layoutParams.gravity = Gravity.LEFT | Gravity.TOP; layoutParams.x = 100; layoutParams.y = 300; // 获取Window的对象 WindowManager mManager = (WindowManager) getApplicationContext() .getSystemService(Context.WINDOW_SERVICE); // 通过WM将View绘制到指定位置 mManager.addView(button, layoutParams);
2. layoutParams.type
layoutParams.type | examp | z-orderered | 注意事项 |
Application | Activity | (0,100) | / |
Child | Dialog | (999,2000) | 不能单独存在,需要附属在特定的父 Window |
System | Toast,StatusBar ,软键盘 | (1999,3000) | 需要声明权限 |
3. Method
public interface ViewManager{ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
addView
概念: 创建 Window
添加View
1.检查参数是否合法,如果子Window,那么还需要调整一些布局参数
addJustLayoutParamsForSubWindow(wparams)
2.创建 ViewRootImpl 并将 View 添加到列表中
// 存储所有Window对应的View private final ArrayList<View> mViews = new ArrayList<View>(); // 存储所有Window对应的ViewRootImpl private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); // 存储所有Window所对应的布局参数 private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); // 存储了正在删除还未完成的 Window 对象 private final ArraySet<View> mDyingViews = new ArraySet<View>(); // 将 Window 对象添加到列表里面 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams);
3.通过 ViewRootImpl 来更新界面并完成 Window 的添加过程
Window
的绘制 由 ViewRootImpl
的 setView
来完成,setView
方法会通过 requestLayout
来完成异步请求
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; // View的绘制入口 scheduleTraversals(); } }
WindowSession
最终完成 Window
的添加过程,真正实现类是 Session
,也是Window
添加过程的一次 IPC
调用.然后底层是Seesion
通过WMS
实现Window
添加.
updateViewLayout
概念: 更新 Window
中的View
removeView
概念: 删除一个 Window
4. Window 事件操作
// 将 View 设置 OnTouchListener mButton.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // 只需根据手指设置 mLayoutParams 的x,y值更改Window的位置 int rawX = (int) event.getRawX(); int rawY = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: mLayoutParams.x = rawX; mLayoutParams.y = rawY; // onTouch 方法不断更新 View的位置 manager.updateViewLayout(mButton, mLayoutParams); break; default: break; } return false; } });
5. Activity && Window
View
是Android
中视图的程序方式,View
不能单独存在,必须依附 Window 这个抽象概念上,因此有视图的地方就有Window
,下面我就带大家来看一下,Activty
的Windwow
创建过程.
Activity
启动过程很复杂,最终是交给ActivityThread
的 performLauchActivity()
来完成整个启动过程,在整个方法内部会通过类加载器创建 Activity
的实例对象,并通过 attach
方法关联一切所需要的上下文环境.
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { java.lang.ClassLoader cl = appContext.getClassLoader(); activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); // -------为了避免浪费篇幅,省略无关代码-------- if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (r.overrideConfig != null) { config.updateFrom(r.overrideConfig); } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); Window window = null; if (r.mPendingRemoveWindow != null && r.mPreserveWindow) { window = r.mPendingRemoveWindow; r.mPendingRemoveWindow = null; r.mPendingRemoveWindowManager = null; } appContext.setOuterContext(activity); 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); // -------为了避免浪费篇幅,省略无关代码-------- }
final void attach(IBinder windowToken) { // window 对象通过 PolicyManager.makeNewWindow 创建 mWindow = PolicyManager.makeNewWindow(this); // window 的 Callback接口里面有我们熟悉的dispaTouch方法以及onAttachToWindow方法 mWindow.setCallback(this); mWindow.requestFeature(Window.FEATURE_NO_TITLE); mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000)); WindowManager.LayoutParams lp = mWindow.getAttributes(); lp.type = WindowManager.LayoutParams.TYPE_DREAM; lp.token = windowToken; lp.windowAnimations = com.android.internal.R.style.Animation_Dream; }
PolicyManager
的具体实现接口方法都在策阅接口IPolicy
里面完成.其中makeNewWindow
实际上是创建了一个Window
对象.
public interface IPolicy { public Window makeNewWindow(Context context); public LayoutInflater makeNewLayoutInflater(Context context); public WindowManagerPolicy makeNewWindowManager(); public FallbackEventHandler makeNewFallbackEventHandler(Context context); }
然后我们去找 makeNewWindow
的接口实现,我们发现,Window
具体实现实际上是 PhoneWindow
.
public PhoneWindow makeNewWindow(Context context) { return new PhoneWindow(context); }
整个过程 Window
就已经创建完毕了,接下来我们只要看Activity是怎样将具体实现交给Window
处理,而Window
的具体实现是PhoneWindow
,所以我们来看setContentView
的具体实现.
借助孙群
的源码结构图,整个setContentView
源码结构如上,接下来我们来看一下PhoneWindow
的setContentView
源码
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { // installDecor()方法会调用generateDecor()和generateLayout()方法 installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { // 最后触发内容变化的回调 cb.onContentChanged(); } mContentParentExplicitlySet = true; }
1. DecorView
创建
这里我们着重需要了解的是 generateDecor()
方法实现
protected DecorView generateDecor(int featureId) { Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, getContext().getResources()); if (mTheme != -1) { context.setTheme(mTheme); } } } else { context = getContext(); } return new DecorView(context, featureId, this, getAttributes()); }
DecorView
其实就是一个FrameLayout
,它的创建过程由 installDecor
完成,然后installDecor
内部方法通过 generateDecor
来创建 DecorView
,这个时候 DecorView
还是一个空白的 FrameLayout
.
mDecor.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
其中具体的布局文件和主题都在generateDecor
完成,然后我们需要注意的是ID_ANDROID_CONTENT
多对应的id其实就是ViewGroup
对应的MContentParent
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
2. View 添加到 DecorView
的 mContenParnent
中
mLayoutInflate.inflate(layoutResID,mContenParnent);
3. 回调 Activity 的 onContentchanged 方法 通知 Activity 视图已经发生改变.
if (cb != null && !isDestroyed()) { // 最后触发内容变化的回调 cb.onContentChanged(); } mContentParentExplicitlySet = true; }
DecorView 绘制完成,绘制主要是在onResume完成的,具体可以查看ActivityThread -> handleResumeActivity -> makeVisible -> addView
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }
6. Dialog && Window
Dialog 的创建和 Activty 类似,需要注意两个事情.第一点, Dialog 使用的是 Activity 的 token,而不是 Application 的token,所以上下文注意不要用错了,第二点,因为Dilaog是子Window,所以需要申请系统Window权限.
7. Toast && Window
原理:
- IPC 机制
- Toast 访问 NMS
- NMS 回调 Toast TN 接口
- Handler 定时系统
8. View && Window
任何一个 View 都是依附在Window上的,Window 是 View
直接 管理者
9. 注意事项
- 华为手机需要手动赋权,才能开启Toast.建议自定义Toast
- 频繁请求Toast,系统有权拒绝NMS,防止网络攻击