前几天凯子哥写的Framework层的解析文章《Activity启动过程全解析》,反响还不错,这说明“写让大家都能看懂的Framework解析文章”的思想是基本正确的。
我个人觉得,深入分析的文章必不可少,但是对于更多的Android开发者——即只想做应用层开发,不想了解底层实现细节——来说,“整体上把握,重要环节深入“是更好的学习方式。因为这样既可以有完整的知识体系,又不会在浩瀚的源码世界里迷失兴趣和方向。
所以呢,今天凯子哥又带来一篇文章,接着上一篇的结尾,重点介绍Activity开启之后,Android系统对界面的一些操作及相关知识。
- 本期关键字
- 学习目标
- 写作方式
- 进入正题
- onCreate中的setContentView到底做了什么为什么不能在setContentView之后设置某些Window属性标志
- Activity中的findViewById本质上是在做什么
- Window和PhoneWindow是什么关系WindowManager是做什么的
- Activity中Window类型的成员变量mWindow是什么时候初始化的
- PhoneWindowsetContentView到底发生了什么
- 如何验证上一个问题
- 我们通过setContentView设置的界面为什么在onResume之后才对用户可见呢
- ViewManagerWindowManagerWindowManagerImplWindowManagerGlobal到底都是些什么玩意
- ViewRootImpl是什么有什么作用ViewRootImpl如何与WMS通信
- 从什么时候开始绘制整个Activity的View树的
- Window的类型有几种分别在什么情况下会使用到哪一种
- 为什么使用PopWindow的时候不设置背景就不能触发事件
- 在Activity中使用Dialog的时候为什么有时候会报错Unable to add window token is not valid is your activity running
- 为什么Toast需要由系统统一控制在子线程中为什么不能显示Toast
- 结语
- 参考文章
本期关键字
- 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);
}
通过上面几行简单的代码,我们的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();
}
我们上面用到的就是第一个方法。虽然setContentView()的重载函数有3种,但是我们可以发现,内部做的事情都是基本一样的。首先是调用getWindow()获取到一个对象,然后调用了这个对象的相关方法。
咱们先来看一下,getWindow()到底获取到了什么对象。
private Window mWindow;
public Window getWindow() {
return mWindow;
}
喔,原来是一个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());
}
哟,没想到这里第一行代码就又调用了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);
}
而且这两行代码必须在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();
“注意,这个方法第一次调用的时候,会锁定在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);
}
}
哇塞,有个好眼熟的方法,findViewById()!
是的,你每次在Activity中用的这个方法,其实间接调用了Window类里面的方法!
public View findViewById(int id) {
return getWindow().findViewById(id);
}
不过,findViewById()的最终实现是在View及其子类里面的,所以getDecorView()获取到的肯定是一个View对象或者是View的子类对象:
public abstract View getDecorView();
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;
}
}
但是,很显然,最终调用的肯定不是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;
}
}
所以说,在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;
}
在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();
}
}
attach()是Activity实例化之后,调用的第一个函数,在这个时候,就实例化了Window。那么这个PolicyManager是什么玩意?
mWindow = PolicyManager.makeNewWindow(this);
来来来,咱们一起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);
}
}
“Policy”是“策略”的意思,所以就是一个策略管理器,采用了策略设计模式。而sPolicy是一个IPolicy类型,IPolicy实际上是一个接口
public interface IPol