Android探秘之WindowManager

来源:互联网 发布:jdk 8u51 windows x64 编辑:程序博客网 时间:2024/05/14 03:41

在Android的世界里,我们可以通过WindowManager将一个视图添加到屏幕上。下面就是实现此需求的两条关键语句:

WindowManager wm = (WindowManager) contex.getSystemService(Context.WINDOW_SERVICE);...wm.addView(view, layoutParam);

这篇文章将以这两条语句作为切入点,探究一下与WindowManager相关的源码(基于5.1.1系统)。

WindowManager是什么

根据WindowManager的定义

public interface WindowManager extends ViewManager

可以看出WindowManager是一个继承自ViewManager的接口。所以WindowManager继承了ViewManager中的两个主要函数:

public void addView(View view, ViewGroup.LayoutParams params);public void removeView(View view);

从函数名便可推断这两个函数的作用分别是往屏幕添加视图以及移除之前添加的视图。

既然WindowManager只是一个接口,那必然有类实现了这个接口。让我们回到这条语句

WindowManager wm = (WindowManager) contex.getSystemService(Context.WINDOW_SERVICE);

去探究一下通过contex.getSystemService(Context.WINDOW_SERVICE)拿到的WindowManager到底是什么。

因为与UI紧密相关的ContextActivity,所以假定这里的context是一个Activity

首先看一下ActivitygetSystemService的实现:

5033    public Object getSystemService(@ServiceName @NonNull String name) {5034        if (getBaseContext() == null) {5035            throw new IllegalStateException(5036                    "System services not available to Activities before onCreate()");5037        }50385039        if (WINDOW_SERVICE.equals(name)) {5040            return mWindowManager;5041        } else if (SEARCH_SERVICE.equals(name)) {5042            ensureSearchManager();5043            return mSearchManager;5044        }5045        return super.getSystemService(name);5046    }

通过5039行的if语句可以看出,如果参数nameWINDOW_SERVICE则直接返回Activity的成员变量mWindowManager. 接下来需要找到mWindowManager被初始化的地方。它的初始化紧随Activity的初始化。而Activity的初始化在ActivityThread.performLaunchActivity中进行。这里的ActivityThread就是我们通常所说的主线程或者UI线程。摘取ActivityThread.performLaunchActivity中的几条关键语句:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...       Activity activity = null;...       activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);...        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);

activity被创建出来后其attach方法即被调用。

5922    final void attach(Context context, ActivityThread aThread,5923            Instrumentation instr, IBinder token, int ident,5924            Application application, Intent intent, ActivityInfo info,5925            CharSequence title, Activity parent, String id,5926            NonConfigurationInstances lastNonConfigurationInstances,5927            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {           ...      5932        mWindow = PolicyManager.makeNewWindow(this);           ...5966        mWindow.setWindowManager(5967                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),5968                mToken, mComponent.flattenToString(),5969                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);5970        if (mParent != null) {5971            mWindow.setContainer(mParent.getWindow());5972        }5973        mWindowManager = mWindow.getWindowManager();5974        mCurrentConfig = config;5975    }

在第5932行,Activity的成员变量mWindow是一个Window类对象。Window是对Activity或者Dialog的视图的一种上层抽象。Window是一个抽象类,而PhoneWindow继承了Window并且实现了其中的一些关键方法。PhoneWindow中有一个类型为DecorView的成员变量mDecor,表示的是Activity对应的视图的最外层容器。PolicyManager.makeNewWindow(this)正好返回了一个PhoneWindow对象。

接下来在5966行,Window.setWindowManager被调用。该函数需要四个参数。首先我们看第一个参数(WindowManager)context.getSystemService(Context.WINDOW_SERVICE). 在这里context.getSystemService(Context.WINDOW_SERVICE)又一次被调用并且返回一个WindowManager对象。不过这里的context不是某个Activity对象,而是一个ContextImpl对象。接下来看一下ContextImpl.getSystemService的实现。

@Overridepublic Object getSystemService(String name) {    ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);    return fetcher == null ? null : fetcher.getService(this);}

