凯子哥带你学FrameworkActivity界面显示全解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了凯子哥带你学FrameworkActivity界面显示全解析相关的知识,希望对你有一定的参考价值。

前几天凯子哥写的Framework层的解析文章《Activity启动过程全解析》,反响还不错,这说明“写让大家都能看懂的Framework解析文章”的思想是基本正确的。

我个人觉得,深入分析的文章必不可少,但是对于更多的android开发者——即只想做应用层开发,不想了解底层实现细节——来说,“整体上把握,重要环节深入“是更好的学习方式。因为这样既可以有完整的知识体系,又不会在浩瀚的源码世界里迷失兴趣和方向。

所以呢,今天凯子哥又带来一篇文章,接着上一篇的结尾,重点介绍Activity开启之后,Android系统对界面的一些操作及相关知识。

 

 

本期关键字

  • Window
  • PhoneWindow
  • WindowManager
  • WindowManagerImpl
  • WindowManagerGlobal
  • RootViewImpl
  • DecorView
  • Dialog
  • PopWindow
  • Toast

学习目标

  • 了解Android中Activity界面显示的流程,涉及到的关键类,以及关键流程
  • 解决在开发中经常遇到的问题,并在源码的角度弄清楚其原因
  • 了解Framework层与Window相关的一些概念和细节

写作方式

老样子,咱们还是和上次一样,采用一问一答的方式进行学习,毕竟“带着问题学习”才是比较高效的学习方式。

进入正题

话说,在上次的文章中,我们解析到了从手机开机第一个zygote进程开启,到App的第一个Activity的onCreate()结束,那么我们这里就接着上次留下的茬,从第一个Activity的onCreate()开始说起。

onCreate()中的setContentView()到底做了什么?为什么不能在setContentView()之后设置某些Window属性标志?

一个最简单的onCreate()如下:

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

通过上面几行简单的代码,我们的App就可以显示在activity_main.xml文件中设计的界面了,那么这一切到底是怎么做到的呢?

我们跟踪一下源码,然后就在Activity的源码中找到了3个setContentView()的重载函数:

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

    public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

我们上面用到的就是第一个方法。虽然setContentView()的重载函数有3种,但是我们可以发现,内部做的事情都是基本一样的。首先是调用getWindow()获取到一个对象,然后调用了这个对象的相关方法。

咱们先来看一下,getWindow()到底获取到了什么对象。

private Window mWindow;

public Window getWindow() {
        return mWindow;
    }
  • 1
  • 2
  • 3
  • 4
  • 5

喔,原来是一个Window对象,你现在可能不知道Window到底是个什么玩意,但是没关系,你只要能猜到它肯定和咱们的界面显示有关系就得了,毕竟叫“Window”么,Windows系统的桌面不是叫“Windows”桌面么,差不多的东西,反正是用来显示界面的就得了。

那么initWindowDecorActionBar()函数是做什么的呢?

写了这么多程序,看名字也应该能猜出八九不离十了,init是初始化,Window是窗口,Decor是装饰,ActionBar就更不用说了,所以这个方法应该就是”初始化装饰在窗口上的ActionBar”,来,咱们看一下代码实现:

