战略性了解WindowManager

什么是战略性了解:知其然,无需知其所以然,也就是知道整体结构框架即可,不追根内部具体实现。
注:文章前面会探索API 14的源码,后面会探索API 28的源码


WindowManager直译过来叫做窗口管理者,用了这么多年Window的我们,应该对窗口这个概念并不陌生,打开应用,弹出应用窗口,使用应用。电脑屏幕大,所以我们可以在一个电脑屏幕上看到多个应用,手机屏幕小,只看一个应用的体验比较好。

如果是我们要写一个类叫做WindowManager,我们会写哪些方法呢?每个应用都给他分配一个WindowManager,然后添加界面,删除界面,更新界面,差不多就这些方法吧,等有需求了再拓展嘛,嗯嗯,大概是这个逻辑。看看实际是怎么实现的吧。

ViewManager探索

根据源码,我们很轻易的发现WindowManager其实是一个接口,并且继承ViewManager,ViewManager也是个接口。

public interface WindowManager extends ViewManager

ViewManager听着像是管理View的,感觉添加View删除View什么的都在这里,别猜了,先看看源码:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

添加View,更新View布局,移除View,方法名字倒是一目了然的,但是为啥没有deleteView,难道Android窗口这里不会删除View吗,感觉移除操作很简单,只需要把View设置为不可见就行了,也不知道内部怎么实现的,根据方法名字猜想一波吧。

WindowManager探索

我们还是来看看继承了ViewManager的WindowManager,看看他又实现了啥,最开始猜想的添加删除view居然是
ViewManager的方法,现在都不知道WindowManager这个类是干嘛的了,看波源码再做打算。

public interface WindowManager extends ViewManager {
    
    public static class BadTokenException extends RuntimeException {
        public BadTokenException() {}
        public BadTokenException(String name) {
            super(name);
        }
    }

    public Display getDefaultDisplay();

    public void removeViewImmediate(View view);

    public boolean isHardwareAccelerated();
    
    public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable{
            // 各种属性
    }
}

内部居然有个异常类,倒是挺简单;还有个静态公共类,是我们熟悉的LayoutParams,原来是在这里定义的。

接着是三个待实现的接口方法,我们来猜猜是干嘛的:

    // 获取默认Display实例
    public Display getDefaultDisplay();
    // 立刻移除View
    public void removeViewImmediate(View view);
    // 是否使用硬件加速
    public boolean isHardwareAccelerated();

猜测完毕,源码看到这里,下面两个方法倒是还是好理解,第一个方法不知道Display是啥东西,感觉是跟屏幕有点关系,看看源码大部分都是一些get的方法,听着都像是获取屏幕都信息:

// 随便摘要一些方法
public class Display {
    ...
    public void getSize(Point outSize){...}
    public int getDisplayId(){...}
    public int getWidth(){...}
    public int getHeight(){...}
    public void getRealSize(Point outSize){...}
    ...
}

大概了解一些方法是干嘛的,不求甚解。确实,主要的一些方法还是跟屏幕有关系。

WindowManagerImpl探索

既然大概知道了WindowManager的结构,那么来了解一下WindowManager的子类吧。

WindowManager的子类有这么几个,除去Test旗下的类,在剩下的类中,有一个类带着主角脸,那就是WindowManagerImpl,既然这个类自带主角脸,那么我们主要就来探索一下这个类。

进去这个类之后,发现这个类只有500多行的代码,难得源码才500多行,好好研究一下。既然继承了WindowManager和ViewManager接口,不然先看看WindowManagerImpl对这几个方法具体这么实现的。

ViewManager方法的具体实现

首先看看ViewManager的那三个方法:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

第一个是addView方法是吧。那就来吧:

    public void addView(View view) {
        addView(view, new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE));
    }

    public void addView(View view, ViewGroup.LayoutParams params) {
        addView(view, params, null, false);
    }

    public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) {
        addView(view, params, cih, false);
    }

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {
        ...
    }

没想到居然有这么多addView方法,而且又是以这种形式调用的,最终还是会调用最下面的这个方法,那么我们就来具体看看最下面的这个方法是怎么实现的吧。

为了便于阅读,我将addView方法的内部划分成了三个部分,我们一个一个的来看,大事化小。(注:这三个部分合起来并非addView所有源码,我只是挑出了主要代码)

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {
        // 第一部分
        // 第二部分
        // 第三部分
    }

我们首先来看第一部分的源码:

    private View[] mViews;
    private ViewRootImpl[] mRoots;
    
    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {

        final WindowManager.LayoutParams wparams
                = (WindowManager.LayoutParams) params;

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (this) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (!nest) {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                root = mRoots[index];
                root.mAddNesting++;
                // Update layout parameters.
                view.setLayoutParams(wparams);
                root.setLayoutParams(wparams, true);
                return;
            }

           // 第二部分
           // 第三部分

        }
        
        root.setView(view, wparams, panelParentView);
    }

这些代码看着并非那么晦涩难懂,虽然不知道findViewLocked这个方法是干嘛的,但是我们可以继续看下去,毕竟就两个if而已,我们看到第二个if抛了个异常,条件是!nest,但是根据前面看到的addView方法的一系列的引用,好像此处的nest变量为false,那么这个!nest就一定为true了,所以这个异常一定会抛出来,我们还是看看第一个if的index>=0在什么情况下进去吧,再往上看就是findViewLocked方法了,姑且进去一探究竟。

    private View[] mViews;
    
    private int findViewLocked(View view, boolean required) {
        synchronized (this) {
            final int count = mViews != null ? mViews.length : 0;
            for (int i = 0; i < count; i++) {
                if (mViews[i] == view) {
                    return i;
                }
            }
            if (required) {
                throw new IllegalArgumentException(
                        "View not attached to window manager");
            }
            return -1;
        }
    }

看来WindowManagerImpl是用View[]来储存view的,这个方法是用来检查WindowManagerImpl是否已经拥有这个view了。
我们再回到addView方法:

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {

        synchronized (this) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (!nest) {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
            }

           // 第二部分
           // 第三部分

        }
        
    }

再看这个if判断好像就很合理了,如果该view已经被添加了,就抛出异常。

我们接着来看第二部分的代码:

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {
        if (false) Log.v("WindowManager", "addView view=" + view);

        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException(
                    "Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams
                = (WindowManager.LayoutParams) params;

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (this) {
            // 第一部分
            
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews != null ? mViews.length : 0;
                for (int i = 0; i < count; i++) {
                    if (mRoots[i].mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews[i];
                    }
                }
            }

            root = new ViewRootImpl(view.getContext());
            root.mAddNesting = 1;
            if (cih == null) {
                root.mCompatibilityInfo = new CompatibilityInfoHolder();
            } else {
                root.mCompatibilityInfo = cih;
            }

            view.setLayoutParams(wparams);
            
            // 第三部分
        }
        root.setView(view, wparams, panelParentView);
    }

第二部分的代码,第一个判断涉及binder,不在我们的讨论范围,我们就暂且忽视。(一旦讨论会涉及更多其他类的源码,还有AIDL类,涉及源码太多,就此打住)

接下来就涉及ViewRootImpl,所以第二部分的代码就此跳过吧,我们来看看第三部分。

    private void addView(View view, ViewGroup.LayoutParams params,
                         CompatibilityInfoHolder cih, boolean nest) {
        if (false) Log.v("WindowManager", "addView view=" + view);

        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException(
                    "Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams
                = (WindowManager.LayoutParams) params;

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (this) {
        
            // 第一部分
            // 第二部分        

            if (mViews == null) {
                index = 1;
                mViews = new View[1];
                mRoots = new ViewRootImpl[1];
                mParams = new WindowManager.LayoutParams[1];
            } else {
                index = mViews.length + 1;
                Object[] old = mViews;
                mViews = new View[index];
                
                System.arraycopy(old, 0, mViews, 0, index - 1);
                old = mRoots;
                mRoots = new ViewRootImpl[index];
                System.arraycopy(old, 0, mRoots, 0, index - 1);
                old = mParams;
                mParams = new WindowManager.LayoutParams[index];
                System.arraycopy(old, 0, mParams, 0, index - 1);
            }
            index--;

            mViews[index] = view;
            mRoots[index] = root;
            mParams[index] = wparams;
        }
        root.setView(view, wparams, panelParentView);
    }

第三部分的代码显的中规中矩,如果被添加的view是第一个,就新建一个数据放到第一个位置,如果是第N个,就将原来的数组复制一份,并新增一个位置,将要添加的view放到新增的位置上。然后调用ViewRootImpl的setView方法,将View加载进去。(setView方法的内容很多,你甚至可以直接通过追踪源码找到view的事件分发那里去)

到此为止,我们就算了解了addView的方法。从整个结构上来看,addView的结构很清晰,将view添加进去。不过记录了添加的view罢了。

接着我们来看具体首先的第二个方法:updateViewLayout

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

源码很短:

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams
                = (WindowManager.LayoutParams) params;

        view.setLayoutParams(wparams);

        synchronized (this) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots[index];
            mParams[index] = wparams;
            root.setLayoutParams(wparams, false);
        }
    }

找到View对应的ViewRootImpl类,然后调用setLayoutParams方法,间接刷新界面,也算对得起这个方法的名字。

接着我们来看removeView方法:

    public void removeView(View view) {
        synchronized (this) {
            int index = findViewLocked(view, true);
            View curView = removeViewLocked(index);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

首先查看要被移除的view的位置是哪一个,然后调用removeViewLocked,我们来看看removeViewLocked做了啥:

    View removeViewLocked(int index) {
        ViewRootImpl root = mRoots[index];
        View view = root.getView();

        // Don't really remove until we have matched all calls to add().
        root.mAddNesting--;
        if (root.mAddNesting > 0) {
            return view;
        }

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
            if (imm != null) {
                imm.windowDismissed(mViews[index].getWindowToken());
            }
        }
        root.die(false);
        finishRemoveViewLocked(view, index);
        return view;
    }

里面的代码涉及了很多位置的方法,不过我们依然可以根据方法名来判断这个方法具体干了什么,不过里面有行注释能够给我们提供不少帮助,翻译过来:在调用add()方法之前,并没有真正移除view。
不过这个方法总体就是移除指定的view了。(不求甚解)

WindowManager方法的具体实现

现在我们来看看关于WindowManager接口的具体实现过程:

public interface WindowManager extends ViewManager { 
    public Display getDefaultDisplay();
    public void removeViewImmediate(View view);
    public boolean isHardwareAccelerated();
}

WindowManager接口的具体实现就要简单的多了。

    public Display getDefaultDisplay() {
        return new Display(Display.DEFAULT_DISPLAY, null);
    }

获取默认Display原来就是获取Display的默认实例。

    public void removeViewImmediate(View view) {
        synchronized (this) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots[index];
            View curView = root.getView();

            root.mAddNesting = 0;
            root.die(true);
            finishRemoveViewLocked(curView, index);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

这个方法倒是和removeView方法很类似。

public boolean isHardwareAccelerated() {
        return false;
}

这个方法就显的异常简单了。

到目前为止,我们就算是把WindowManager的这6个方法的具体实现给看完了,那我们又能得出什么呢,总不能白看吧。

我们发现,WindowManager确实主要是用来管理view的,主要就是view的那几个操作,增加删除和更新操作,但这里的删除更新,并不是由WindowManager本身来实现的,而是借助ViewRootImpl类来进行真正具体的操作。本身的主要任务是通过数组的方式记录View和对应的ViewRootImpl,不愧是管理者,指挥其他人帮你干事就行了,然后我们指挥管理者。

基于API28的探索

前面的代码都是基于API14的代码,为什么我要用API14的代码讲解之后再用API28的代码呢,因为低级的API往往比高级的API源码要简单很多,但是Android的源码,这些代码的主要架构是不会变的,现在我们已经了解了API14的源码,那么现在我们来了解API28的源码就会快很多,试试?

API28的ViewManager类倒是没有变化,不过WindowManager倒是变了不少,现在来看看这个接口变成什么样子了:

public interface WindowManager extends ViewManager {

    // 一堆属性
    // 两个异常类

    public Display getDefaultDisplay();

    public void removeViewImmediate(View view);

    public interface KeyboardShortcutsReceiver {
        void onKeyboardShortcutsReceived(List<KeyboardShortcutGroup> result);
    }

    public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);

    public Region getCurrentImeTouchRegion();

    public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable{
            // 各种属性
    }

}

我们可以看到,还是有不少变化,新增了2个方法,去掉了isHardwareAccelerated这个方法。

子类也由多个变成了一个:

WindowManagerImpl的变化更大,从原来的500多行,变成了100来行,难得源码变少。

大概看看里面长什么样:

public final class WindowManagerImpl implements WindowManager{
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

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

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

原来里面的方法都被mGlobal给承包了,以前是通过ViewRootImpl实现的东西,现在由WindowManagerGlobal实现,所以这到底是个啥。

进来WindowManagerGlobal这个类之后,发现这个类,跟API14的WindowManagerImpl特别像,以前WindowManagerImpl有的方法,现在在WindowManagerGlobal这个类里都能找到,而且还做了拓展,并且实现方式也很类似,看来Google将原来WindowManagerImpl的方法都让WindowManagerGlobal去管理了。而且储存view的形式也从数组变成了ArrayList,想必是要管理更多的View了。

总结

通过对WindowManager类的探索,我们大致了解到了,其实这个类主要是对view进行管理,添加移除什么的,但是这只是个管理类,所以具体的实现不可能写在这个类里面,若要了解具体如何实现的,还需进入ViewRootImpl类里面细细探索,这个类才是真正的大头,我们只是站在了一个顶端的位置俯瞰整个结构,还需深入每一个模块才能知其所以然。

我们也知道了老代码和新代码虽然有差异,但是核心的思想还是不会变的,如果要想了解某些模块的具体实现流程,在啃不动源码的时候,不妨啃啃老代码,老代码没有花里胡哨(各种各样)的类去装饰,侧重核心,可以很快了解内部机制。(其实本篇博客的目的是这个?)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值