图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之间的桥梁,负责分发事件、设置窗口属性等。
③ 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是怎么创建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();
}
函数首先调用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;
......
public void setWindowManager( WindowManager wm,
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();
}
}
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);
//...
}
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_INDETERMINATE_PROGRESS))) != 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = com.android.internal.R.layout.screen_progress;
// System.out.println("Progress!");
}
//...
//3 选定了窗口修饰布局文件 ,添加至DecorView对象里,并且指定mcontentParent值
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
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);
}
}
//...
return contentParent;
}
PhoneWindow先检查是否创建了mContentParent(DecorView或其子视图对象) ,第一次创建会先创建DecorView,然后再把activity的UI设置到mContentParent上。至此,我们就看到了PhoneWindow和DecorView的关系:
图7 PhoneWindow与DecorView
创建好了DecorView,并添加上了ContentView后, ActivityThread的成员函数 handleLaunchActivity会通过 handleResumeActivity,调用前面所获得的一个 LocalWindowManager对象的成员函数addView,为当前正在激活的Activity组件的应用程序窗口视图对象关联一个 ViewRoot对象。
public final class ActivityThread {
......
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
......
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
......
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
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;
wm.addView(decor, l);
}
}
......
}
......
}
public class WindowManagerImpl implements WindowManager {
......
public void addView(View view, ViewGroup.LayoutParams params)
{
addView(view, params, false);
}
......
private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
{
......
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams)params;
ViewRoot root;
View panelParentView = null;
synchronized (this) {
// Here's an odd/questionable case: if someone tries to add a
// view multiple times, then we simply bump up a nesting count
// and they need to remove the view the corresponding number of
// times to have it actually removed from the window manager.
// This is useful specifically for the notification manager,
// which can continually add/remove the same view as a
// notification gets updated.
int index = findViewLocked(view, false); //是否已经存在这个view
if (index >= 0) {
if (!nest) {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
root = mRoots[index];
root.mAddNesting++;
// Update layout parameters.
view.setLayoutParams(wparams);
root.setLayoutParams(wparams, true);
return;
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews != null ? mViews.length : 0;
for (int i=0; i if (mRoots[i].mWindow.asBinder() == wparams.token) {
panelParentView = mViews[i]; // 找到这个子视图对象的父视图对象panelParentView
}
}
}
root = new ViewRoot(view.getContext());
root.mAddNesting = 1;
view.setLayoutParams(wparams);
if (mViews == null) {
index = 1;
mViews = new View[1];
mRoots = new ViewRoot[1];
mParams = new WindowManager.LayoutParams[1];
} else {
index = mViews.length + 1;
Object[] old = mViews;
mViews = new View[index];
System.arraycopy(old, 0, mViews, 0, index-1);
old = mRoots;
mRoots = new ViewRoot[index];
System.arraycopy(old, 0, mRoots, 0, index-1);
old = mParams;
mParams = new WindowManager.LayoutParams[index];
System.arraycopy(old, 0, mParams, 0, index-1);
}
index--;
mViews[index] = view;
mRoots[index] = root;
mParams[index] = wparams;
}
// do this last because it fires off messages to start doing things
root.setView(view, wparams, panelParentView); // 将这个View对view象和这个WindowManager.LayoutParams对象,以及变量panelParentView所描述的一个父应用程序窗视图对象,保存到ViewRoot中
}
......
private View[] mViews;
private ViewRoot[] mRoots;
private WindowManager.LayoutParams[] mParams;
//mViews[i]、mRoots[i]和mParams[i]就 形成了对应 的关系
......
}
frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
......
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
......
View mView;
......
final View.AttachInfo mAttachInfo;
......
boolean mAdded;
......
public void setView(View view, WindowManager.LayoutParams attrs,
View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mWindowAttributes.copyFrom(attrs);
......
mAttachInfo.mRootView = view;
.......
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
mAdded = true;
......
requestLayout(); // 请求对应用程序窗口视图的UI作第一次布局
......
try {
res = sWindowSession.add(mWindow, mWindowAttributes,
getHostVisibility(), mAttachInfo.mContentInsets,
mInputChannel); // Binder对象请求WindowManagerService增加一个WindowState对象,用来描述ViewRoot所关联的一个应用程序窗口
} catch (RemoteException e) {
mAdded = false;
mView = null;
......
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
......
}
......
}
}
......
}
至此,我们就分析完成了View的创建过程,总结一下这个流程:
图8 View的创建过程
(2)客户端进程如何与后台的服务通信?
客户端完成窗口和视图的创建后,就会请求与服务端的WindowManagerService建立链接,为其增加一个WindowState对象,用来描述它的窗口状态。接下来,我们就来看客户端和服务端是怎么链接的?
Activity组件---->WindowManagerService服务,是以Activity组件所在的应用程序进程为单位来进行的。当一个应用程序进程在启动第一个Activity组件的时候,它便会打开一个到WindowManagerService服务的连接,这个连接以应用程序进程从WindowManagerService服务处获得一个实现了IWindowSession接口的Session代理对象来标志。
WindowManagerService服务--->Activity组件,连接是以Activity组件为单位来进行的。在应用程序进程这一侧,每一个Activity组件都关联一个实现了IWindow接口的W对象,这个W对象在Activity组件的视图对象创建完成之后,就会通过前面所获得一个Session代理对象来传递给WindowManagerService服务,而WindowManagerService服务接收到这个W对象之后,就会在内部创建一个WindowState对象来描述与该W对象所关联的Activity组件的窗口状态,并且以后就通过这个W对象来控制对应的Activity组件的窗口状态。
图9 activity与windowmanager的通信
重点分析ViewRoot与WMS的通信过程:
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
......
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
......
View mView;
......
final View.AttachInfo mAttachInfo;
......
boolean mAdded;
......
// sWindowSession指向了一个实现了IWindowSession接口的Session代理对象。当应用程序进程启动第一个Activity组件的时候,它就会请求WindowManagerService服务发送一个建立连接的Binder进程间通信请求。WindowManagerService服务接收到这个请求之后,就会在内部创建一个类型为Session的Binder本地对象,并且将这个Binder本地对象返回给应用程序进程,后者于是就会得到一个Session代理对象,并且保存在ViewRoot类的静态成员变量sWindowSession中
mWindow是W类型的变量,也在构造函数中创建,W类实现了IWindow接口,因此,WindowManagerService服务就可以通过它在内部所创建的WindowState对象的成员变量mClient来要求运行在应用程序进程这一侧的Activity组件来配合管理窗口的状态。
public ViewRoot() {
super();
++sInstanceCount;
// Initialize the statics when this class is first instantiated. This is
// done here instead of in the static block because Zygote does not
// allow the spawning of threads.
synchronized (mStaticInit) {
if (!mInitialized) {
try {
sWindowSession = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"))
.openSession(new Binder());
mInitialized = true;
} catch (RemoteException e) {
}
}
}
mWindow = new W(this);
...}
...}
public void
setView(View view, WindowManager.LayoutParams attrs,
View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mWindowAttributes.copyFrom(attrs);
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
mAdded = true;
mAttachInfo.mRootView = view;
int res;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
......
requestLayout(); //
请求对应用程序窗口视图的UI作第一次布局
......
try {
res =
sWindowSession.add(
mWindow, mWindowAttributes,
getHostVisibility(), mAttachInfo.mContentInsets,
mInputChannel);
// Binder对象请求WindowManagerService增加一个WindowState对象,用来描述ViewRoot所关联的一个应用程序窗口
} catch (RemoteException e) {
mAdded = false;
mView = null;
......
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
......
}
......
}
}
......
}
图10 Session对象的创建过程
图11 WindowState对象的创建过程
总结一下创建一个窗口的流程:
1 )客户端在创建窗口时调用getWindowManager获得本地窗口管理对象,并调用其addView函数,在这里为窗口的布局参数赋值,如窗口标题、包名字、token值、flag值等;
2)接着调用本地窗口管理对象的超类CompatModeWrapper的addView函数;
3)CompatModeWrapper是WindowManagerImpl的包装类,采用了桥接模式,达到实现和抽象的分离目的. 因此CompatModeWrapper函数addView转而调用WindowManagerImpl的addView函数;
4) WindowManagerImpl的addView函数首先查看要add的视图是否已经存在,若不存在时实例化一个ViewRootImpl对象,并把view和ViewRootImpl对象及布局参数保存到本地数组中,接着 调用ViewRootImpl对象的setView函数;
5)ViewRootImpl对象的setView函数首先请求进行窗口的第一次布局(调用 requestLayout),然后根据窗口属性是否支持输入实例化一个InputChannel,然后通过服务端远程代理对象sWindowSession向服务端发出新建窗口的请求(调用其addToDisplay接口);最后还要调用assignParent函数设置视图的父视图为ViewRootImpl对象本身;
6)服务端的Session对象收到addwindow的请求,就调用窗口管理服务的addWindow函数来完成新建窗口的任务,完成实际创建窗口的工作。首先实例化一个WindowState对象代表新建的窗口,接着使用客户端窗口的BINDER对象(W对象)为键值把WindowState对象放入mWindowMap中,并根据Z-ORDER放入WindowState的数组中,还要调用WindowState对象的attach函数与Session完成绑定,并在SurfaceSession没有创建时完成创建;然后新建或获得 WindowToken,并使用传进来的客户端窗口布局参数中的token值把WindowToken放入mTokenMap中,建立两者映射关系;最后根据窗口能否能够接收键值更新焦点窗口。
参考资料:
Android 中Activity,Window和View之间的关系
http://lpqsun-126-com.iteye.com/blog/1409989
android的窗口机制分析------UI管理系统
http://blog.csdn.net/windskier/article/details/6957854
Android应用程序窗口(Activity)的视图对象(View)的创建过程分析
http://blog.csdn.net/luoshengyang/article/details/8245546
Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析
http://blog.csdn.net/luoshengyang/article/details/8223770
android的窗口机制分析------ViewRoot类
http://blog.csdn.net/windskier/article/details/6957901
Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析
http://blog.csdn.net/luoshengyang/article/details/8275938
分析了窗口的创建,我们接着看窗口是如何显示出来的。 一个窗口要显示出来,需要 Android应用程序窗口请求SurfaceFlinger服务创建一个绘图表面Surface,接着请求为该绘图表面创建图形缓冲区,而当Android应用程序窗口往这些图形缓冲区填充好UI数据之后,就可以请求SurfaceFlinger服务将它们渲染到硬件帧缓冲区中去。这样我们就能在屏幕上看到具体的UI了。 这个过程 涉及窗口管理服务、VIEW视图系统、SurfaceFlinger本地服务、硬件加速等。
首先,我们要 创建绘图表面Surface,有了“画板”才能把UI绘制出来。绘图表面Surface是由应用程序进程请求SurfaceFlinger服务来创建的,在SurfaceFlinger内部用Layer表示,在客户端和服务端都有其引用 。客户端的 Surface对象负责绘制应用程序窗口的UI,即往应用程序窗口的图形缓冲区填充UI数据,而位于WindowManagerService服务这一侧的Surface对象负责设置应用程序窗口的属性,例如位置、大小等属性。 之所以会有这样的区别,是因为绘制应用程序窗口是独立的,由应用程序进程来完即可,而设置应用程序窗口的属性却需要全局考虑,即需要由WindowManagerService服务来统筹安排,例如,一个应用程序窗口的Z轴坐标大小要考虑它到的窗口类型以及它与系统中的其它窗口的关系。
图12 Surface在应用进程端和服务端的引用关系
图13 Surface创建流程
Android应用程序窗口请求SurfaceFlinger服务创建了一个绘图表面之后,就可以接着请求为该绘图表面创建图形缓冲区,而当Android应用程序窗口往这些图形缓冲区填充好UI数据之后,就可以请求SurfaceFlinger服务将它们渲染到硬件帧缓冲区中去。
创建好Surface后,就可以在上面绘制UI了,这个过程需要 Application调用SurfaceFlinger来完成。
Application与SurfaceFlinger的链接不是通过Binder机制,因为UI数据量太大,所以他们是通过更高效的匿名内存共享(Anonymous Shared Memory)机制来通信。原生的匿名共享内存又是封装成SharedClient来使用的。
图14 匿名内存共享机制
Anonymous Shared Memory结构化后成为SharedClient,用来传递UI数据。在SharedClient内部,每个Surface都有对应的SharedBufferStack—共享缓冲区堆栈。这个堆栈就缓存了APP的UI数据和窗口描述,然后通知SurfaceFlinger在Surface上渲染。
图15 SharedBufferStack的结构示意图
一般情况下,视图的绘制就是在以上的Surface对象创建的画布上进行, 画布由Surface对象的lockCanvas函数经过JNI调用获取绘制输出缓冲区后初始化一个bitmap,并赋值给Surface对象相关的Canvas对象中的mNativeCanvas, 然后返回Canvas对象。以后视图的绘制实际上是在Canvas对象的bitmap上绘制。 但在硬件加速打开的情况下,会 采用HardwareRenderer对象创建的画布进行硬件加速绘制。
从API11开始,硬件加速可以针对:
Application
Activity
Window
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);//不支持在Window上关闭
View
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);//不支持在View上开启
检测是否启用了硬件加速:
View.isHardwareAccelerated()
canvas.isHardwareAccelerated();
视图绘制相关的对象主要由五个对象完成。视图(view)和画布(canvas)对象,视图在具体画布对象上完成各种绘制图形操作,根据不同需求视图绘制可以使用不同的画布对象(当前有三个具体的画布对象,两个硬件加速使用的继承于HardwareCanvas的GLES20Canvas和GLES20RecordingCanvas,不使用硬件加速的CompatibleCanvas)。通过使用抽象接口和桥接设计模式(Bridge模式)视图的绘制操作可以不管具体的具体画布对象是什么,也就是不论是否使用硬件加速与否,统一由一个绘制函数完成当前视图及子视图的递归绘制,只是根据函数参数传进去不同的画布对象。
图17 JAVA对象创建的每种画布与C++ Render对象一一对应,通过JNI 调用相应的Render 对象完成实际绘制工作。
绘制的流程上,开不开硬件加速有一定区别:
1.有硬件加速:invalidate the view hierarchy ------> record and update the display list ------> draw the display list
第一步是把视图的各种绘制函数作为绘制指令(包含操作指令和绘制参数)写到DisplayListRenderer对象的SkWriter32对象中;
第二步是读取SkWriter32对象中保存的绘制指令调用OPENGL相关函数完成实际绘制。
把视图的各种绘制函数翻译成绘制指令保存起来,可以达到重用的目的。在视图绘制过一次且没有或很少发生改变的情况下,在视图重绘时,可以重用原先DisplayListRenderer对象保存的操作指令,不用再按照原先复杂的操作顺序(每一步都需要经过JAVA对象的操作函数通过JNI调用C++本地对象的操作函数)继续重新绘制一遍,而只需对于发生改变的部分按照上面的顺序进行录制及绘制,而对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高视图的显示速度。
2.没有硬件加速:invalidate the view hierarchy ------> draw the view hierarchy
图18
完成Surface的创建后,就可以在画布上绘制各个UI视图了。View的绘制可分为测量、布局、绘制三大步骤。
图19 View的绘制流程
我们知道一个Activity对应的界面ViewTree的顶级视图是DecorView,DecorView又是通过ViewRoot的performTraversals来启动测量、布局和绘制工作。这三步工作又调用了 DecorView的 measure和layout以及ViewRoot的draw函数完成的。绘制从根view开始,然后根据view tree逐渐遍历绘制。ViewGroup先绘制自己再绘制孩子。绘制过程是先measure再layout。Measure是根据每个ViewGroup的大小和child view的大小,计算出每个View的最终大小。Layout在这个基础上,再确定每个child view相对与ViewGroup的位置。ViewGroup要负责安排好每个子View的位置。
Measure:确定大小
因为DecorView实际上是派生自FrameLayout的类,也即一个ViewGroup实例,该ViewGroup内部的ContentViews又是一个ViewGroup实例,依次内嵌View或ViewGroup形成一个View树。所以measure函数的作用是为整个View树计算实际的大小,设置每个View对象的布局大小(“窗口”大小)。实际对应属性就是View中的mMeasuredHeight(高)和mMeasureWidth(宽)。
在View类中measure过程主要涉及三个函数,函数原型分别为:
public final void measure(int widthMeasureSpec, int heightMeasureSpec){
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
// Find rightmost and bottommost child
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
}
...
setMeasuredDimension( resolveSize(maxWidth, widthMeasureSpec),
resolveSize(maxHeight, heightMeasureSpec));
...
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight){
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
}
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
......
public static int resolveSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
result = Math.min(size, specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
......
}
前面两个函数都是final类型的,不能重载,为此在ViewGroup派生的非抽象类中我们必须重载onMeasure函数,实现measure的原理是:假如View还有子View,则measure子View,直到所有的子View完成measure操作之后,再measure自己。ViewGroup中提供的measureChild或measureChildWithMargins就是实现这个功能的。
在具体介绍测量原理之前还是先了解些基础知识,即measure函数的参数由类measureSpec的makeMeasureSpec函数方法生成的一个32位整数,该整数的高两位表示模式(Mode),低30位则是具体的尺寸大小(specSize)。
MeasureSpec有三种模式分别是UNSPECIFIED, EXACTLY和AT_MOST,各表示的意义如下
如果是AT_MOST,specSize代表的是最大可获得的尺寸;
如果是EXACTLY,specSize代表的是精确的尺寸;
如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。
那么对于一个View的上述Mode和specSize值默认是怎么获取的呢,他们是根据View的LayoutParams参数来获取的:
参数为fill_parent/match_parent时,Mode为EXACTLY,specSize为剩余的所有空间;
参数为具体的数值,比如像素值(px或dp),Mode为EXACTLY,specSize为传入的值;
参数为wrap_content,Mode为AT_MOST,specSize运行时决定。
当然也可以不计算直接通过 setMeasuredDimension来设置。
图20 测量Measure的过程
Layout:确定位置
DecorView的Layout()是继承View的,布局过程如下:
View的布局过程
layout操作用于设置视图在屏幕中显示的位置。在view中定义为final类型,要求子类不能修改。layout()函数中有两个基本操作:
(1)setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;
(2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;
参数l、t、r和b分别用来描述当前视图的左上右下四条边与其父视图的左上右下四条边的距离,这样当前视图通过这四个参数就可以知道它在父视图中的位置以及大小。
layout( int l, int t, int r, int b) {//与父view上下左右四条边的距离,确定位置
boolean changed = isLayoutModeOptical( mParent ) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//与上次位置是否发生变化
if (changed || ( mPrivateFlags & PFLAG_LAYOUT_REQUIRED ) == PFLAG_LAYOUT_REQUIRED ) {
onLayout(changed, l, t, r, b);
}
}
setFrame( int left, int top, int right, int bottom) {//设置当前视图的位置以及大小
if ( mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true ;
invalidate(sizeChanged);// 检查当前视图上次请求的UI绘制操作是否已经执行。已经执行,就再次显示一次。
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if (( mViewFlags & VISIBILITY_MASK ) == VISIBLE ) {//当前视图如果可见,需要调用invalidate绘制UI
mPrivateFlags |= PFLAG_DRAWN ;
invalidateParentCaches();
void invalidate( boolean invalidateCache) {//10904
final AttachInfo ai = mAttachInfo ;
final ViewParent p = mParent ;// The parent this view is attached to.DecorView--->ViewRoot
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
if (p != null && ai != null && ai. mHardwareAccelerated ) {
p.invalidateChild( this , null );
return ;
}
}
if (p != null && ai != null ) {
final Rect r = ai. mTmpInvalRect ;
r.set(0, 0, mRight - mLeft , mBottom - mTop );
p.invalidateChild( this , r);
}
}
}
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
public void invalidateChild(View child, Rect dirty) {
checkThread();
......
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
mDirty.union(dirty);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
...
}
public class FrameLayout extends ViewGroup {
......
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
final int parentLeft = mPaddingLeft + mForegroundPaddingLeft;
final int parentRight = right - left - mPaddingRight - mForegroundPaddingRight;
final int parentTop = mPaddingTop + mForegroundPaddingTop;
final int parentBottom = bottom - top - mPaddingBottom - mForegroundPaddingBottom;
mForegroundBoundsChanged = true;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft = parentLeft;
int childTop = parentTop;
final int gravity = lp.gravity;
if (gravity != -1) {
final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (horizontalGravity) {
case Gravity.LEFT:
childLeft = parentLeft + lp.leftMargin;
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = parentRight - width - lp.rightMargin;
break;
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
......
}
FrameLayout类的成员函数onLayout通过一个for循环来布局当前视图的每一个子视图。如果一个子视图child是可见的,那么FrameLayout类的成员函数onLayout就会根据当前视图可以用来显示子视图的区域以及它所设置的gravity属性来得到它在应用程序窗口中的左上角位置(childeLeft,childTop)。再结合它在前面的测量过程中所确定的宽度width和高度height,我们就可以完全地确定它在应用程序窗口中的布局了,即可以调用它的成员函数layout来设置它的位置和大小了。
Draw:绘制显示
ViewRoot类的成员函数draw首先会创建一块画布,接着再在画布上绘制Android应用程序窗口的UI,最后再将画布的内容交给SurfaceFlinger服务来渲染
dirtyDect:脏矩形 需要重绘的一块矩形区域。
// ViewRoot.java里的performTraversals()方法会继续调用draw()方法开始绘图
class ViewRoot{
private void draw(){
Surface surface=mSurface;
//...
View mView ;
mView.draw(canvas) ;
if (!dirty.isEmpty() || mIsAnimating) {
Canvas canvas;
canvas = surface.lockCanvas(dirty);
//....
}
}
//回调View视图里的drawo过程 ,该方法只由ViewGroup类型实现
private void draw(Canvas canvas){
// (1)绘制背景;
//(2)如果要视图显示渐变框,这里会做一些准备工作;
//(3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;
//(4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;
dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
//(5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;
//(6)绘制滚动条;
}
@Override
protected void dispatchDraw(Canvas canvas) {
//其实现方法类似如下:
int childCount = getChildCount() ;
for(int i=0 ;i View child = getChildAt(i) ;
//调用drawChild完成
drawChild(child,canvas) ;
}
}
protected void drawChild(View child,Canvas canvas) {
// ....
//简单的回调View对象的draw()方法,递归就这么产生了。
child.draw(canvas) ;
//.........
}
在上面draw六个操作中,有些是可以优化的。例如,如果当前视图的某一个子视图是不透明的,并且覆盖了当前视图的内容,那么当前视图的背景以及内容就不会绘制了,即不用执行第1和第3个操作。又如,如果当前视图不是处于滑动的状态,那么第2和第5个操作也是不用执行的。
图22 窗口的绘制过程
至此,我们就分析完了窗口的渲染显示过程,它分为测量、布局、绘制。Android应用程序窗口UI首先是使用Skia图形库API来绘制在一块画布上,实际地是绘制在这块画布里面的一个图形缓冲区中,这个图形缓冲区最终会被交给SurfaceFlinger服务,而SurfaceFlinger服务再使用OpenGL图形库API来将这个图形缓冲区渲染到硬件帧缓冲区中。
参考资料:
第六篇 ANDROID窗口系统机制之显示机制
http://blog.csdn.net/melody157398/article/details/8190992
Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析
http://blog.csdn.net/luoshengyang/article/details/8372924
Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析
http://blog.csdn.net/luoshengyang/article/details/8303098
Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析
http://blog.csdn.net/luoshengyang/article/details/7932268
Android View绘制流程
http://www.cnblogs.com/franksunny/archive/2012/04/20/2459738.html
Android 深入研究Layout机制(一)
http://www.eoeandroid.com/forum.php?mod=viewthread&tid=93640
3.窗口管理
前面分析了单个应用的窗口创建和显示,但在android系统运行中,需要管理一系列的应用窗口,这些窗口各不相同,而且相互影响,因此窗口的管理想必不简单。这里就对后台的窗口管理者—— WindowManagerService做简要介绍。
对于WindowManagerService服务来说,这并不意味着它每次只需要管理一个Activity窗口,例如,在两个Activity窗口的切换过程中,前后两个Activity窗口实际上都是可见的。即使在只有一个Activity窗口是可见的时候,WindowManagerService服务仍然需要同时管理着多个窗口,这是因为可见的Activity窗口可能还会被设置了壁纸窗口(Wallpaper Winodw)或者弹出了子窗口(Sub Window),以及可能会出现状态栏(Status Bar)以及输入法窗口(Input Method Window).
图23 WMS的基本功能
WMS是由System Server启动的系统服务。在4.2以上版本中,WMS是运行在SystemServer的wmHanlderThread中,其他Service也可以往这个线程投递事件。WMS内容比较复杂,有关详细分析可参考一下资料:
参考资料:
Android窗口管理服务WindowManagerService的简要介绍和学习计划
http://blog.csdn.net/luoshengyang/article/details/8462738