/**
     * Creates a new ActionBar, locates the inflated ActionBarView,
     * initializes the ActionBar with the view, and sets mActionBar.
     */
    private void initWindowDecorActionBar() {
        Window window = getWindow();

        // Initializing the window decor can change window feature flags.
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

哟,没想到这里第一行代码就又调用了getWindow(),接着往下调用了window.getDecorView(),从注释中我们知道,在调用这个方法之后,Window的特征标志就被初始化了,还记得如何让Activity全屏吗?

@Override

    public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

    requestWindowFeature(Window.FEATURE_NO_TITLE); 
    getWindow().setFlags(WindowManager.LayoutParams.FILL_PARENT,                  WindowManager.LayoutParams.FILL_PARENT);

    setContentView(R.layout.activity_main);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

而且这两行代码必须在setContentView()之前调用,知道为啥了吧?因为在这里就把Window的相关特征标志给初始化了,在setContentView()之后调用就不起作用了!

如果你还不确定的话,我们可以再看下window.getDecorView()的部分注释

 /**
     * Note that calling this function for the first time "locks in"
     * various window characteristics as described in
     * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
     */
    public abstract View getDecorView();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

“注意,这个方法第一次调用的时候,会锁定在setContentView()中描述的各种Window特征” 
所以说,这也同样解释了为什么在setContentView()之后设置Window的一些特征标志,会不起作用。如果以后遇到类似问题,可以往这方面想一下。

Activity中的findViewById()本质上是在做什么?

在上一个问题里面,咱们提到了一个很重要的类——Window,下面先简单看一下这个类的几个方法:

public abstract class Window {

    public abstract void setContentView(int layoutResID);

    public abstract void setContentView(View view);

    public abstract void setContentView(View view, ViewGroup.LayoutParams params);

    public View findViewById(int id) {
        return getDecorView().findViewById(id);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

哇塞,有个好眼熟的方法,findViewById()!

是的,你每次在Activity中用的这个方法,其实间接调用了Window类里面的方法!

 public View findViewById(int id) {
        return getWindow().findViewById(id);
    }
  • 1
  • 2
  • 3

不过,findViewById()的最终实现是在View及其子类里面的,所以getDecorView()获取到的肯定是一个View对象或者是View的子类对象:

public abstract View getDecorView();
  • 1

Activity、Window中的findViewById()最终调用的,其实是View的findViewById()。

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {

    public final View findViewById(int id) {
         if (id < 0) {
                return null;
            }
            return findViewTraversal(id);
        }

        protected View findViewTraversal(int id) {
            if (id == mID) {
                return this;
            }
            return null;
        }   
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

但是,很显然,最终调用的肯定不是View类里面的findViewTraversal(),因为这个方法只会返回自身。 
而且,findViewById()是final修饰的,不可被重写,所以说,肯定是调用的被子类重写的findViewTraversal(),再联想到,我们的界面上有很多的View,那么既能作为View的容器,又是View的子类的类是什么呢?很显然,是ViewGroup!

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    @Override
    protected View findViewTraversal(int id) {
        if (id == mID) {
            return this;
        }

        final View[] where = mChildren;
        final int len = mChildrenCount;

        for (int i = 0; i < len; i++) {
            View v = where[i];

            if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
                v = v.findViewById(id);

                if (v != null) {
                    return v;
                }
            }
        }

        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

所以说,在onCreate()中调用findViewById()对控件进行绑定的操作,实质上是通过在某个View中查找子View实现的,这里你先记住,这个View叫做DecorView,而且它位于用户窗口的最下面一层。

Window和PhoneWindow是什么关系?WindowManager是做什么的?

话说,咱们前面介绍Window的时候,只是简单的介绍了下findViewById(),还没有详细的介绍下这个类,下面咱们一起学习一下。

前面提到过,Window是一个抽象类,抽象类肯定是不能实例化的,所以咱们需要使用的是它的实现类,Window的实现类有哪些呢?咱们从Dash中看下Window类的文档

技术分享

Window只有一个实现类,就是PhoneWindow!所以说这里扯出了PhoneWindow这个类。

而且文档还说,这个类的一个实例,也就是PhoneWindow,应该被添加到Window Manager中,作为顶层的View,所以,这里又扯出了一个WindowManager的概念。

除此之外,还说这个类提供了标准的UI策略,比如背景,标题区域,和默认的按键处理等等,所以说,咱们还知道了Window和PhoneWindow这两个类的作用!

所以说,看文档多重要呀!

OK,现在咱们已经知道了Window和唯一的实现类PhoneWindow,以及他们的作用。而且我们还知道了WindowManager,虽然不知道干嘛的,但是从名字也可以猜出是管理Window的,而且还会把Window添加到里面去,在下面的模块中,我会详细的介绍WindowManager这个类。

Activity中,Window类型的成员变量mWindow是什么时候初始化的?

在每个Activity中都有一个Window类型的对象mWindow,那么是什么时候初始化的呢?

是在attach()的时候。

还记得attach()是什么时候调用的吗?是在ActivityThread.performLaunchActivity()的时候:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

     Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            } catch (Exception e) {
             ...ignore some code...
        }

    try {

        ...ignore some code...

        activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.voiceInteractor);

        ...ignore some code...

    } catch (Exception e) {  }

     return activity;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在attach()里面做了些什么呢?

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {

    private Window mWindow;

    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, IVoiceInteractor voiceInteractor) {

             ...ignore some code...

             //就是在这里实例化了Window对象
              mWindow = PolicyManager.makeNewWindow(this);
              //设置各种回调
            mWindow.setCallback(this);
            mWindow.setOnWindowDismissedCallback(this);
            mWindow.getLayoutInflater().setPrivateFactory(this);

             //这就是传说中的UI线程,也就是ActivityThread所在的,开启了消息循环机制的线程,所以在Actiivty所在线程中使用Handler不需要使用Loop开启消息循环。
             mUiThread = Thread.currentThread();

             ...ignore some code...

            //终于见到了前面提到的WindowManager,可以看到,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());
          }
              mWindowManager = mWindow.getWindowManager();

            }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

attach()是Activity实例化之后,调用的第一个函数,在这个时候,就实例化了Window。那么这个PolicyManager是什么玩意?

mWindow = PolicyManager.makeNewWindow(this);
  • 1

来来来,咱们一起RTFSC(Read The Fucking Source Code)!

public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;
    static {
        // Pull in the actual implementation of the policy at run-time
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        }
    }

    private PolicyManager() {}

    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

