Window的内部机制

老样子,灵魂画手给你们绘制的整体关系图…

image

Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是不存在的,它是以View的形式存在。这点从ViewManager的定义也可以看得出来,它只提供三个接口方法:addView、updateViewLayout、removeView,这些方法都是针对View的。这说明View才是Window存在的实体。

Window的创建过程

首先要分析Window的创建过程,就必须了解Activity的启动过程。

Activity的启动过程很复杂,最终会由ActivityThread中的handleLaunchActivity()来完成整个启动过程。

在这个方法中会通过performLaunchActivity()方法创建Activity,performLaunchActivity()内部通过类加载器创建Activity的实例对象,并调用其attach()方法为其关联运行过程中所依赖的一系列上下文环境变量以及创建与绑定窗口。

具体不在赘述,更多Activity启动细节请移步Activity的启动过程,地址为:

http://blog.csdn.net/qian520ao/article/details/78156214

//ActivityThreadprivate void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ... 
    //获取WindowManagerService的Binder引用(proxy端)。
    WindowManagerGlobal.initialize();        //会调用Activity的onCreate,onStart,onResotreInstanceState方法
    Activity a = performLaunchActivity(r, customIntent);    if (a != null) {
        ... 
        //会调用Activity的onResume方法.
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        ... 
    } 

}//ActivityThread

   private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {            //通过类加载器创建Activity
        Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
        ...       

        //通过LoadedApk的makeApplication方法来创建Application对象
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);            if (activity != null) {
            ... 
            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, window);
            ... 

            //onCreate
            mInstrumentation.callActivityOnCreate(activity, r.state);                        //onStart
            activity.performStart();

        }                    return activity;
    }

在Activity的attach()方法里,系统会创建Activity所属的Window对象并为其设置回调接口,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法。Callback接口中的方法很多,下面举几个比较眼熟的方法。

public interface Callback { 

        public boolean dispatchTouchEvent(MotionEvent event); 

        public View onCreatePanelView(int featureId); 

        public boolean onMenuItemSelected(int featureId, MenuItem item); 

        public void onContentChanged(); 

        public void onWindowFocusChanged(boolean hasFocus); 

        public void onAttachedToWindow(); 

        public void onDetachedFromWindow(); 
    } 

//Activity final void attach(...) { 
        //绑定上下文 
        attachBaseContext(context); 

        //创建Window,PhoneWindow是Window的唯一具体实现类 
        mWindow = new PhoneWindow(this, window);        //此处的window==null,但不影响 
        mWindow.setWindowControllerCallback(this); 
        mWindow.setCallback(this); 
       ...        //设置WindowManager 
        mWindow.setWindowManager( 
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 
                mToken, mComponent.flattenToString(), 
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
        if (mParent != null) { 
            mWindow.setContainer(mParent.getWindow()); 
        } 
        //创建完后通过getWindowManager就可以得到WindowManager实例 
        mWindowManager = mWindow.getWindowManager();//其实它是WindowManagerImpl 

    } 

    @Override 
    public Object getSystemService(@ServiceName @NonNull String name) { 
        ...        if (WINDOW_SERVICE.equals(name)) { 
            return mWindowManager; 
        }  
        return super.getSystemService(name); 
    }

等,等一下。看到这里是不是有点懵,Activity的getSystemService根本没有创建WindowManager,那么mWindow.setWindowManager()设置的岂不是空的WindowManager,那这样它的下一步mWindowManager = mWindow.getWindowManager()岂不是无线空循环?

因为PhoneWindow中并没有setWindowManager()方法,所以我们打开Window类看看。

//Window 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  
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); 
} 

//在WindowManagerImpl类中 public WindowManagerImpl createLocalWindowManager(Window parentWindow) { 
    return new WindowManagerImpl(mContext, parentWindow); 
}

类似于PhoneWindow和Window的关系,WindowManager是一个接口,具体的实现是WindowManagerImpl。

到了这里Window已经创建完毕,在上面的performLaunchActivity()方法中我们可以看到调用了onCreate()方法:

//ActivityThread --> performLaunchActivity mInstrumentation.callActivityOnCreate(activity, r.state);

在Activity的onCreate方法里,我们通过setContentView()将view添加到DecorView的mContentParent中,也就是将资源布局文件和phoneWindow关联。

所以在PhoneWindow的最后,因为Activity实现了Callback接口,便可以通过下面代码通知Activity视图已经发生改变。

//PhoneWindow  -->  setContentView callback.onContentChanged();

