正确地理解Window窗口概念

    xiaoxiao2021-03-25  133


    一、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中保存了已经加载的布局。

    转载请注明原文地址: https://ju.6miu.com/read-38038.html

    最新回复(0)