“Policy”是“策略”的意思,所以就是一个策略管理器,采用了策略设计模式。而sPolicy是一个IPolicy类型,IPolicy实际上是一个接口

public interface IPolicy {}
  • 1
  • 2

所以说,sPolicy的实际类型是在静态代码块里面,利用反射进行实例化的Policy类型。静态代码块中的代码在类文件加载进类加载器之后就会执行,sPolicy就实现了实例化。

那我们看下在Policy里面实际上是做了什么

public class Policy implements IPolicy {

    //看见PhoneWindow眼熟么?还有DecorView,眼熟么?这就是前面所说的那个位于最下面的View,findViewById()就是在它里面找的
    private static final String[] preload_classes = {
        "com.android.internal.policy.impl.PhoneLayoutInflater",
        "com.android.internal.policy.impl.PhoneWindow",
        "com.android.internal.policy.impl.PhoneWindow$1",
        "com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback",
        "com.android.internal.policy.impl.PhoneWindow$DecorView",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
    };

    //由于性能方面的原因,在当前Policy类加载的时候,会预加载一些特定的类
     static {
           for (String s : preload_classes) {
            try {
                Class.forName(s);
            } catch (ClassNotFoundException ex) {
                Log.e(TAG, "Could not preload class for phone policy: " + s);
            }
        }
    }

    //终于找到PhoneWindow了,我没骗你吧,前面咱们所说的Window终于可以换成PhoneWindow了~
    public Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
        }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

PhoneWindow.setContentView()到底发生了什么?

上面说了这么多,实际上只是追踪到了PhoneWindow.setContentView(),下面看一下到底在这里执行了什么:

@Override
    public void setContentView(int layoutResID) {
         if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {

        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

当我们第一次调用serContentView()的时候,mContentParent是没有进行过初始化的,所以会调用installDecor()。

为什么能确定mContentParent是没有初始化的呢?因为mContentParent就是在installDecor()里面赋值的

private void installDecor() {

     if (mDecor == null) {
            mDecor = generateDecor();
            ...
        }

         if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
          }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在generateDecor()做了什么?返回了一个DecorView对象。

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

还记得前面推断出的,DecorView是一个ViewGroup的结论吗?看下面,DecorView继承自FrameLayout,所以咱们的推论是完全正确的。而且DecorView是PhoneWindow的私有内部类,这两个类关系紧密!

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {}
}
  • 1
  • 2
  • 3

咱们再看一下在对mContentParent赋值的generateLayout(mDecor)做了什么

protected ViewGroup generateLayout(DecorView decor) {

    ...判断并设置了一堆的标志位...

    //这个是我们的界面将要采用的基础布局xml文件的id
    int layoutResource;

    //根据标志位,给layoutResource赋值
     if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } 

    ...我们设置不同的主题以及样式,会采用不同的布局文件...

     else {
         //我们在下面代码验证的时候,就会用到这个布局,记住它哦
            layoutResource = R.layout.screen_simple;
        }

        //要开始更改mDecor啦~
        mDecor.startChanging();
        //将xml文件解析成View对象,至于LayoutInflater是如何将xml解析成View的,咱们后面再说
        View in = mLayoutInflater.inflate(layoutResource, null);
        //decor和mDecor实际上是同一个对象,一个是形参,一个是成员变量
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;
     //这里的常量ID_ANDROID_CONTENT就是 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
     //而且,由于是直接执行的findViewById(),所以本质上还是调用的mDecor.findViewById()。而在上面的decor.addView()执行之前,decor里面是空白的,所以我们可以断定,layoutResource所指向的xml布局文件内部,一定存在一个叫做“content”的ViewGroup
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn‘t find content container view");
        }

        ......

        mDecor.finishChanging();
        //最后把id为content的一个ViewGroup返回了
        return contentParent;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

当上的代码执行之后,mDecor和mContentParent就初始化了,往下就会执行下面的代码,利用LayoutInflater把咱们传进来的layoutResID转化成View对象,然后添加到id为content的mContentParent中

mLayoutInflater.inflate(layoutResID, mContentParent);
  • 1

所以到目前为止,咱们已经知道了以下几个事实,咱们总结一下:

  • DecorView是PhoneWindow的内部类,继承自FrameLayout,是最底层的界面
  • PhoneWindow是Window的唯一子类,他们的作用就是提供标准UI,标题,背景和按键操作
  • 在DecorView中会根据用户选择的不同标志,选择不同的xml文件,并且这些布局会被添加到DecorView中
  • 在DecorView中,一定存在一个叫做“content”的ViewGroup,而且我们在xml文件中声明的布局文件,会被添加进去

既然是事实,那么怎么才能验证一下呢?

如何验证上一个问题