可以看到getSystemService会根据参数nameSYSTEM_SERVICE_MAP中拿到对应的ServiceFetcher对象,然后通过ServiceFetcher.getService返回具体的对象。在ContextImpl中有一个函数的作用是往SYSTEM_SERVICE_MAP中放入(name, ServiceFetcher)映射。

private static void registerService(String serviceName, ServiceFetcher fetcher) {    if (!(fetcher instanceof StaticServiceFetcher)) {        fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;    }    SYSTEM_SERVICE_MAP.put(serviceName, fetcher);}

另外在ContextImpl中有一个static语句块,里面通过多次调用registerService方法将所有可能的(name, ServiceFetcher)映射放入了SYSTEM_SERVICE_MAP. 这里我们特别关注一下WINDOW_SERVICE:

634     registerService(WINDOW_SERVICE, new ServiceFetcher() {635             Display mDefaultDisplay;636             public Object getService(ContextImpl ctx) {637                 Display display = ctx.mDisplay;638                 if (display == null) {639                     if (mDefaultDisplay == null) {640                         DisplayManager dm =(DisplayManager)ctx.getOuterContext().getSystemService(Context.DISPLAY_SERVICE);642                         mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);643                     }644                     display = mDefaultDisplay;645                 }646                 return new WindowManagerImpl(display);647             }});

可以看到getService返回了一个WindowManagerImpl对象。之前提到WindowManager只是一个接口,而这里的WindowManagerImpl正好实现了WindowManager.

public final class WindowManagerImpl implements WindowManager {    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();    private final Display mDisplay;    private final Window mParentWindow;    private IBinder mDefaultToken;    public WindowManagerImpl(Display display) {        this(display, null);    }    private WindowManagerImpl(Display display, Window parentWindow) {        mDisplay = display;        mParentWindow = parentWindow;    }    ...}

有一点需要注意:这里用到了WindowManagerImpl的只需一个参数的构造函数。通过上面的类的定义可以看到,WindowManagerImpl还有一个需要两个参数的构造函数,这个构造函数的调用接下来就会看到。还有我们可以看到WindowManagerImpl中有一个类型为WindowManagerGlobal的成员变量mGlobal,使用了单例模式,它的作用将在下节说明。

现在回到Activity.attach方法的5966行,我们已经知道第一个参数是一个WindowManagerImpl对象。接着就看一下Window.setWindowManager的具体实现。

539     public void setWindowManager(WindowManager wm, IBinder appToken, String appName,540             boolean hardwareAccelerated) {541         mAppToken = appToken;542         mAppName = appName;543         mHardwareAccelerated = hardwareAccelerated544                 || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);545         if (wm == null) {546             wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);547         }548         mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);549     }

这里有两个比较关键的点:

  • 第541行,将参数appToken赋给了Window的成员变量mAppToken. appToken是一个Binder对象,在ActivityManagerService, WindowManagerService等系统服务中通过它来标识Activity. 让Window的成员变量mAppToken指向appToken,就好比这个Window得到了与之对应的Activity的身份通行证。这样一来,WindowManagerService就能知道这个Window是属于哪个Activity了。
  • 第548行,调用WindowManagerImpl.createLocalWindowManager创建了一个新的WindowManagerImpl对象,并赋给了成员变量mWindowManager. WindowManagerImpl.createLocalWindowManager的实现如下:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {        return new WindowManagerImpl(mDisplay, parentWindow);    }

可以看到这里用到了WindowManagerImpl中需要两个参数的构造函数,第二个参数是对应的Window.

再次回到Activity.attach方法。第5973行将Window的成员变量mWindowManager指向的WindowManagerImpl对象赋给了Activity的成员变量mWindowManager. 至此,我们就知道了通过Activity.getSystemService(Context.WINDOW_SERVICE);拿到的是一个WindowManager的实现类WindowManagerImpl的对象。对于非ActivityContext,调用它们的getSystemService方法实际上会调用ContextImpl.getSystemService. 这个方法返回的也是一个WindowManagerImpl的对象。只不过这个WindowManagerImpl的对象的成员变量mParentWindownull,也就是没有关联任何Window对象。

