您的位置:首页 > 移动开发 > Android开发

Android 理解Window 和 WindowManager

2015-12-26 20:11 501 查看
这几天阅读了《Android开发艺术探索》的关于Window和WindowManager的章节,特此写一片博文来整理和总结一下学到的知识,做一个笔记方便自己查阅。

 说到Window,大家都会想到所有的视图,包括Activity,Dialog,Toast,它们实际上都是附加在Window上的,Window是这些视图的管理者.今天我们就来具体将一下这些视图是如何附加在Window上的,Window有是如何管理这些视图的.

1.Window的属性和类别

 当我们通过WindowManager添加Window时,可以通过WindowManger.LayoutParams来确定Window的属性和类别.其中Flags参数标示Window的属性,我们列出几个比较常见的属性:

FLAG_NOT_FOCUSABLE 这个参数表示Window不需要获取焦点,也不需要接收任何输入事件
FLAG_NOT_TOUCH_MODAL 这个参数表示当前Window区域之外的点击事件传递给底层Window,区域之内的点击事件自己处理,一般默认开启
FLAG_SHOW_WHEN_LOCKED 这个属性可以让Window显示在锁屏界面上

 Window不仅有属性,还有类型.Type参数表示Window的类型,分别为应用Window(activity对应的),子window(dialog对应的),和系统Window(Toast和系统通知栏).Window是分层的,每个window都有z-ordered,层级大的window会覆盖层级小的window,其大小关系为系统window>子window>应用window.所以系统window总会显示在最上边,但是使用系统window是需要声明相应的权限的.这一点需要注意.具体Type参数表示Window的类型如下

应用 Window:层级范围 1-99,优先级最低;一个应用 Window 对应一个 Activity
子 Window:1000-1999,优先级中等;例如对应一个 Dialog 。
系统 Window:2000-2999,一般为 TYPE_SYSTEM__OVERLAY 或 TYPE_SYSTEM_ERROR ,需要声明 SYSTEM_ALERT_WINDOW 权限

2.WindowManager接口

WindowManger实现了ViewManager这个接口,所提供的主要函数只有三个:

public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);


每一个 Window 都对应着一个 View 和一个 ViewRootImpl
Window 和 View 通过 ViewRootImpl 建立联系
无法直接操作 Window,只能通过 WidowManager

2.1 Window的添加过程

示例代码可以在Window中简单添加一个Button
Button btn = new Button(this);
btn.setText("Button");
LayoutParams params = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL |
LayoutParams.FLAG_NOT_FOCUSABLE |
LayoutParams.FLAG_SHOW_WHEN_LOCKED;
params.gravity = Gravity.LEFT | Gravity.TOP;
params.x = 100;
params.y = 300;
windowManager.addView(btn, params);


看看WindowManagerImpl#addView
public void  addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}                                                                                                                                                         private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();<span style="font-family: monospace;font-size:14px; white-space: pre;"></span>


接着看WindowManagerGlobal#addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
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 and we're running on L or above (or in the
// system context), assume we want hardware acceleration.
final Context context = view.getContext();
if (context != null
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}

int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}

// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}

// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
顺便看下这个类里面比较重要的成员变量
// 存储所有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>();
// 存储了那些正在被删除的view对象,调用了removeVIew,但是没有完成的
private final ArraySet<View> mDyingViews = new ArraySet<View>();
WindowManagerGlobal#addView按照书中精简一下代码就是
// 创建ViewRootImpl,然后将下述对象添加到列表中
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);//设置Params
mViews.add(view);//window列表添加
mRoots.add(root);//ViewRootImpl列表添加
mParams.add(wparams);//布局参数列表添加
最后添加要加入的View
// 通过ViewRootImpl的setView来完成
root.setView(view, wparams, panelParentView);
在ViewRootImpl的setView函数中,会调用requestLayout来完成异步刷新,然后在requestLayout中调用scheduleTraversals来进行view绘制.

@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals(); // 实际View绘制的入口
}
}


最后通过WindowSession来完成Window的添加过程,它是一个Binder对象,通过IPC调用来添加window,sheduleTraversals()会调用到下面的方法
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel);