经过了上面几个过程,Window和DecorView已经被创建并初始化完毕,Activity的布局文件也成功添加到了DecorView的mContentParent中,但这个时候的DecorView还没有被WindowManager正式添加到Window中。

这里需要理解的是,Window更多表示的是一种抽象功能集合,虽然说早在Activity的attach方法中window就已经被创建了,但是这个时候由于DecorView并没有被WindowManager识别,所以这个时候的Window暂时无法提供具体功能。

总的来说,Window可以成功使用有2个标志:

  • ①View绘制完毕,可以呈现给用户。

  • ②View可以接收外界信息(触摸事件等)。

Window的添加过程

PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View,作为窗口添加到 WmS 的过程是由 WindowManager 来完成的。WindowManager 的具体实现是 WindowManagerImpl。

下面我们继续来分析handleLaunchActivity()方法中handleResumeActivity()的执行过程。

//ActivityThread final void handleResumeActivity(IBinder token, 
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { 

        //把activity数据记录更新到ActivityClientRecord 
        ActivityClientRecord r = mActivities.get(token); 

        r = performResumeActivity(token, clearHide, reason); 

        if (r != null) { 

            if (r.window == null && !a.mFinished && willBeVisible) { 
                r.window = r.activity.getWindow(); 
                View decor = r.window.getDecorView(); 
                decor.setVisibility(View.INVISIBLE);//不可见 
                ViewManager wm = a.getWindowManager(); 
                WindowManager.LayoutParams l = r.window.getAttributes(); 
                a.mDecor = decor; 
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 

             ... 
                if (a.mVisibleFromClient && !a.mWindowAdded) { 
                    a.mWindowAdded = true; 
                    wm.addView(decor, l);//把decor添加到窗口上(划重点) 
                } 

            }  
                //屏幕参数发生了改变 
                performConfigurationChanged(r.activity, r.tmpConfig); 

                WindowManager.LayoutParams l = r.window.getAttributes(); 

                    if (r.activity.mVisibleFromClient) { 
                        ViewManager wm = a.getWindowManager(); 
                        View decor = r.window.getDecorView(); 
                        wm.updateViewLayout(decor, l);//更新窗口状态 
                    } 

                ...                if (r.activity.mVisibleFromClient) { 
                    //已经成功添加到窗口上了(绘制和事件接收),设置为可见 
                    r.activity.makeVisible(); 
                } 

            //通知ActivityManagerService,Activity完成Resumed 
             ActivityManagerNative.getDefault().activityResumed(token); 
        }  
    }

在上面代码中,首先配置ActivityClientRecord,之后将DecorView设置为INVISIBLE,因为View并未绘制完成,当前的DecorView只是一个有结构的空壳。

然后通过WindowManagerImpl将DecorView正式的添加到窗口上wm.addView(decor, l);,这一步非常非常重要,因为它包括了2个比较重要和常见的过程:Window的添加过程和View的绘制流程。

灵魂画手一出手就知有没有~~下面画一张图来看看整体添加流程,留一个大致的概括印象。

image

窗口的添加过程如上图所示,我们知道 WmS 运行在单独的进程中。这里 IWindowSession 执行的 addtoDisplay 操作应该是 IPC 调用。接下来的Window添加过程,我们会知道每个应用窗口创建时,最终都会创建一个 ViewRootImpl 对象。

ViewRootImpl 是一很重要的类,类似 ApplicationThread 负责跟AmS通信一样,ViewRootImpl 的一个重要职责就是跟 WmS 通信,它通静态变量 sWindowSession(IWindowSession实例)与 WmS 进行通信。

每个应用进程,仅有一个 sWindowSession 对象,它对应了 WmS 中的 Session 子类,WmS 为每一个应用进程分配一个 Session 对象。WindowState 类有一个 IWindow mClient 参数,是由 Session 调用 addToDisplay 传递过来的,对应了 ViewRootImpl 中的 W 类的实例。

简单的总结一下,ViewRootImpl通过IWindowSession远程IPC通知WmS,并且由W类接收WmS的远程IPC通知。(这个W类和ActivityThread的H类同样精悍的命名,并且也是同样的工作职责!)

图解完Window的添加过程,对整个流程有一个印象和思路,那么下面继续分析源码。

在上面的handleResumeActivity()方法中,我们看到源码通过wm.addView(decor, l);操作DecorView和WindowManager.LayoutParams。上面讲解也说过,因为WindowManager是接口,真正具体实现类是windowManagerImpl。如果Window中类的关系还不太清楚的可以再回到上面的「Window的内部机制」看图解。

//WindowManagerImpl 

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

