引言
-
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,防止网络攻击