Android 设计模式 笔记 - 深入了解WindowManager

Posted 鲨鱼丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 设计模式 笔记 - 深入了解WindowManager相关的知识,希望对你有一定的参考价值。

了解一:

所有的可以显示到屏幕上的内容都是通过windowManager来操作的。包括Activity等。

了解二:

WindowManager是一个非常重要的子系统。简称WMS

了解三:

和WindowManager联系上的第一步就是通过Context中的getSystemService()方法。

我们已经了解到各种系统的服务都会注册到ContextImpl的一个map容器里,然后通过该服务的字符串键进行获取,而WindowManager就是ContextImpl中注册的众多服务之一。

了解四:

WindowManager在java代码中的具体实现是WindowManagerImpl。


  • 实践 -- Dialog中的WindowManager

追踪Dialog代码的构造方法:

    Dialog(Context context, int theme, boolean createContextThemeWrapper) 
        if (createContextThemeWrapper) 
            if (theme == 0) 
                TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
                        outValue, true);
                theme = outValue.resourceId;
            
            mContext = new ContextThemeWrapper(context, theme);
         else 
            mContext = context;
        

        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        Window w = PolicyManager.makeNewWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    

可以看到Dialog最终是通过setWindowManager方法把window和WindowManager建立了关系。

追踪setWindowManager方法:

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName) 
        setWindowManager(wm, appToken, appName, false);
    

    /**
     * Set the window manager for use by this Window to, for example,
     * display panels.  This is <em>not</em> used for displaying the
     * Window itself -- that must be done by the client.
     *
     * @param wm The window manager for adding new windows.
     */
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) 
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) 
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    

可以看到最后一行代码处,有一个WindowManagerImpl的方法,即createLocalWindowManager方法,继续跟踪这个方法:

   public WindowManagerImpl createLocalWindowManager(Window parentWindow) 
        return new WindowManagerImpl(mDisplay, parentWindow);
    

单纯的构建了一个WindowManagerImpl对象,不过和ContextImpl里面注册WindowManager不同的是这里多了一个参数,
parentWindow
这个参数的增加说明构建WindowManagerImpl的这个方法是于具体的window相关联的。表示java层上的Window对象已经和WindowManager建立了第一步的联系。

好了,我们通过WindowManagerImpl和Window建立联系之后接下来进行分析WindowManagerImpl代码代码如下:

public final class WindowManagerImpl implements WindowManager 
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;

    public WindowManagerImpl(Display display) 
        this(display, null);
    

    private WindowManagerImpl(Display display, Window parentWindow) 
        mDisplay = display;
        mParentWindow = parentWindow;
    

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) 
        return new WindowManagerImpl(mDisplay, parentWindow);
    

    public WindowManagerImpl createPresentationWindowManager(Display display) 
        return new WindowManagerImpl(display, mParentWindow);
    

    @Override
    public void addView(View view, ViewGroup.LayoutParams params) 
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    

    @Override
    public void updateViewLayout(View view, ViewGroup.LayoutParams params) 
        mGlobal.updateViewLayout(view, params);
    

    @Override
    public void removeView(View view) 
        mGlobal.removeView(view, false);
    

    @Override
    public void removeViewImmediate(View view) 
        mGlobal.removeView(view, true);
    

    @Override
    public Display getDefaultDisplay() 
        return mDisplay;
    

我们看到在WindowManagerImpl中有一个WindowManagerGlobal的单例对象,而WindowManagerImpl的具体实现方法也是调用WindowManagerGlobal中的,也就是说WindowManagerGlobal是具体的实现类,而WindowManagerImpl只是一个架子罢了,我们又有了解的是,每一次的创建出一个界面显示到屏幕上所调用的是addView方法我,我们现在主要看的是addView方法,看看WindowManagerGlobal到底是怎么显示界面的:

 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);
        

        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;
        
    

这一方法首先是判断参数的合法性,在合法性的同事给参数赋值。

而关于创建界面的主要步骤如下:

  • 构建ViewRootImpl对象
  • 把参数布局设置到view
  • 存储ViewRootImpl,View,LayoutParams到列表
  • 通过ViewRootImpl的setView方法把view显示到窗口界面上。

值得注意的是ViewRootImpl并不是一个view而是一个继承Handler类的,作为native和java层view系统的一个通信桥梁。

代码分析到这里我们就已经到了Android的Framework层了。

下面才是Framework和Native层建立通讯关系的具体类,即使ViewRootImpl

因为addView的第一步就是创建ViewRootImpl的对象root,我们可以看下他的代码:

    public ViewRootImpl(Context context, Display display) 
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();

        mDisplayAdjustments = display.getDisplayAdjustments();

        mThread = Thread.currentThread();
  /**
 *省略代码
 **/
 
我们看到代码里有一个WindowManagerGlobal的方法,我们不得不怀疑这个代码和ViewRootImpl的作用有很大的关系,所以我们去追踪这个方法的实现:

    public static IWindowSession getWindowSession() 
        synchronized (WindowManagerGlobal.class) 
            if (sWindowSession == null) 
                try 
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            imm.getClient(), imm.getInputContext());
                    float animatorScale = windowManager.getAnimationScale(2);
                    ValueAnimator.setDurationScale(animatorScale);
                 catch (RemoteException e) 
                    Log.e(TAG, "Failed to open window session", e);
                
            
            return sWindowSession;
        
    
    public static IWindowManager getWindowManagerService() 
        synchronized (WindowManagerGlobal.class) 
            if (sWindowManagerService == null) 
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
            
            return sWindowManagerService;
        
    

在getWindowSession方法中,首先根据getWindowManagerService方法获得了IWindowManager对象,而getWindowManagerService方法是通过ServiceManager.getService("window")方法获取WindowManagerService,并且通过IWindowManager.Stub.asInterface方法把这个函数直接转换成IWindowManager对象。

我们下面一步步来,先去看getService方法的实现:

    public static IBinder getService(String name) 
        try 
            IBinder service = sCache.get(name);
            if (service != null) 
                return service;
             else 
                return getIServiceManager().getService(name);
            
         catch (RemoteException e) 
            Log.e(TAG, "error in getService", e);
        
        return null;
    
返回的是一个IBinder对象。也就是说Android Framework和WindowManagerService通过的是binder机制进行通讯的,我们这边到这一步才真正的和WindowManagerService建立初步的联系。

接下来我们继续分析这个getWindowSession方法的实现:

我们获取到IWindowManager对象之后,使用openSession方法来和WMS建立一个通信会话,以后如果有各种需求只需要通过这个session就好了。


和WMS建立了session关系之后,就是我们已经完成了ViewRootImpl对象的创建,之后我们就可以使用setView方法进行显示界面了,这个方法会向WMS发送显示界面的请求。

我们看下setView的代码:

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) 
        synchronized (this) 
       
                requestLayout();
                try 
                 
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mInputChannel);
                 
            
        
    

代码简化之后如上,源代码非常复杂,但是我们主要关注的是setView通知显示界面,所以我们在setView上面只需要关注请求布局的方法(requestLayout),和发送请求的方法(  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mInputChannel);)

请求布局的方法实现:

    @Override
    public void requestLayout() 
        if (!mHandlingLayoutInLayoutRequest) 
            checkThread();
            mLayoutRequested = true;
            <pre name="code" class="java">    void scheduleTraversals() 
        if (!mTraversalScheduled) 
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            scheduleConsumeBatchedInput();
        
    

();

 
    void scheduleConsumeBatchedInput() 
        if (!mConsumeBatchedInputScheduled) 
            mConsumeBatchedInputScheduled = true;
            mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
                    mConsumedBatchedInputRunnable, null);
        
    

也就是向handler中发送了一个消息,这个消息会触发整个视图的绘制操作。即performTraversals()方法,看下这个方法:

    private void performTraversals() 

额...方法太复杂,我们要知道的是在这个方法里面总共做了四件事情:

  • 对象,获取Surface对象,用于图形的绘制
  • 丈量,丈量整个视图书的各个View的大小,performMeasure方法
  • 布局,使用performLayout方法布局整个视图树
  • 绘制,使用performDraw绘制真个视图树

绘制界面的代码就不专门的进行表现了,我们仔细研究了performDraw的代码之后发现具体实现绘制视图界面的方法是draw方法,在这个方法里面,我们想要显示图形到界面要经过一下几个步骤:

  • 判断是CPU绘制还是GPU绘制
  • 获取Surface对象
  • 通过surface对象获取并且锁住Canvas绘图对象
  • 从DecorView开始发起整个视图树的绘制
  • 从Surface解锁Canvas,并且通知SurfaceFlinger更新视图







以上是关于Android 设计模式 笔记 - 深入了解WindowManager的主要内容,如果未能解决你的问题,请参考以下文章

Android 设计模式 笔记 - 深入了解属性动画

深入了解Android蓝牙Bluetooth——《基础篇》

关于 Android 稳定性优化你应该了解的知识点

Android项目架构设计深入浅出

读书笔记《Linux内核设计与实现》进程管理与调度

Android深度探索——第六章读书笔记及心得