一、Window窗口:通过图像的形式响应用于操作的界面
Android中Window窗口的概念来自Windows系统对窗口的定义:图形化模式计算机用户操作界面。随便想象一下我们在PC端使用Windows时的场景,就可以想见用户和计算机的交互过程大致如是:
人机交互,对操作系统而言意味着:处理输入然后将结果输出;对用户而言意味着:我输入了操作,计算机需要响应我的意图,最终将结果呈现给我。Android提供的输入设备有按键、触摸屏、摄像头等。Android提供的输出设备则有屏幕、震动器、音频播放器等。
正确的Window窗口概念应该是:通过图像的形式响应用于操作的界面。
因此,理解Window窗口就需要解答两个问题:
1、Window窗口获取用户输入的流程是怎样的?
2、Window窗口绘制图像的流程是怎样的?
首先给出实现一个Window窗口的关键代码:
//获取的是WindowManager系统服务
mWindowManager = (WindowManager)getApplication()
.getSystemService(getApplication().WINDOW_SERVICE);
wmParams = new WindowManager.LayoutParams();
//设置window type,这个值决定了悬浮窗在在各类Window窗口中的位置
wmParams.type = LayoutParams.TYPE_SYSTEM_DIALOG;
//设置浮动窗口不可聚焦(浮动窗口不响应任何操作)
wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
//调整悬浮窗显示的位置
wmParams.gravity = Gravity.LEFT | Gravity.TOP;
LayoutInflater inflater = LayoutInflater.from(getApplication());
//获取浮动窗口视图所在布局
mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);
//将mFloatLayout添加到系统中展示
mWindowManager.addView(mFloatLayout, wmParams);
我们开始学习Android的时候说:Activity是用户与手机的交互界面。更进一步,Activity是通过WindowManager系统服务管理Window窗口来实现人机交互界面的。接下来我们就到Activity的源码中去寻找上面给出的关键代码,并回答那两个问题。
二、Window窗口获取用户输入的流程
我们以触摸屏事件为线索来追溯Window窗口获取用户的流程。Activity实现了Window.Callback接口,在自身被创建的时候会将自身的引用注册到一个Window对象实例中。这个Window对象实例最终会通过IPC机制传递给WindowManagerService。因此WindowManagerService可以通过这个接口来回调触摸世界的处理方法。
触摸事件的终点是onTouchEvent(),而onTouchEvent()是在dispatchTouchEvent(MotionEvent ev)中被调用:
我们知道,Activity实例的创建者是ActivityThread.java。具体的Activity启动过程这里不展开。总之,最终在ActivityThread.performLaunchActivity()中创建Activity的实例:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
// 创建Activity的实例
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
……
} catch (Exception e) {
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
Configuration config = new Configuration(mCompatConfiguration);
// 关联运行过程中所依赖的一系列上下文
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);
……
}
r.paused = true;
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
} catch (Exception e) {
}
return activity;
}
在activity.attach()方法中,系统会创建Activity所属的Window窗口并为其设置回调接口:
final void attach(……) {
// 创建Window类的实例
mWindow = PolicyManager.makeNewWindow(this);
// 注册回调接口
mWindow.setCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
mWindow.setWindowManager(……);
mWindowManager = mWindow.getWindowManager();
}
Window类的setCallback()被调用之后就会保存这个接口的实例:
private Callback mCallback;
public void setCallback(Callback callback) {
mCallback = callback;
}
当Activity所属的Window窗口被添加到系统中时,这个回调接口会被作为一个IPC接口注册到WindowManagerService中。回看我们的关键代码:
//将mFloatLayout添加到系统中展示
mWindowManager.addView(mFloatLayout, wmParams);
Activity中同样的代码位于ActivityThread.handleResumeActivity()中:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
……
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
// 获取加载了Activity布局的View对象
View decor = r.window.getDecorView();
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
// 将Activity的Window窗口添加到系统中
wm.addView(decor, l);
}
}
WindowManager的继承关系图:
从上图中我们看到,addView()的最终实是WindowManagerGlobal.addView(),而addView()的功能最终又转移到ViewRootImpl.setView()中实现:
WindowManagerGlobal:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
}
ViewRootImpl:
mWindowSession = WindowManagerGlobal.getWindowSession();
public void setView(……) {
// 在这个方法中完成绘制工作
requestLayout();
// mWindowSession是一个Binder对象
// 通过远程调用请求WindowManagerService添加一个Window窗口对象
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
}
WindowManagerGlobal:
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
// 获取IWindowSession实例对象做为回调WindowManagerService的接口
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
在ViewRootImpl.setView()方法中我们看到,最终会使用mWindowSession对象通过Binder机制进行IPC调用,最终在WindowManagerService中添加一个Window窗口。同时需要注意的是其中一个参数mWindow,这个mWindow实际上指向了实现了Window.Callback接口的Activity实例对象。WindowManagerService就是通过这个接口来回调Activity.dispatchTouchEvent()的。
三、Window窗口的绘制流程
我们知道,在Activity中,加载布局的方法是Activity.setContentView(),这个方法首先会在PhoneWindow.setContentView()方法中加载布局,然后等待该Window实例对象被添加到系统中。当调用添加到系统中时会在ViewRootImpl.requestLayout()中调用mWindowSession对象通过IPC机制请求WindowManagerService绘制界面。
mWindow = PolicyManager.makeNewWindow(this);
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
public Window getWindow() {
return mWindow;
}
Window只有唯一的实现类PhoneWindow:
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 创建顶层布局DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
……
} else {
// 加载布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
PhoneWindow.setContentView()会首先创建一个DecorView。这个DecorView作为顶级的View,实际上是一个FrameLayout,所有的事件都会先经过这个DecorView才传到我们自定义的View中。接着会将布局加载到mLayoutInflater实例对象中。
在我们研究Window窗口获取事件的代码时曾追溯了addView()的流程并提到:
ViewRootImpl:
public void setView(……) {
// 在这个方法中完成绘制工作
requestLayout();
}
ViewRootImpl.requestLayout()最终会调用到ViewRootImpl.performTraversals()中:
private void performTraversals() {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
)
最终是通过mWindowSession对象调用到WindowManagerService的方法请求绘制。注意mWindow.mLayoutInflater中保存了已经加载的布局。