我们看到这个WindowManagerImpl原来也是一个吃空饷的家伙!对于Window(或者可以说是View)的操作都是交由WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。这种工作模式是桥接模式,将所有的操作全部委托给WindowManagerGlobal来实现。

在WindowManagerImpl的全局变量中通过单例模式初始化了WindowManagerGlobal,也就是说一个进程就只有一个WindowManagerGlobal对象。

//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) { 
            //调整布局参数,并设置token 
            parentWindow.adjustLayoutParamsForSubWindow(wparams); 
        }  

        ViewRootImpl root; 
        View panelParentView = null; 

        synchronized (mLock) { 

            int index = findViewLocked(view, false); 
            if (index >= 0) { 
                if (mDyingViews.contains(view)) { 
                    //如果待删除的view中有当前view,删除它 
                    // Don't wait for MSG_DIE to make it's way through root's queue. 
                    mRoots.get(index).doDie(); 
                } 
                // The previous removeView() had not completed executing. Now it has. 
                //之前移除View并没有完成删除操作,现在正式删除该view 
            } 

            //如果这是一个子窗口个(popupWindow),找到它的父窗口。 
            //最本质的作用是使用父窗口的token(viewRootImpl的W类,也就是IWindow) 
            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); 
                    } 
                } 
            } 
            //创建ViewRootImpl,并且将view与之绑定 
            root = new ViewRootImpl(view.getContext(), display); 

            view.setLayoutParams(wparams); 

            mViews.add(view);//将当前view添加到mViews集合中 
            mRoots.add(root);//将当前ViewRootImpl添加到mRoots集合中 
            mParams.add(wparams);//将当前window的params添加到mParams集合中 
        } 

         ...            //通过ViewRootImpl的setView方法,完成view的绘制流程,并添加到window上。 
            root.setView(view, wparams, panelParentView); 
    }

在上面代码中有2个比较重要的知识点

  • 在WindowManagerGlobal中有如下几个重要的集合

    //存储所有Window对应的View 
    private final ArrayList<View> mViews = new ArrayList<View>(); 

    //存储所有Window对应的ViewRootImpl 
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); 

    //存储所有Window对应的布局参数 
    private final ArrayList<WindowManager.LayoutParams> mParams = 
            new ArrayList<WindowManager.LayoutParams>(); 

    //存储正被删除的View对象(已经调用removeView但是还未完成删除操作的Window对象)      
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
  • token

在源码中token一般代表的是Binder对象,作用于IPC进程间数据通讯。并且它也包含着此次通讯所需要的信息,在ViewRootImpl里,token用来表示mWindow(W类,即IWindow),并且在WmS中只有符合要求的token才能让Window正常显示。所以上面提出的问题

我们能否在Acitivty的onCreate()中创建popupWindow并显示呢?

就与之有关,我们暂时把token放一边,走完Window的添加过程先。(最后分析token)

言归正传,我们继续看代码。

在WindowManagerGlobal的addView()方法里,最后调用ViewRootImpl的setView方法,处理添加过程。

//WindowManagerGlobal  -->  addView 

            //创建ViewRootImpl,并且将view与之绑定 
            root = new ViewRootImpl(view.getContext(), display); 

            //通过ViewRootImpl的setView方法,完成view的绘制流程,并添加到window上。 
            root.setView(view, wparams, panelParentView);

通过上面这个代码可知,WindowManagerGlobal将View的处理操作全权交给ViewRootImpl,而且上面我们也提到了,View成功添加到Window,无非就是展现视图和用户交互。

  • ①View绘制完毕,可以呈现给用户。

  • ②View可以接收外界信息(触摸事件等)。

在ViewRootImpl的setView()方法中,将会完成上面2个艰巨而又伟大的任务。

//ViewRootImpl  

  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { 

                int res;  

                // Schedule the first layout -before- adding to the window 
                // manager, to make sure we do the relayout before receiving 
                // any other events from the system. 
                requestLayout();//View的绘制流程 

                if ((mWindowAttributes.inputFeatures 
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { 
                    //创建InputChannel 
                    mInputChannel = new InputChannel(); 
                } 

                try { 

                    //通过WindowSession进行IPC调用,将View添加到Window上 
                    //mWindow即W类,用来接收WmS信息 
                    //同时通过InputChannel接收触摸事件回调 
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, 
                            getHostVisibility(), mDisplay.getDisplayId(), 
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, 
                            mAttachInfo.mOutsets, mInputChannel); 
                } 

                ... 

                    //处理触摸事件回调 
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, 
                            Looper.myLooper()); 

                ... 
    }