首先,说明一下运行条件:

 //主题
name="AppTheme" parent="@android:style/Theme.Holo.Light.NoActionBar"

//编译版本
android {
    compileSdkVersion 19
    buildToolsVersion ‘19.1.0‘

    defaultConfig {
        applicationId "com.socks.uitestapp"
        minSdkVersion 15
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
}

dependencies {
    compile fileTree(include: [‘*.jar‘], dir: ‘libs‘)
    compile ‘com.android.support:appcompat-v7:19.1.0‘
}

//Activity代码
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:text="Hello World!"
    android:textSize="20sp" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

OK,咱们的软件已经准备好了,采用的是最简单的布局,界面效果如下:

技术分享

下面用Hierarchy看一下树状结构:

技术分享

第一层,就是上面的DecorView,里面有一个线性布局,上面的是ViewStub,下面就是id为content的ViewGroup,是一个FrameLayout。而我们通过setContentView()设置的布局,就是TextView了。

能不能在源码里面找到源文件呢?当然可以,这个布局就是screen_simple.xml

frameworks/base/core/res/res/layout/screen_simple.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

所以,即使你不调用setContentView(),在一个空Activity上面,也是有布局的。而且肯定有一个DecorView,一个id为content的FrameLayout。

你可以采用下面的方式获取到DecorView,但是你不能获取到一个DecorView实例,只能获取到ViewGroup。

下面贴上这个图,你就可以看明白了(转自 工匠若水)

技术分享

ViewGroup view = (ViewGroup) getWindow().getDecorView();
  • 1

我们通过setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?

有开发经验的朋友应该知道,我们的界面元素在onResume()之后才对用户是可见的,这是为啥呢?

那我们就追踪一下,onResume()是什么时候调用的,然后看看做了什么操作就Ok了。

这一下,我们又要从ActivityThread开始说起了,不熟悉的快去看前一篇文章《Activity启动过程全解析》](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287)。

话说,前文说到,我们想要开启一个Activity的时候,ActivityThread的handleLaunchActivity()会在Handler中被调用

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);
                    }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

所以说,ActivityThread.handleLaunchActivity执行完之后,Activity的生命周期已经执行了4个(onCreate、onStart()、onResume、onPause())。

下面咱们重点看下handleResumeActivity()做了什么

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;
                //这里记住这个WindowManager.LayoutParams的type为TYPE_BASE_APPLICATION,后面介绍Window的时候会见到
                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) {
                     //在这里,执行了重要的操作!
                     if (r.activity.mVisibleFromClient) {
                            r.activity.makeVisible();
                        }
                    }
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

从上面的分析中我们知道,其实在onResume()执行之后,界面还是不可见的,当我们执行了Activity.makeVisible()方法之后,界面才对我们是可见的


if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

OK,其实讲到了这里,关于Activity中的界面显示应该算是告一段落了,我们知道了Activity的生命周期方法的调用时机,还知道了一个最简单的Activity的界面的构成,并了解了Window、PhoneWindow、DecorView、WindowManager的存在。

但是我还是感觉不过瘾,因为上面只是在流程上大体上过了一遍,对于Window、WindowManager的深入了解还不够,所以下面就开始讲解Window、WindowManager等相关类的稍微高级点的知识。

前面看累了的朋友,可以上个厕所,泡个咖啡,休息下继续往下看。

ViewManager、WindowManager、WindowManagerImpl、WindowManagerGlobal到底都是些什么玩意?

WindowManager其实是一个接口,和Window一样,起作用的是它的实现类

public interface WindowManager extends ViewManager {

     //对这个异常熟悉么?当你往已经销毁的Activity中添加Dialog的时候,就会抛这个异常
     public static class BadTokenException extends RuntimeException {
            public BadTokenException() {
        }

        public BadTokenException(String name) {
            super(name);
        }
    }

     //其实WindowManager里面80%的代码是用来描述这个内部静态类的
      public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {
            }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

WindowManager继承自ViewManager这个接口,从注释和方法我们可以知道,这个就是用来描述可以对Activity中的子View进行添加和移除能力的接口。

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

那么我们在使用WindowManager的时候,到底是在使用哪个类呢?

是WindowManagerImpl。

public final class WindowManagerImpl implements WindowManager {}
  • 1

怎么知道的呢?那我们还要从Activity.attach()说起

话说,在attach()里面完成了mWindowManager的初始化

 final void attach(Context context, ActivityThread aThread,
            Inst

以上是关于凯子哥带你学FrameworkActivity界面显示全解析的主要内容,如果未能解决你的问题,请参考以下文章

狗哥带你零基础学C语言

狗哥带你零基础学C语言

狗哥带你零基础学C语言

狗哥带你零基础学C语言

岩哥带你飞——Spring 学习1&重要组件

雷哥带你了解webSocket原理