前几天凯子哥写的Framework层的解析文章《Activity启动过程全解析》,反响还不错,这说明"写让你们都能看懂的Framework解析文章"的思路是基本正确的。
我我的以为,深刻分析的文章必不可少,可是更多的Android开发者——即只想作应用层开发,不想了解底层实现细节——来讲,"总体上把握,重要环节深刻"是更好的学习方式。所以这样既能够有完整的知识体系,又不会在好汉的源码世界里迷失兴趣和方向。
因此呢,今天凯子哥又带来一篇文章,接着上一篇的结尾,终点介绍Activity开启后,Android系统对界面的一些操做及相关知识。java
Windowandroid
PhoneWindowweb
WindowManagerwindows
WindowManagerImpl设计模式
WindowManagerGlobalapp
RootViewImplide
DecorView函数
Dialogoop
PopWindow布局
Toast
了解Android中Activity界面显示的流程,涉及到的关键类,以及关键流程
解决在开发中常常遇到的问题,并在源码的角度弄清其缘由
了解Framework层与Window相关的一些概念和细节
老样子,我们仍是和上次同样,采用一问一答的方式进行学习,毕竟"带着问题学习"才是比较高效的学习方式。
话说,在上次的文章中,咱们解析到了从手机开机第一个zygote进程开启,到App的第一个Activity的onCreate()结束,那么咱们这里就接着上次留下的茬,从第一个Activity的onCreate()开始提及。
一个最简单的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的一些特征标志,会不起做用。若是之后遇到相似问题,能够往这方面想一下。
在上一个问题里面,我们提到了一个很重要的类——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的时候,只是简单的介绍了下findViewById(),尚未详细的介绍下这个类,下面我们一块儿学习一下。
前面提到过,Window是一个抽象类,抽象类确定是不能实例化的,因此我们须要使用的是它的实现类,Window的实现类有哪些呢?我们从Dash中看下Window类的文档
在每一个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 IPolicy {}
因此说,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);
}
}
上面说了这么多,实际上只是追踪到了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();
}
}
当咱们第一次调用setContentView()的时候,mContentParent是没有进行初始化的,因此会调用installDecor()。
为何能肯定mContentParent是没有初始化的呢?由于mContentParent就是在installDecor()里面赋值的
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
在generateDecor()作了什么?返回了一个DecorView对象。
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
还记得前面推断出的,DecorView是一个ViewGroup的结论吗?看下面,DecorView继承自FrameLayout,因此我们的推论是彻底正确的。并且DecorView是PhoneWindow的私有内部类,这两个类关系紧密!
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {}
}
我们再看一下在对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;
}
当上面的代码执行以后,mDecor和mContentParent就初始化了,往下就会执行下面的代码。利用LayoutInflater把我们传进来的layoutResID转化成View对象,而后添加到id为content的mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
因此到目前为止,我们已经知道了如下几个事实,我们总结一下:
DecorView是PhoneWindow的内部类,继承自FrameLayout,是最底层的界面
PhoneWindow是Window的惟一子类,他们的做用就是提供标准UI,标题,背景和按键操做
在DecorView中会根据用户选择的不一样标志,选择不一样的xml文件,而且这些布局会被添加大DecorView中
在DecorView中,必定存在一个叫作"content"的ViewGroup,并且咱们在xml文件中声明的布局文件,会被添加进去
既然是事实,那么怎么样才能验证一下呢?
我们下篇再说~
做者:凯子哥
连接:http://blog.csdn.net/zhaokaiqiang1992