在ViewRootImpl的setView()方法里,执行requestLayout()方法完成View的绘制流程(下篇文章专门讲解),并且通过WindowSession将View和InputChannel添加到WmS中,从而将View添加到Window上并且接收触摸事件。

通过mWindowSession来完成Window的添加过程 ,mWindowSession的类型是IWindowSession,是一个Bindler对象,真正的实现类是Session,也就是Window的添加是一次IPC调用。(mWindowSession在ViewRootImpl的构造函数中通过WindowManagerGlobal.getWindowSession();创建)

同时将mWindow(即 W extends IWindow.Stub)发送给WmS,用来接收WmS信息。

image
//Session 

    final WindowManagerService mService; 

   @Override 
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, 
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, 
            Rect outOutsets, InputChannel outInputChannel) { 

        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, 
                outContentInsets, outStableInsets, outOutsets, outInputChannel); 
    }

如此一来,Window的添加请求就交给WmS去处理了,在WmS内部会为每一个应用保留一个单独的Session。在WmS 端会创建一个WindowState对象用来表示当前添加的窗口。 WmS负责管理这里些 WindowState 对象。至此,Window的添加过程就结束了。

至于Window的删除和更新过程,举一反三,也是使用WindowManagerGlobal对ViewRootImpl的操作,最终也是通过Session的IPC跨进程通信通知到WmS。整个过程的本质都是同出一辙的。

image

Window中的token

在WindowManager的LayoutParams中,与type同等重要的还有token。

上面说到:在源码中token一般代表的是Binder对象,作用于IPC进程间数据通讯。并且它也包含着此次通讯所需要的信息,在ViewRootImpl里,token用来表示mWindow(W类,即IWindow),并且在WmS中只有符合要求的token才能让Window正常显示。

先剧透一下,在Window中,token(LayoutParams中的token)分为以下2种情况

  • 应用窗口 : token表示的是activity的mToken(ActivityRecord)

  • 子窗口 : token表示的是父窗口的W对象,也就是mWindow(IWindow)

Activity的attach()

我们通过源码分析token的流程,顺带再巩固一遍Window的创建流程。

在Activity的attach()方法中

//Activity 

 final void attach(Context context, ActivityThread aThread, 
            Instrumentation instr, IBinder token, int ident, 
            Application application, Intent intent, ActivityInfo info, 
            CharSequence title, Activity parent, String id, 
            NonConfigurationInstances lastNonConfigurationInstances, 
            Configuration config, String referrer, IVoiceInteractor voiceInteractor, 
            Window window) { 

        attachBaseContext(context); 

        mToken = token;//ActivityRecord 

        mWindow = new PhoneWindow(this, window); 

        ...
        mWindow.setWindowManager( 
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 
                mToken, mComponent.flattenToString(), 
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
        if (mParent != null) { 
            mWindow.setContainer(mParent.getWindow()); 
        } 
        mWindowManager = mWindow.getWindowManager(); 
    }

创建windowManager

在Window中创建windowManager,并且全局变量mAppToken就是上面代码传入的mToken(ActivityRecord)

//Window 

    private IBinder mAppToken;//Activity的mToken 

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName, 
            boolean hardwareAccelerated) { 

        mAppToken = appToken; 

        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); 
    } 

//WindowManagerImpl 

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) { 

        //parentWindow就是activity的Window 
        return new WindowManagerImpl(mContext, parentWindow); 
    } 

    @Override 
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { 
        applyDefaultToken(params); 
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); 
    }

我们拿出上面的关系图,目前已经分析了WindowManagerImpl,接下来就是WindowManagerGlobal的addView()方法。
image
//WindowManagerGlobal public void addView(View view, ViewGroup.LayoutParams params, 
            Display display, Window parentWindow) { 

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

        //parentWindow就是Acitivty的Window 
        if (parentWindow != null) { 
            //1.调整布局参数,并设置token 
            parentWindow.adjustLayoutParamsForSubWindow(wparams); 
        }  

        ViewRootImpl root; 
        View panelParentView = null; 

        synchronized (mLock) { 
            //2.如果这是一个子窗口个(popupWindow),找到它的父窗口。 
            //3.最本质的作用是使用父窗口的token(viewRootImpl的W类,也就是IWindow) 
            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); 
                        //从mRoots找到相同的W类,再从mViews找到父View 
                    } 
                } 
            } 

            //4.创建ViewRootImpl,并且将view与之绑定 
            root = new ViewRootImpl(view.getContext(), display); 

            view.setLayoutParams(wparams); 

            mViews.add(view); 
            mRoots.add(root); 
            mParams.add(wparams); 
        } 
            //5.通过ViewRootImpl的setView方法,完成view的绘制流程,并添加到window上。 
            root.setView(view, wparams, panelParentView); 
    }