所以,Window的添加请求就交给WindowManagerService去处理,在其内部为每个应用保留一个单独的Session.

2.2 Window的删除过程

WindowManagerGlobal.removeView()

public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}

synchronized (mLock) {
int index = findViewLocked(view, true); //先找到view的index
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);
}
}
removeView先通过findViewLocked来查找待删除的View的索引,然后用removeViewLocked来做进一步删除.
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index); //获得当前的view的viewRootImpl
View view = root.getView();

if (view != null) { //先让imm下降
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate); //die方法只是发送一个请求删除的消息之后就就返回
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);//加入dyingView
}
}
}
WindowManagerGlobal
通过 ViewRootImpl 来完成删除操作,在WindowManager中提供了两种删除接口removeVIew()和removeViewImmediate(),它们分别表示异步和同步删除(不常用).而异步操作中会调用die函数,来发送一个MSG_DIE消息来异步删除,ViewRootImpl的Handler会调用doDie(),而如果是同步删除,那么就直接调用doDie(),然后在removeView函数中把View添加到mDyingViews中.
ViewRootImpl.die()
boolean die (boolean immediate) {
if (immediate && !mIsInTraversal) {
doDia();
return flase;
}

if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "尝试摧毁正常绘制中的 Window");
}
// ViewRootImpl 的 mHandler 将处理此消息并调用 doDie
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}


当 immediate 为 false,使用异步删除,就发送一个 MSG_DIE 的消息,ViewRootImpl 的 mHandler 将处理此消息并调用 doDie 方法;如果是同步删除,就是直接调用 doDie 方法;doDie 方法会调用 dispatchDetachedFromWindow 方法,内部真正实现了 View 的删除逻辑。

dispatchDetachedFromWindow 方法做了四步工作:

垃圾回收,例如清除数据、消息、移除回调

通过 Session 的 remove 方法删除 Window

mWindow.remove(mWindow)
WindowManagerService.removeWindow()


调用 View 的 dispatchDetachedFromWindow 方法,内部调用 View 的 onDetachedFromWindow、onDetachedFromInternal()

调用 WindowManagerGlobal 的 doRemoveView 方法刷新数据,包括 mRoots、mParams、mDyingViews

2.3 Window 的更新过程

WindowManagerGlobal.updateViewLayout

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
.....
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);//这是主要的方法
}
}


更新 View 的 LayoutParams,更新 ViewRootImpl 的 LayoutParams,内部调用 scheduleTraversals 对 view 重绘,通过 WindowSession 更新 Window 的视图,最终调用 WindowMService 的 relayoutWindow() 具体实现。

3.Window的创建

学习完第二章我就有个疑问:WindowManager提供的接口只有Window的增删改,那么Window的创建到底是在哪进行的?不同类型的Window的创建过程不同,这里我只来讲一下Activity的Window的创建过程。在Window的启动过程中,会调用attach()函数来为其关联运行过程中所依赖的一系列上下文环境变量,这里的流程也可以参照setContentView源码浅析中的第三点。
ActivityThread
performLaunchActivity()

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
// 为 Activity 关联运行过程中所依赖的一系列上下文环境变量
activity.attch(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config, r.voiceInteractor);
}

Activity 的 attach 方法内部,系统为 Activity 创建所属 Window 对象并设置回调,注意此时并未与 WindowManager 关联,最终 onResume 时才会完成关联:

mWindow = PolicyManager.makeNewWindow(this);
// 当 Window 接收到外界状态改变时回调 Activity 的接口实现
// onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
PolicyManager
实现了 IPolicy 定义的四个方法:
public interface IPolicy {
public Window makeNewWindow(Context context);
public LayoutInflater makeNewLayoutInflater(Context context);
public WindowManagerPolicy makewNewWindowManager();
public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}
PolicyManager
的实现 Policy,makeNewWindow

public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
Window对象是通过PolicyManager的makeNewWindow方法实现的,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的对应方法.而我们去追寻Window的具体实现类,会发现它就是PhoneWindow,而Activity中最常用的setContentView方法的具体操作都是在PhoneWindow的相应方法中实现的.至于setContentView的具体流程请参考setContentView源码解析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: