欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Window机制

程序员文章站 2022-04-09 23:17:20
...

引言

在Android开发中,一直都是使用Activity来显示并与用户交互,那么Activity是如何将具体的view显示给用户?如何控制与用户交互?从Window机制中,我们能得到具体的答案。
首先,贴一张Activity中关于显示的层级图,大概了解view加载、展示的流程及关系:

Window机制

关于Activity、Window和View

我们的工匠大神Activity

一个应用程序里所有的界面展示都来自于Activity,那Activity是如何工作的呢?
Activity工作过程:
要了解Activity工作过程,首先从启动开始,下面没有贴源码,因为本文章主题是三者之间的关系,而Activity东西太多了,就简单的讲一下。

启动:
从startActivity开始,它会调用到Instrumentation,然后Instrumentation通过Binder向AMS(ActivityManagerService)发请求,通过PIC启动Activity。而这个AIDL操作的方法定义在ApplicationThread中(里面包括了Activity所有的生命周期方法的调用)。然后通过Handle回到主线程启动activity。

启动Activity所执行的操作:
1、从ActivityClientRecord中获取待启动的Activity组件信息
2、通过Instrumentation的newActivity方法使用类加载器创建Activity对象
3、通过LoadedApk的mackApplication方法来尝试创建Application对象(如果Application已经创建,则不会重复创建)
4、创建ContextImpl对象,并通过Activity的attach方法来完成一些重要数据的初始化(包括让Activity跟Window关联)
5、调用Activity的onCreate方法
Activity其他生命周期的调用都是通过Binder向AMS发请求,然后执行的PIC操作,最后从ApplicationThread对生命周期调用。下面是重点:Activity、View、Window三者的关系。

美丽的窗花View

View如何跟Activity关联起来的?
其实View并不是直接跟Activity关联起来的,而是通过Window这个中间人。如前面所说,View只是窗花,Window才是直接关联到Activity上的。那么:
View如何跟Window关联起来呢?
下面先了解一下Window,就可以理解这个问题了

灵活的窗户Window

Window如何跟Activity关联?每一个Activity都包含了唯一一个PhoneWindow,这个就是Activity根Window(之所以是说根Window是因为在它上面可以增加更多其他的Window,例如:弹出框(dialog))
那么,PhoneWindow如何跟Activity关联起来的呢?来个最简单的,setContentView其实就让View与Window关联,Window跟Activity关联起来了。
那setContentView不是View跟Activity关联吗?
真相见Activity源码:

Window机制

明显是将layout设置到Window上了,那这个 getWindow() 返回的Window是谁呢? 是不是前面提及PhoneWindow?

真的是PhoneWindow,在 attach 的时候执行了PhoneWindow的初始化。
提到了 activity 的 attach 方法,该方法是在执行Activity启动时在ActivityThread里面的performLaunchActivity调用的。performLaunchActivity里面做了很多Activity启动过程具体的操作,例如:主题、记录Activity栈、执行Activity onCreate 方法等。
这么说来setContentView其实就是将View设置到Window上,Activity展示的其实是PhoneWindow上的内容。那么其实 setContentView 实际上是调用的 getWindow().setContentView。
PhoneWindow是个什么东西?它作为Activity跟View的中间人,它做了哪些工作?

首先 PhoneWindow 本身就是一个 Window。
从setContentView来分析:

Window机制
这里的 mContentParent 其实是一个 ViewGroup。这么看来就简单了。PhoneWindow里面包含了一个ViewGroup,setContentView其实就是将layout设置到了这个ViewGroup上了。

ViewRoot

所有View的绘制以及事件分发等交互都是通过它来执行或传递的。
ViewRoot对应ViewRootImpl类,它是连接WindowManagerService和DecorView的纽带,View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成。
ViewRoot并不属于View树的一份子。从源码实现上来看,它既非View的子类,也非View的父类,
onResume() 执行完后,WindowManager 将会执行 addView(),然后在这里面会去创建一个 ViewRootImpl 对象,接着将 DecorView 跟 ViewRootImpl 对象绑定起来,并且将 DecorView 的 mParent 设置成 ViewRootImpl,而 ViewRootImpl 是实现了 ViewParent 接口的,所以虽然 ViewRootImpl 没有继承 View 或 ViewGroup,但它确实是 DecorView 的 parent,
但是,它实现了ViewParent接口,这让它可以作为View的名义上的父视图。RootView继承了Handler类,可以接收事件并分发,Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRoot进行分发的。
四者之间的关系:
Window机制

DecorView

它直接跟PhoneWindow关联起来的,有了mContentParent,为啥还需要DecorView?
如图所见,DecorView它不仅包含了我们自己的布局,它还包含了titleBar。为啥需要?结构上的需要,更好的管理布局。
Window作为中间人,已经关联了Activity跟View了,那么如果处理Activity跟View之间的关系呢?
是时候揭开Window这个神秘面纱了:
之前提的PhoneWindow是继承于Window的,它是连接Activity跟View之间的桥梁。所有对View的一些操作都需要借助这个桥梁。

DecorView的创建

这部分内容主要讲DecorView是怎么一层层嵌套在Actvity,PhoneWindow中的,以及DecorView如何加载内部布局。
setContentView
先是从Activity中的setContentView()开始。

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

可以看到实际是交给Window装载视图。下面来看看Activity是怎么获得Window对象的?

 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) {
    ..................................................................
        mWindow = new PhoneWindow(this, window);//创建一个Window对象
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);//设置回调,向Activity分发点击或状态改变等事件
        mWindow.setOnWindowDismissedCallback(this);
     .................................................................
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//给Window设置WindowManager对象
 ....................................................................
    }

在Activity中的attach()方法中,生成了PhoneWindow实例。既然有了Window对象,那么我们就可以设置DecorView给Window对象了。

public void setContentView(int layoutResID) {
        if (mContentParent == null) {//mContentParent为空,创建一个DecroView
            installDecor();
        } else {
            mContentParent.removeAllViews();//mContentParent不为空,删除其中的View
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);//为mContentParent添加子View,即Activity中设置的布局文件
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();//回调通知,内容改变
        }
    }

看了下来,可能有一个疑惑:mContentParent到底是什么?
就是前面布局中@android:id/content所对应的FrameLayout。
通过上面的流程我们大致可以了解先在PhoneWindow中创建了一个DecroView,其中创建的过程中可能根据Theme不同,加载不同的布局格式,例如有没有Title,或有没有ActionBar等,然后再向mContentParent中加入子View,即Activity中设置的布局。到此位置,视图一层层嵌套添加上了。
下面具体来看看installDecor();方法,怎么创建的DecroView,并设置其整体布局?

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor(); //生成DecorView
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor); // 为DecorView设置布局格式,并返回mContentParent
        ...
        } 
    }
}

再来看看 generateDecor()

 protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

很简单,创建了一个DecorView。
再看generateLayout

protected ViewGroup generateLayout(DecorView decor) {
        // 从主题文件中获取样式信息
        TypedArray a = getWindowStyle();
        ...................
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }
     ................
        // 根据主题样式,加载窗口布局
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if(...){
            ...
        }

        View in = mLayoutInflater.inflate(layoutResource, null);    //加载layoutResource

       //往DecorView中添加子View,即文章开头介绍DecorView时提到的布局格式,那只是一个例子,根据主题样式不同,加载不同的布局。
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 这里获取的就是mContentParent
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            registerSwipeCallbacks();
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        ...

        return contentParent;
    }

虽然比较复杂,但是逻辑还是很清楚的。先从主题中获取样式,然后根据样式,加载对应的布局到DecorView中,然后从中获取mContentParent。获得到之后,可以回到上面的代码,为mContentParent添加View,即Activity中的布局。
以上就是DecorView的创建过程,其实到installDecor()就已经介绍完了,后面只是具体介绍其中的逻辑。

DecorView的显示

以上仅仅是将DecorView建立起来。通过setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?
这就要从ActivityThread开始说起了,由于个人也是一知半解,仅仅阐述下个人理解吧。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    //就是在这里调用了Activity.attach()呀,接着调用了Activity.onCreate()和Activity.onStart()生命周期,
    //但是由于只是初始化了mDecor,添加了布局文件,还没有把
    //mDecor添加到负责UI显示的PhoneWindow中,所以这时候对用户来说,是不可见的
    Activity a = performLaunchActivity(r, customIntent);

    ......

    if (a != null) {
    //这里面执行了Activity.onResume()
    handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);

    if (!r.activity.mFinished && r.startsNotResumed) {
        try {
                    r.activity.mCalled = false;
                    //执行Activity.onPause()
                    mInstrumentation.callActivityOnPause(r.activity);
                    }
        }
    }
}

重点看下handleResumeActivity(),在这其中,DecorView将会显示出来,同时重要的一个角色:ViewRoot也将登场。

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {

            //这个时候,Activity.onResume()已经调用了,但是现在界面还是不可见的
            ActivityClientRecord r = performResumeActivity(token, clearHide);

            if (r != null) {
                final Activity a = r.activity;
                  if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                //decor对用户不可见
                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 = true;
                    //被添加进WindowManager了,但是这个时候,还是不可见的
                    wm.addView(decor, l);
                }

                if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                     //在这里,执行了重要的操作,使得DecorView可见
                     if (r.activity.mVisibleFromClient) {
                            r.activity.makeVisible();
                        }
                    }
            }

当我们执行了Activity.makeVisible()方法之后,界面才对我们是可见的。

void makeVisible() {
   if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());//将DecorView添加到WindowManager
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);//DecorView可见
    }

到此DecorView便可见,显示在屏幕中。但是在这其中,wm.addView(mDecor, getWindow().getAttributes());起到了重要的作用,因为其内部创建了一个ViewRootImpl对象,负责绘制显示各个子View。
具体来看addView()方法,因为WindowManager是个接口,具体是交给WindowManagerImpl来实现的。

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

交给WindowManagerGlobal 的addView()方法去实现

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

              final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
             ......
                 synchronized (mLock) {

                 ViewRootImpl root;
                  //实例化一个ViewRootImpl对象
                 root = new ViewRootImpl(view.getContext(), display);
                 view.setLayoutParams(wparams);

                mViews.add(view);
                mRoots.add(root);
                mParams.add(wparams);
            }
             ......

             try {
                //将DecorView交给ViewRootImpl 
                root.setView(view, wparams, panelParentView);
            }catch (RuntimeException e) {
            }

            }
 }

看到其中实例化了ViewRootImpl对象,然后调用其setView()方法。其中setView()方法经过一些列折腾,最终调用了performTraversals()方法,然后依照下图流程层层调用,完成绘制,最终界面才显示出来。

其实ViewRootImpl的作用不止如此,还有许多功能,如事件分发。
要知道,当用户点击屏幕产生一个触摸行为,这个触摸行为则是通过底层硬件来传递捕获,然后交给ViewRootImpl,接着将事件传递给DecorView,而DecorView再交给PhoneWindow,PhoneWindow再交给Activity,然后接下来就是我们常见的View事件分发了。
硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity
不详细介绍了,如果感兴趣,可以看这篇文章。
由此可见ViewRootImpl的重要性,是个连接器,负责WindowManagerService与DecorView之间的通信。

总结

1、为什么要设计Activity、View、Window?
Activity就像工匠,Window就像是窗户,View就像是窗花,LayoutInflater像剪刀,Xml配置像窗花图纸。
Android根据他们不同的职能让他们各斯其活,同时也相互配合展示给我们灵活、精致的界面。为啥这样设计?因为这样的结构更好管理。就像为啥需要使用MVP、MVVM、各种设计模式一样。
2、Activity工作过程是什么样的?以Activity启动过程为例,Activity启动时是通过Binder向AMS(ActivityManagerService)发请求,通过PIC启动Activity的。
3、Window是什么?它的职能是什么?Activity要管理View需要通过Window来间接管理的。Window通过addView()、removeView()、updateViewLayout()这三个方法来管理View的。
4、View跟Window有什么联系?View需要通过Window来展示在Activity上。
5、Activity、View、Window三者如何关联?Activity包含了一个PhoneWindow,而PhoneWindow就是继承于Window的,Activity通过setContentView将View设置到了PhoneWindow上,而View通过WindowManager的addView()、removeView()、updateViewLayout()对View进行管理。Window的添加过程以及Activity的启动流程都是一次IPC的过程。Activity的启动需要通过AMS完成;Window的添加过程需要通过WindowSession完成。

Thanks

https://www.jianshu.com/p/8766babc40e0

https://juejin.im/entry/596329686fb9a06bc903b6fd

相关标签: Window