WindowManager.addView做了什么

接下来探究一下WindowManager.addView到底做了什么。

通过上面的分析,我们已经知道实现WindowManager这个接口的是WindowManagerImpl类,因此直接看一下WindowManagerImpl.addView的实现:

@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {    applyDefaultToken(params);    mGlobal.addView(view, params, mDisplay, mParentWindow);}

函数就只有两句话,我们看关键的第二句。这里的mGlobal就是之前提到过的WindowManagerGlobal对象。它是一个单例,也就意味着在一个应用进程里,虽然每一个Activity会对应不同的WindowManagerImpl对象,但是它们的视图是由唯一的一个WindowManagerGlobal对象统一管理。

在看WindowManagerGlobal.addView的实现之前,有必要先说明一下WindowManagerGlobal中有三个比较重要的ArrayList:

private final ArrayList<View> mViews = new ArrayList<View>();private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

这三个ArrayList分别保存了View, ViewRootImplWindowManager.LayoutParams.

接下来就具体分析一下WindowManagerGlobal.addView.

204    public void addView(View view, ViewGroup.LayoutParams params,205            Display display, Window parentWindow) {206        if (view == null) {207            throw new IllegalArgumentException("view must not be null");208        }209        if (display == null) {210            throw new IllegalArgumentException("display must not be null");211        }212        if (!(params instanceof WindowManager.LayoutParams)) {213            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");214        }215216        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;217        if (parentWindow != null) {218            parentWindow.adjustLayoutParamsForSubWindow(wparams);219        } else {220            // If there's no parent and we're running on L or above (or in the221            // system context), assume we want hardware acceleration.222            final Context context = view.getContext();223            if (context != null224                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {225                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;226            }227        }228229        ViewRootImpl root;230        View panelParentView = null;231232        synchronized (mLock) {233            // Start watching for system property changes.234            if (mSystemPropertyUpdater == null) {235                mSystemPropertyUpdater = new Runnable() {236                    @Override public void More ...run() {237                        synchronized (mLock) {238                            for (int i = mRoots.size() - 1; i >= 0; --i) {239                                mRoots.get(i).loadSystemProperties();240                            }241                        }242                    }243                };244                SystemProperties.addChangeCallback(mSystemPropertyUpdater);245            }246247            int index = findViewLocked(view, false);248            if (index >= 0) {249                if (mDyingViews.contains(view)) {250                    // Don't wait for MSG_DIE to make it's way through root's queue.251                    mRoots.get(index).doDie();252                } else {253                    throw new IllegalStateException("View " + view254                            + " has already been added to the window manager.");255                }256                // The previous removeView() had not completed executing. Now it has.257            }258259            // If this is a panel window, then find the window it is being260            // attached to for future reference.261            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&262                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {263                final int count = mViews.size();264                for (int i = 0; i < count; i++) {265                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {266                        panelParentView = mViews.get(i);267                    }268                }269            }270271            root = new ViewRootImpl(view.getContext(), display);272273            view.setLayoutParams(wparams);274275            mViews.add(view);276            mRoots.add(root);277            mParams.add(wparams);278        }279280        // do this last because it fires off messages to start doing things281        try {282            root.setView(view, wparams, panelParentView);283        } catch (RuntimeException e) {284            // BadTokenException or InvalidDisplayException, clean up.285            synchronized (mLock) {286                final int index = findViewLocked(view, false);287                if (index >= 0) {288                    removeViewLocked(index, true);289                }290            }291            throw e;292        }293    }

206-214行是对参数的正确性检查。

然后217行有一个if语句:如果parentWindow不为null则调用其adjustLayoutParamsForSubWindow方法。这里的parentWindow实际上指向的是WindowManagerImpl的成员变量mParentWindow. 如果我们仍然假定当前的Context是一个Activity, 那么parentWindow就非空。因此Window.adjustLayoutParamsForSubWindow被调用:

551     void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {552         CharSequence curTitle = wp.getTitle();553         if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&554             wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {                ...581         } else {582             if (wp.token == null) {583                 wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;584             }585             if ((curTitle == null || curTitle.length() == 0)586                     && mAppName != null) {587                 wp.setTitle(mAppName);588             }589         }            ...596     }

这里最关键的是第583行,将Window里保存的Activity的标识mAppToken赋给了传进来的WindowManager.LayoutParamstoken. 由于这个WindowManager.LayoutParams最后会传给WindowManagerService, 因此WindowManagerService可以通过这个token知道是哪个Activity要添加视图。

回到WindowManagerGlobal.addView, 248-257行会对要添加的view进行重复添加的检查。如果发现是重复添加则抛出异常。

如果当前要添加的view从属于某个之前已经添加的view,261-269行就会去找出那个已经添加的view.

接着在271行,一个ViewRootImpl对象被创建出来。ViewRootImpl在应用进程这边管理视图的过程中担任了重要的角色。像Activity对应的视图的measure, layout, draw等过程都是从ViewRootImpl开始。另外ViewRootImpl还负责应用进程和WindowManagerService进程之间的通信。

275-277行将view, rootwparams分别加入各自对应的ArrayList. View, ViewRootImplWindowManager.LayoutParams这三者是通过ArrayList的下标一一对应的。

最后282行调用了ViewRootImpl.setView, 在这个方法中ViewRootImpl会去通知WindowManagerService将新的视图添加到屏幕上。

总结

这篇文章所讲的内容可以浓缩成下面这张图。

windowmanager

一个Activity会有一个成员变量指向一个WindowManager的具体实现类WindowManagerImpl对象。该WindowManagerImpl对象会保存该Activity对应的一个PhoneWindow对象的引用。一个PhoneWindow又会引用一个DecorView对象。在一个应用进程中,会有一个WindowManagerGlobal的单例管理应用中所有Activity对应的视图。WindowManagerGlobal中有三个关键的ArrayList,分别保存了View, ViewRootImplWindowManager.LayoutParams.

上文所讲的这些内容都是发生在应用进程里的事情。在系统里真正负责视图管理的是WindowManagerService进程。其中,ViewRootImpl担任了沟通应用进程和WindowManagerService进程的角色。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 t恤的印花粘粘的怎么办 从包图网下载的模板素材丢失怎么办 大屏导航的语音功能怎么办用 手机导航不走地图上面走时怎么办 行车撞到步行人死亡师机逃离怎么办 小天才平板电脑不显示了怎么办 高德地图导航不显示车速怎么办 桥梁梁片强度达不到设计要求怎么办 新车交车检验表客户没签字怎么办 中铁快运职工拒绝提货要怎么办 奇瑞a3暖风水箱爆了怎么办 别人挖鱼塘占了我的山土怎么办 自己的鱼塘让别人强行占住了怎么办 公路扩路占地占了鱼塘怎么办? 玉米皮编垫子编好后玉米绳怎么办 入户门门框未预留纱窗位怎么办 门和墙有2cm缝隙怎么办 支座预埋钢板忘记埋了怎么办 做完线雕一边紧一边松怎么办 卖家把没发货的填写了单号怎么办 买的人民币白银亏了好多钱怎么办 带控制线的三相四线开关怎么办 覆膜除尘布袋风拉不动怎么办 家里装修把暖气管道打破了怎么办 冷水管与热水管接错了怎么办 磨砂皮的鞋子打湿变硬了怎么办 等离子淡化热处理层渗不够厚怎么办 寄快递快递公司把东西弄坏了怎么办 寄美国的快递客人拒绝清关怎么办 国际e邮宝几天没物流信息了怎么办 石家庄小学网上报名填错了怎么办 去医院看病不知道挂什么科怎么办 深水井深水泵埋了2米怎么办 请问我捡的手机不是我的指纹怎么办 宝宝把塑料子弹塞到了鼻子里怎么办 坐便池上面的小孔不出水怎么办 还没离职已经找好工作怎么办 因火车晚点而耽误下趟火车怎么办 在广州坐的士丢了东西怎么办 找兼职的话他要求交押金怎么办 08vip不给提现了怎么办