正确地理解Window窗口概念


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

阅读更多
换一批

没有更多推荐了,返回首页