我们一步一步分析,先看①,parentWindow.adjustLayoutParamsForSubWindow(wparams);

//Window 

    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { 

        //如果它是子窗口 
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && 
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { 
            if (wp.token == null) { 
                View decor = peekDecorView(); 
                if (decor != null) { 
                    wp.token = decor.getWindowToken();//分析View的getWindowToken 
                } 
            } 

        } 
        ...        else { 
            //应用窗口(dialog的情况) 
            //此时的wp.token即为mAppToken 
            if (wp.token == null) { 
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; 
            } 
        } 

    } 

我们看到如果是应用窗口,wp.token==null的情况,就会给他赋值mAppToken,而这个mAppToken就是我们上面在Activity的attach()方法中传入的mToken!

我们再分析子窗口的token,接上面的decor.getWindowToken()

//View  

    AttachInfo mAttachInfo; 
    /** 
     * Retrieve a unique token identifying the window this view is attached to. 
     * @return Return the window's token for use in 
     * {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}. 
     */ 
    public IBinder getWindowToken() { 
        return mAttachInfo != null ? mAttachInfo.mWindowToken : null; 
    }

ViewRootImpl

那么View的mAttachInfo又是如何赋值的呢?

我们接着看第③root = new ViewRootImpl(view.getContext(), display);

//ViewRootImpl类 

    public ViewRootImpl(Context context, Display display) { 
        mWindowSession = WindowManagerGlobal.getWindowSession(); 

        mWindow = new W(this);//W是用于接收WmS通知的 

        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); 

         ...
    } 

//View类 

        AttachInfo(IWindowSession session, IWindow window, Display display, 
                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) { 
            mSession = session; 
            mWindow = window; 
            mWindowToken = window.asBinder();//即W类 
            mDisplay = display; 
            mViewRootImpl = viewRootImpl; 
            mHandler = handler; 
            mRootCallbacks = effectPlayer; 
        }

那么,如果这个View是ViewGroup,如何确保它的所有子View都能调用getWindowToken()获取到mAttachInfo的mWindowToken呢?

这里我们涉及一点View的绘制流程看一下源码。

// ViewRootImpl 

    private void performTraversals() { 
       ... 
        if (mFirst) { 

            //第一次执行performTraversals时,会把自己的mAttachInfo关联到所有的子View 
            host.dispatchAttachedToWindow(mAttachInfo, 0); 
        } 
    } 

//ViewGroup.java 

    void dispatchAttachedToWindow(AttachInfo info, int visibility) { 

       final int count = mChildrenCount; 
       final View[] children = mChildren; 
       for (int i = 0; i < count; i++) { 
           final View child = children[i]; 
           //关联到所有子View 
           child.dispatchAttachedToWindow(info,visibility | (child.mViewFlags & VISIBILITY_MASK)); 
      } 
    } 

//View.java 

    void dispatchAttachedToWindow(AttachInfo info, int visibility) { 
       mAttachInfo = info; 
        ... 
     }

这里由于篇幅原因,部分省略,有兴趣的同学可以点击左下角的阅读原文进行内容查看。

所以无论是应用窗口,还是子窗口,token 不能为空。否则会抛出异常,并且应用窗口的token 必须是某个 Activity 的 mToken,子窗口的token 必须是父窗口的 IWindow 对象。而且子窗口还需等父窗口添加成功之后才可添加到Window上。

如果对这部分源码敢兴趣的请查看RxEasyHttp源码,由于此部分涉及的知识点和内容比较多,篇幅问题,准备在下一篇文章中单独介绍网络数据缓存的各大场景实现,敬请期待!

结语

至此,Window的机制已经探索的有些眉目,阅读源码read the fuck source code能够更好的了解到Android的各种机制流程,而且能够熟悉源码编写的码神他们的编写风格,例如performLaunchActivity()就是Activity的整体流程开始,performTraversals()就是开始绘制View,还有performMeasure()、performLayout()、performDraw()等等,并且探索了Window机制后,我们就能比较容易的上手View的绘制流程。

虽然说阅读源码在短时间内可能没办法体现出多大的作用,但是这是一种循序渐进积累和爆发的过程,只要不断的进步,量变终究能引起质变。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,306评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,657评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,928评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,688评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,105评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,024评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,159评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,937评论 0 212
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,689评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,851评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,325评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,651评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,364评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,192评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,985评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,154评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,955评论 2 279

推荐阅读更多精彩内容