图1 打开一个android手机应用界面
把一个android手机拿在手里,点开Google play,滑动显示侧边栏,点击能打开新界面,这是我们十分熟悉的简单操作。
但如果要问,手机为什么会显示这样的画面?点击或滑动为什么会有相应效果?恐怕就不那么简单了。
随便想一想,这也涉及到了屏幕、内存、cpu、gpu、framework层的管理,app层的管理等方面。
这里我们重点关注窗口的管理、绘制以及对手势事件的处理,底层渲染和输入监听以及屏幕硬件等部分略过。
1.窗口创建
图1 Activity窗口及其子窗口、壁纸窗口、输入法窗口和状态栏的位置结构
android的窗口有不同的类型,可分为:
Application Window: 应用程序的窗口,如activity的窗口。
System Window:系统窗口,如状态栏、壁纸、输入法等。
Sub Window: 子窗口,如对话框、Menu菜单等。
由于窗口不仅涉及到应用内的界面切换,还涉及到app间的切换管理。所以,android设计的窗口管理分为前台应用进程的
客户端和后台全局的
服务端,客户端和服务端通过Binder机制实现高效的IPC通信。
图2 android窗口管理示意图
(1)客户端进程是如何管理窗口的?
我们首先分析一下
窗口都有哪些组成部分。打开eclipse,利用工具Hierachy View,可以看到类似图3的ViewTree.
图3 android基本的decorview viewtree(带actionbar的viewtree类似)
这里可以清晰的看到,我们在activity里setContentView的layout并不是界面全部,而只是id/content的FrameLayout的子视图。那一个窗口还包括哪些呢?请看下图:
图4 窗口的基本结构
①
PhoneWindow:Android中的最基本的窗口系统,每个activity都会创建一个PhoneWindow,是Activity和整个View系统交互的接口。
② DecorView:每个PhoneWindow包含一个DecorView,DecorView继承自FrameLayout,是当前Activity所有View的祖先,管理system layout和contentView,activity中的findViewById就是通过decorview查找的。它还是PhoneWindow与ViewRoot之间的桥梁,负责分发事件、设置窗口属性等。
② DecorView:每个PhoneWindow包含一个DecorView,DecorView继承自FrameLayout,是当前Activity所有View的祖先,管理system layout和contentView,activity中的findViewById就是通过decorview查找的。它还是PhoneWindow与ViewRoot之间的桥梁,负责分发事件、设置窗口属性等。
③
System Layout:DecorView有一个直接的子View,我们称之为System Layout,这个View是从系统的Layout.xml中解析出的,它包含当前UI的风格,如是否带title、是否带process bar等。我们将这个System Layout添加到DecorView中,目前android提供了8种预设风格的System Layout。(如图5)
图5 sytem layout与decorview关系
④
Content Parent:这个ViewGroup才是我们设置的ContentView的父视图,对应的是System Layout中的id为”content”的一个FrameLayout。
⑤ Activity Layout:这才是我们设置的Activity组件的UI-ContentView。
⑤ Activity Layout:这才是我们设置的Activity组件的UI-ContentView。
摸清楚了窗口的结构,我们再来看看
他们是怎么组织起来的。
首先,
Activity是怎么创建Window的呢?我们知道每个Activity组件都关联了一个Window,但不是所有的窗口都会有Window类,如输入法窗口就没有Window类修饰视图。Window是一个具有交互功能视图的抽象,PhoneWindow是其的唯一实现,表示一个应用窗口,持有一个DecorView。PhoneWindow对象是从Activity类的成员函数attach中创建的。
图6 activity创建window的过程
attach函数是个重要的函数!ActivityThread创建了activity和ContextImpl之后,就调用activity的attach函数进行真正的初始化。我们具体看下这个函数的有关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) {
attachBaseContext(context);
mWindow = PolicyManager.makeNewWindow( this);
mWindow.setCallback( this);
...
if (info. softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED ) {
mWindow.setSoftInputMode(info. softInputMode);
}
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE ),
mToken, mComponent.flattenToString(),
(info. flags & ActivityInfo. FLAG_HARDWARE_ACCELERATED ) != 0);
...
mWindowManager = mWindow .getWindowManager();
}
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config) {
attachBaseContext(context);
mWindow = PolicyManager.makeNewWindow( this);
mWindow.setCallback( this);
...
if (info. softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED ) {
mWindow.setSoftInputMode(info. softInputMode);
}
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE ),
mToken, mComponent.flattenToString(),
(info. flags & ActivityInfo. FLAG_HARDWARE_ACCELERATED ) != 0);
...
mWindowManager = mWindow .getWindowManager();
}
函数首先调用PolicyManager类的静态成员函数makeNewWindow来创建一个类型为PhoneWindow的应用程序窗口,并且保存在Activity类的成员变量mWindow中。有了这个类型为PhoneWindow的应用程序窗口,函数接下来还会调用它的成员函数setCallback、setSoftInputMode和setWindowManager来设置窗口回调接口、软键盘输入区域的显示模式和本地窗口管理器。
之后,Activity给这个PhoneWindow设置一个窗口管理者WindowManager。WindowManager是一个全局的唯一的服务,它的作用是将窗口添加到后台的WindowManagerService,主要方法有addView(),removeView(),updateViewLayout(),还有一个重要的内部类WindowManager.LayoutParams。我们看
mWindow.setWindowManager()到底做了什么。
public abstract class Window {
......
private WindowManager mWindowManager;
private IBinder mAppToken;
private String mAppName;
......
......
private WindowManager mWindowManager;
private IBinder mAppToken;
private String mAppName;
......
IBinder appToken, String appName) {
mAppToken = appToken;
mAppName = appName;
if (wm == null) {
wm = WindowManagerImpl.getDefault();
}
mWindowManager = new LocalWindowManager(wm);
}
...
}
实际上是它最终创建了一个本地窗口管理器——LocalWindowManager对象。
LocalWindowManager类的构造函数首先将参数wm所描述的一个WindowManagerImpl对象保存它的成员变量mWindowManager中,这样以后就将窗口管理工作交给它来处理。
WindowManagerImpl
是客户端WindowManager管理接口的实现,WindowManagerImpl内部维护一个单例的WindowManagerGlobal对象,WindowManagerImpl通过该对象转发客户端的窗口管理请求。WindowManagerGlobal对象内部维护一个ViewRootImpl实例数组和一个View视图对象数组,每次客户端添加一个视图时都新创建一个ViewRootImpl对象,并把要添加的视图和新创建的ViewRootImpl对象添加到相应数组中,并调用新创建的ViewRootImpl对象的setView函数。
弄清了Window的创建和管理之后,我们再来看看View究竟是怎么创建的。
activity启动过程中,ActivityThread类的成员函数handleLaunchActivity会先调用Activity的onCreate()方法。一般,我们会在这里调用
Activity的setContentView().
class
Activity{
...
public
void
setContentView(
int
layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
void
makeVisible() {
ViewManager wm = getWindowManager();//windowmanager拿着DecorView,建立ViewRoot,并发给后台WMS绘制
wm.addView(
mDecor
, getWindow().getAttributes());
}
public
boolean
dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
Window win = getWindow();
if
(win.superDispatchKeyEvent(event)) {
return
true
;
}
View decor =
mDecor
;
if
(decor ==
null
) decor = win.getDecorView();
return
event.dispatch(
this
, decor !=
null
? decor.getKeyDispatcherState() :
null
,
this
);
}
...
}
原来,Activity的setContentView()方法调用了Phone
W
indow的setContentView()方法:
class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) { //是否是第一次调用setContentView方法, 如果是第一次调用,则mDecor和mContentParent对象都为空
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
public void setContentView(int layoutResID) {
if (mContentParent == null) { //是否是第一次调用setContentView方法, 如果是第一次调用,则mDecor和mContentParent对象都为空
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
private void
installDecor() {
if (mDecor == null) {
//mDecor为空,则创建一个Decor对象
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
//generateLayout()方法会根据窗口的风格修饰,选择对应的修饰布局文件
//并且将id为content(android:id="@+id/content")的FrameLayout赋值给mContentParent
mContentParent = generateLayout(mDecor);
//...
}
if (mDecor == null) {
//mDecor为空,则创建一个Decor对象
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
//generateLayout()方法会根据窗口的风格修饰,选择对应的修饰布局文件
//并且将id为content(android:id="@+id/content")的FrameLayout赋值给mContentParent
mContentParent = generateLayout(mDecor);
//...
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//...1、根据requestFreature()和Activity节点的android:theme="" 设置好 features值
//2 根据设定好的 features值,即特定风格属性,选择不同的窗口修饰布局文件
int layoutResource; //窗口修饰布局文件
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
layoutResource = com.android.internal.R.layout.dialog_title_icons;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMI
// Apply data from current theme.
//...1、根据requestFreature()和Activity节点的android:theme="" 设置好 features值
//2 根据设定好的 features值,即特定风格属性,选择不同的窗口修饰布局文件
int layoutResource; //窗口修饰布局文件
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
layoutResource = com.android.internal.R.layout.dialog_title_icons;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMI