3 Studying《深入理解Android卷(邓凡平)》2
目录
5 深入理解Surface系统
1 概述
2 一个Activity的显示
2.1 Activity的创建
2.2 Activity的UI绘制
2.3 Activity总结
3 初识Surface
3.1 和Surface有关的流程总结
3.2 Surface之乾坤大挪移
3.3 分析乾坤大挪移的JNI层
3.4 Surface和画图
3.5 初识Surface总结
4 深入分析Surface
4.1 与Surface相关的基础知识介绍
4.2 SurfaceComposerClient的分析
4.3 SurfaceControl的分析
4.4 writeToParcel和Surface对象的创建
4.5 lockCanvas和unlockCanvasAndPost的分析
4.6 GraphicBuffer的介绍
4.7 深入分析Surface总结
5 SurfaceFlinger的分析
5.1 SurfaceFlinger的诞生
5.2 SF工作线程的分析
5.3 Transaction的分析
5.4 SurfaceFlinger的总结
6 拓展思考
6.1 Surface系统的CB对象分析
6.2 ViewRoot的你问我答
6.3 LayerBuffer的分析
7 本章小结
6 深入理解PackageManagerService
1 概述
2 初识PackageManagerService
3 PKMS的main函数分析
3.1 构造函数分析之前期准备工作
3.2 构造函数分析之扫描Package
3.3 构造函数分析之扫尾工作
3.4 PKMS构造函数总结
4 APK Installation分析
4.1 adb install分析
4.2 pm分析
4.3 installPackageWithVerification函数分析
4.4 APK 安装流程总结
5 queryIntentActivities分析
5.1 Intent及IntentFilter介绍
5.2 Activity信息的管理
5.3 Intent 匹配查询分析
6 installd介绍
6.1 installd概貌
6.2 dexOpt命令分析
6.3 movefiles命令分析
6.4 doFreeCache
7 本章学习指导
5 深入理解Surface系统
原文链接:https://blog.csdn.net/Innost/article/details/47208337
本章主要内容
· 详细分析一个Activity的显示过程。
· 详细分析Surface。
· 详细分析SurfaceFlinger。
1 概述
本章将集中精力打通Surface系统的“任督二脉”,这任督二脉分别是:
· 任脉:应用程序App和Surface的关系。
· 督脉:Surface和SurfaceFlinger之间的关系。
图1 Surface系统的任督二脉
其中,左图是任脉,右图是督脉。
· 先看左图。可以发现,不论是使用Skia绘制二维图像,还是用OpenGL绘制三维图像,最终Application都要和Surface交互。Surface就像是UI的画布,而App则像是在Surface上作画。
· 再看右图。Surface和SurfaceFlinger的关系,很像Audio系统中AudioTrack和AudioFlinger的关系。Surface向SurfaceFlinger提供数据,而SurfaceFlinger则混合数据。
说明:为书写方便起见,后文将SurfaceFlinger简写为SF。
2 一个Activity的显示
Activity是如何完成界面绘制工作的呢?根据前面所讲的知识,应用程序的显示和Surface有关,那么具体到Activity上,它和Surface又是什么关系呢?
本节就来讨论这些问题。首先从Activity的创建说起。
2.1 Activity的创建
ActivityThread类中有一个handleLaunchActivity函数,它就是创建Activity的地方。一起来看这个函数,代码如下所示:
[-->ActivityThread.java]
private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {//1 performLaunchActivity返回一个ActivityActivity a = performLaunchActivity(r, customIntent);if(a != null) {r.createdConfig = new Configuration(mConfiguration);Bundle oldState = r.state;//2 调用handleResumeActivityhandleResumeActivity(r.token, false, r.isForward);}......
}
handleLaunchActivity函数中列出了两个关键点,下面对其分别介绍。
1. 创建Activity
第一个关键函数performLaunchActivity返回一个Activity,这个Activity就是App中的那个Activity(仅考虑App中只有一个Activity的情况),它是怎么创建的呢?其代码如下所示:
[-->ActivityThread.java]
private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) {ActivityInfo aInfo = r.activityInfo;......Activity activity = null;try {java.lang.ClassLoader cl = r.packageInfo.getClassLoader();/*mInstrumentation为Instrumentation类型,源文件为Instrumentation.java。它在newActivity函数中根据Activity的类名通过Java反射机制来创建对应的Activity,这个函数比较复杂,待会我们再分析它。*/activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);r.intent.setExtrasClassLoader(cl);if (r.state != null) {r.state.setClassLoader(cl);}}try {Application app = r.packageInfo.makeApplication(false,mInstrumentation);if (activity != null) {......//下面这个函数会调用Activity的onCreate函数mInstrumentation.callActivityOnCreate(activity, r.state);......}}return activity;
}
好了,performLaunchActivity函数的作用明白了吧?
· 根据类名以Java反射的方法创建一个Activity。
· 调用Activity的onCreate函数,开始Activity的生命周期。
那么,在onCreate函数中,我们一般会做什么呢?在这个函数中,和UI相关的重要工作就是调用setContentView来设置UI的外观。接下去,需要看handleLaunchActivity中第二个关键函数handleResumeActivity。
2. 分析handleResumeActivity
上面已创建好了一个Activity,再来看handleResumeActivity。它的代码如下所示:
[-->ActivityThread.java]
final void handleResumeActivity(IBinder token,boolean clearHide,boolean isForward) {boolean willBeVisible = !a.mStartedActivity;if (r.window == null && !a.mFinished && willBeVisible) {r.window= r.activity.getWindow();//1 获得一个View对象View decor = r.window.getDecorView();decor.setVisibility(View.INVISIBLE);//2 获得ViewManager对象ViewManager wm = a.getWindowManager();......//3 把刚才的View对象加入到ViewManager中wm.addView(decor,l);}......
}
上面有三个关键点。这些关键点似乎已经和UI部分(如View、Window)有联系了。那么这些联系是在什么时候建立的呢?在分析上面代码中的三个关键点之前,请大家想想在前面的过程中,哪些地方会和UI挂上钩呢?
· 答案就在onCreate函数中,Activity一般都在这个函数中通过setContentView设置UI界面。
看来,必须先分析setContentView,才能继续后面的征程。
3. 分析setContentView
[-->Activity.java]
public void setContentView(View view) {// getWindow返回的是什么呢?一起来看看。getWindow().setContentView(view);
}public Window getWindow() {// 返回一个类型为Window的mWindow,它是什么? return mWindow;
}
上面出现了两个和UI有关系的类:View和Window。
Window是一个抽象基类,用于控制顶层窗口的外观和行为。做为顶层窗口它有什么特殊的职能呢?即绘制背景和标题栏、默认的按键处理等。它将作为一个顶层的view加入到Window Manager中。
View的概念就比较简单了,它是一个基本的UI单元,占据屏幕的一块矩形区域,可用于绘制,并能处理事件。
从上面的View和Window的描述,再加上setContentView的代码,我们能想象一下这三者的关系,如图2所示:
图2 Window/View的假想关系图
根据上面的介绍,大家可能会产生两个疑问:
· Window是一个抽象类,它实际的对象到底是什么类型?
· Window Manager究竟是什么?
下面试来解决这两个问题。
(1)Activity的Window
Window是一个抽象类。它实际的对象到底属于什么类型?先回到Activity创建的地方去看看。
activity = mInstrumentation.newActivity(cl,component.getClassName(), r.intent);
代码中调用了Instrumentation的newActivity,再去那里看看。
[-->Instrumentation.java]
public Activity newActivity(Class<?>clazz, Context context, IBinder token, Application application, Intent intent,ActivityInfo info, CharSequencetitle, Activity parent, String id,Object lastNonConfigurationInstance)throws InstantiationException, IllegalAccessException{Activity activity = (Activity)clazz.newInstance();ActivityThread aThread = null;//关键函数attach!!activity.attach(context, aThread, this, token, application, intent, info, title,parent, id, lastNonConfigurationInstance, new Configuration());return activity;
}
[-->Activity.java]
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,Object lastNonConfigurationInstance,HashMap<String,Object> lastNonConfigurationChildInstances,Configuration config) {......//利用PolicyManager来创建Window对象mWindow = PolicyManager.makeNewWindow(this);mWindow.setCallback(this);......//创建WindowManager对象mWindow.setWindowManager(null, mToken, mComponent.flattenToString());if(mParent != null) {mWindow.setContainer(mParent.getWindow());}//保存这个WindowManager对象mWindowManager = mWindow.getWindowManager();mCurrentConfig = config;
}
(2)水面下的冰山——PolicyManager
[-->PolicyManager.java]
public final class PolicyManager {private static final String POLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy";private static final IPolicy sPolicy;static{try {Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);//创建Policy对象sPolicy = (IPolicy)policyClass.newInstance();} catch (ClassNotFoundException ex) {......}private PolicyManager() {}//通过Policy对象的makeNewWindow创建一个Windowpublic static Window makeNewWindow(Context context) {return sPolicy.makeNewWindow(context);}......
}
这里有一个单例的sPolicy对象,它是Policy类型。单例模式。
(3)真正的Window
Policy类型的定义代码如下所示:
[-->Policy.java]
public class Policy implements IPolicy {private static final String TAG = "PhonePolicy";......public PhoneWindow makeNewWindow(Contextcontext) {//makeNewWindow返回的是PhoneWindow对象return new PhoneWindow(context);}......
}
看下之前的调用:
mWindow = PolicyManager.makeNewWindow(this);
...
mWindow.setWindowManager(null, mToken, mComponent.flattenToString());
...
mWindowManager = mWindow.getWindowManager();
返回的Window,原来是一个PhoneWindow对象。它的定义在PhoneWindow.java中。
还剩下个WindowManager。现在就来揭示其真面目。
(4)真正的WindowManager
mWindow.setWindowManager调用的setWindowManager函数,其实是由PhoneWindow的父类Window类来实现的。
[-->Window.java]
//注意,传入的wm值为null
public void setWindowManager(WindowManager wm,IBinder appToken, String appName) { mAppToken = appToken;mAppName = appName;if(wm == null) {//如果wm为空的话,则创建WindowManagerImpl对象wm = WindowManagerImpl.getDefault();}//mWindowManager是一个LocalWindowManagermWindowManager = new LocalWindowManager(wm);
}
LocalWindowManager是在Window中定义的内部类,请看它的构造函数,其定义如下所示:
[-->Window.java::LocalWindowManager定义]
private class LocalWindowManager implements WindowManager {LocalWindowManager(WindowManager wm) {mWindowManager = wm; //还好,只是简单地保存了传入的wm参数mDefaultDisplay = mContext.getResources().getDefaultDisplay(mWindowManager.getDefaultDisplay());}......
如上面代码所示,LocalWindowManager将保存一个WindowManager类型的对象,这个对象的实际类型是WindowManagerImpl。而WindowManagerImpl又是什么呢?来看它的代码,如下所示:
[-->WindowManagerImpl.java]
public class WindowManagerImpl implements WindowManager {......public static WindowManagerImpl getDefault(){return mWindowManager; //返回的就是WindowManagerImpl对象}private static WindowManagerImpl mWindowManager= new WindowManagerImpl();
}
看到这里,是否有点头晕眼花?
图3 Window和WindowManger的家族图谱
根据上图,可得出以下结论:
· Activity的mWindow成员变量的真实类型是PhoneWindow,而mWindowManager成员变量的真实类型是LocalWindowManager。
· LocalWindowManager和WindowManagerImpl都实现了WindowManager接口。这里采用的是Proxy模式,表明LocalWindowManager将把它的工作委托WindowManagerImpl来完成。
(5)setContentView的总结
了解了上述知识后,重新回到setContentView函数。
[-->Activity.java]
public void setContentView(View view) {getWindow().setContentView(view);//getWindow返回的是PhoneWindow
}
一起来看PhoneWindow的setContentView函数,代码如下所示:
[-->PhoneWindow]
public void setContentView(View view) {//调用另一个setContentViewsetContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));
}public void setContentView(View view, ViewGroup.LayoutParams params) {//mContentParent为ViewGroup类型,它的初值为nullif(mContentParent == null) {installDecor();}else {mContentParent.removeAllViews();}//把view加入到ViewGroup中mContentParent.addView(view, params);......
}
mContentParent是一个ViewGroup类型,它从View中派生,所以也是一个UI单元。从它名字中“Group”所表达的意思分析,它还可以包含其他的View元素。这又是什么意思呢?也就是说,在绘制一个ViewGroup时,它不仅需要把自己的样子画出来,还需要把它包含的View元素的样子也画出来。读者可将它想象成一个容器,容器中的元素就是View。
这里采用的是23种设计模式中的Composite模式,它是UI编程中常用的模式之一。
再来看installDecor函数,其代码如下所示:
[-->PhoneWindow.java]
private void installDecor() {if (mDecor == null) {//创建mDecor,它为DecorView类型,从FrameLayout派生mDecor= generateDecor();......}if(mContentParent == null) {//得到这个mContentParentmContentParent = generateLayout(mDecor);//创建标题栏mTitleView= (TextView)findViewById(com.android.internal.R.id.title);}......
}
generateLayout函数的输入参数为mDecor,输出为mContentParent,代码如下所示:
[-->PhoneWindow]
protected ViewGroup generateLayout(DecorView decor) {......// 加入标题栏decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));/*ID_ANDROID_CONTENT的值为”com.android.internal.R.id.content”这个contentParent由findViewById返回,实际上就是mDecorView的一部分。*/ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);......return contentParent;
}
根据图2,可绘制更细致的图4:
图4 一个Activity中的UI组件
可从上图中看出,在Activity的onCreate函数中,通过setContentView设置的View,其实只是DecorView的子View。DecorView还处理了标题栏显示等一系列的工作。
注意,这里使用了设计模式中的Decorator(装饰)模式,它也是UI编程中常用的模式之一。
4. 重回handleResumeActivity
[-->ActivityThread.java]
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {boolean willBeVisible = !a.mStartedActivity;......if (r.window == null && !a.mFinished&& willBeVisible) {r.window= r.activity.getWindow();//1 获得一个View对象。现在知道这个view就是DecorViewViewdecor = r.window.getDecorView();decor.setVisibility(View.INVISIBLE);//2 获得ViewManager对象,这个wm就是LocalWindowManagerViewManagerwm = a.getWindowManager();WindowManager.LayoutParamsl = r.window.getAttributes();a.mDecor= decor;l.type =WindowManager.LayoutParams.TYPE_BASE_APPLICATION;if(a.mVisibleFromClient) {a.mWindowAdded= true;//3 把刚才的decor对象加入到ViewManager中wm.addView(decor,l);}......}
}
View、ViewManager等和setContentView有关,所以我们之前转而去分析setContentView了。
下面继续从addView的分析开始。来看这个addView函数:
[-->Window.java]
public final void addView(View view,ViewGroup.LayoutParams params) {WindowManager.LayoutParams wp =(WindowManager.LayoutParams)params;CharSequence curTitle = wp.getTitle();......//还记得前面提到过的Proxy模式吗?mWindowManager对象实际上是WindowManagerImpl类型mWindowManager.addView(view, params);
}
[-->WindowManagerImpl.java]
private void addView(View view,ViewGroup.LayoutParams params, boolean nest) {ViewRoot root; synchronized(this) {//1 创建ViewRootroot =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[index]= view;mRoots[index]= root;//保存这个rootmParams[index]= wparams;//2 setView,其中view是刚才我们介绍的DecorViewroot.setView(view, wparams, panelParentView);}
}
ViewRoot,主角终于出场了!这里,列出了ViewRoot的两个重要关键点。
(1)ViewRoot是什么?
它的确和View有关系,因为它实现了ViewParent接口。它和Android基本绘图单元中的View却不太一样,比如:ViewParent不处理绘画,因为它没有onDraw函数。
如上所述,ViewParent和绘画没有关系,那么,它的作用是什么?先来看它的代码,如下所示:
[-->ViewRoot.java]
public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks //从Handler类派生
{private final Surface mSurface = new Surface(); //这里创建了一个Surface对象final W mWindow; //这个是什么?View mView;
}
上面这段代码传达出了一些重要信息:
· ViewRoot继承了Handler类,看来它能处理消息。ViewRoot果真重写了handleMessage函数。稍侯再来看它。
· ViewRoot有一个成员变量叫mSurface,它是Surface类型。
· ViewRoot还有一个W类型的mWindow和一个View类型的mView变量。
其中,W是ViewRoot定义的一个静态内部类:
static class W extends IWindow.Stub
这个类将参与Binder的通信,以后对此再做讲解,先来介绍Surface类。
(2)神笔马良乎?(画笔)
Surface类是什么?在回答此问题之前,先来考虑这样一个问题:
· 前文介绍的View、DecorView等都是UI单元,这些UI单元的绘画工作都在onDraw函数中完成。如果把onDraw想象成画图过程,那么画布是什么?
这块画布就是Surface。
SDK文档对Surface类的说明是:Handle on to a raw buffer thatis being managed by the screen compositor。这句话的意思是:
· 有一块Raw buffer,至于是内存还是显存,不必管它。
· Surface操作这块Raw buffer。
· Screen compositor(其实就是SurfaceFlinger)管理这块Raw buffer。
Surface和SF、ViewRoot有什么关系呢?
图5 马良的神笔工作原理
结合之前所讲的知识,图8-5清晰地传达了如下几条信息:
· ViewRoot有一个成员变量mSurface,它是Surface类型,它和一块Raw Buffer有关联。
· ViewRoot是一个ViewParent,它的子View的绘画操作,是在画布Surface上展开的。
· Surface和SurfaceFlinger有交互,这非常类似AudioTrack和AudioFlinger之间的交互。
既然本章题目为“深入理解Surface系统”,那么就需要重点关注Surface和SurfaceFlinger间的关系。建立这个关系需ViewRoot的参与,所以应先来分析ViewRoot的创建和它的setView函数。
(3)ViewRoot的创建和对setView的分析
来分析ViewRoot的构造。
[-->ViewRoot.java]
public ViewRoot(Context context) {super();...// getWindowSession?我们进去看看getWindowSession(context.getMainLooper());...//ViewRoot的mWindow是一个W类型,注意它不是Window类型,而是IWindow类型mWindow= new W(this, context);
}
getWindowsession函数,将建立Activity的ViewRoot和WindowManagerService的关系。代码如下所示:
[-->ViewRoot.java]
public static IWindowSession getWindowSession(Looper mainLooper) {synchronized (mStaticInit) {if (!mInitialized) {try {InputMethodManagerimm = InputMethodManager.getInstance(mainLooper);//下面这个函数先得到WindowManagerService的Binder代理,然后调用它的openSessionsWindowSession = IWindowManager.Stub.asInterface(ServiceManager.getService("window")).openSession(imm.getClient(), imm.getInputContext());mInitialized = true;} catch (RemoteException e) {...}}}return sWindowSession;
}
WindowSession?WindowManagerService?要攻克这些难题,应先来回顾一下与Zygote相关的知识:
WindowManagerService(以后简称WMS)由System_Server进程启动,SurfaceFlinger服务也在这个进程中。
看来,Activity的显示还不单纯是它自己的事,还需要和WMS建立联系才行。继续看setView的处理。
[-->ViewRoot.java]
//第一个参数view是DecorView
public void setView(View view, WindowManager.LayoutParamsattrs, View panelParentView) {......mView= view; //保存这个viewsynchronized (this) {requestLayout(); //待会先看看这个。try {//调用IWindowSession的add函数,第一个参数是mWindowres =sWindowSession.add(mWindow, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets);}......}
}
ViewRoot的setView函数做了三件事:
· 保存传入的view参数为mView,这个mView指向PhoneWindow的DecorView。
· 调用requestLayout。
· 调用IWindowSession的add函数,这是一个跨进程的Binder通信,第一个参数是mWindow,它是W类型,从IWindow.stub派生。
先来看这个requestLayout函数,它非常简单,就是往handler中发送了一个消息。注意,ViewRoot是从Handler派生的,所以这个消息最后会由ViewRoot自己处理,代码如下所示:
[-->ViewRoot.java]
public void requestLayout() {checkThread();mLayoutRequested = true;scheduleTraversals();
}public void scheduleTraversals() {if(!mTraversalScheduled) {mTraversalScheduled = true;sendEmptyMessage(DO_TRAVERSAL); //发送DO_TRAVERSAL消息}
}
好,requestLayout分析完毕。
从上面的代码中可发现,ViewRoot和远端进程SystemServer的WMS有交互,先来总结一下它和WMS的交互流程:
· ViewRoot调用openSession,得到一个IWindowSession对象。
· 调用WindowSession对象的add函数(setView),把一个W类型的mWindow对象做为参数传入。
5. ViewRoot和WMS的关系
下面总结了ViewRoot和WMS的交互流程,其中一共有两个跨进程的调用。一起去看。
(1)调用流程分析
WMS的代码在WindowManagerService.java中:
[-->WindowManagerService.java]
public IWindowSession openSession(IInputMethodClient client, IInputContextinputContext) {......return new Session(client, inputContext);
}
Session是WMS定义的内部类。它支持Binder通信,并且属于Bn端,即响应请求的服务端。
再来看它的add函数。代码如下所示:
[-->WindowManagerService.java::Session]
public int add(IWindow window,WindowManager.LayoutParams attrs, int viewVisibility, Rect outContentInsets) {//调用外部类对象的addWindow,也就是WMS的addWindowreturn addWindow(this, window, attrs, viewVisibility, outContentInsets);
}
[-->WindowManagerService.java]
public int addWindow(Session session, IWindowclient, WindowManager.LayoutParams attrs, int viewVisibility, Rect outContentInsets) {......//创建一个WindowStatewin = new WindowState(session, client, token,attachedWindow, attrs,viewVisibility);......//调用attach函数win.attach();......return res;
}
WindowState类也是在WMS中定义的内部类,直接看它的attach函数,代码如下所示:
[-->WMS.java::WindowState]
void attach() {//mSession就是Session对象,调用它的windowAddedLocked函数mSession.windowAddedLocked();
}
[-->WMS.java::Session]
void windowAddedLocked() {if(mSurfaceSession == null) {......//创建一个SurfaceSession对象mSurfaceSession= new SurfaceSession();......}mNumWindow++;
}
这里出现了另外一个重要的对象SurfaceSession。在讲解它之前,急需理清一下现有的知识点,否则可能会头晕。
(2)ViewRoot和WMS的关系梳理
ViewRoot和WMS之间的关系,可用图6来表示:
图6 ViewRoot和WMS的关系
总结一下图6中的知识点:
· ViewRoot通过IWindowSession和WMS进程进行跨进程通信。IWindowSession定义在IWindowSession.aidl文件中。这个文件在编译时由aidl工具处理,最后会生成类似于Native Binder中Bn端和Bp端的代码,后文会介绍它。
· ViewRoot内部有一个W类型的对象,它也是一个基于Binder通信的类,W是IWindow的Bn端,用于响应请求。IWindow定义在另一个aidl文件IWindow.aidl中。
为什么需要这两个特殊的类呢?简单介绍一下:
首先,来看IWindowSession.aidl对自己的描述:
· System private per-application interface to the window manager
也就是说每个App进程都会和WMS建立一个IWindowSession会话。这个会话被App进程用于和WMS通信。后面会介绍它的requestLayout函数。
再看对IWindow.adil的描述:
· API back to a client window that the Window Manager uses to informit of interesting things happening
大意是IWindow是WMS用来做事件通知的。每当发生一些事情时,WMS就会把这些事告诉某个IWindow。可以把IWindow想象成一个回调函数。
IWindow的描述表达了什么意思呢?不妨看看它的内容,代码如下所示:
[-->IWindow.aidl定义]
void dispatchKey(in KeyEvent event);
void dispatchPointer(in MotionEvent event, longeventTime, boolean callWhenDone);
void dispatchTrackball(in MotionEvent event,long eventTime, boolean callWhenDone);
明白了?这里的事件指的就是按键、触屏等事件。那么,一个按键事件是如何被分发的呢?下面是它大致的流程:
· WMS所在的SystemServer进程接收到按键事件。
· WMS找到UI位于屏幕顶端的进程所对应的IWindow对象,这是一个Bp端对象。
· 调用这个IWindow对象的dispatchKey。IWindow对象的Bn端位于ViewRoot中,ViewRoot再根据内部View的位置信息找到真正处理这个事件的View,最后调用dispatchKey函数完成按键的处理。
上面介绍的是ViewRoot和WMS的交互,但是我们最关心的Surface还没有正式介绍,在此之前,还是先介绍Activity的流程。
2.2 Activity的UI绘制
ViewRoot的setView函数中,会有一个requestLayout。根据前面的分析可知,它会向ViewRoot发送一个DO_TRAVERSAL消息,接收也是ViewRoot来处理,来看它的handleMessage函数,代码如下所示:
[-->ViewRoot.java]
public void handleMessage(Message msg) {switch (msg.what) {......case DO_TRAVERSAL:......performTraversals();//调用performTraversals函数......break;}
}
再去看performTraversals函数,这个函数比较复杂,先只看它的关键部分,代码如下所示:
[-->ViewRoot.java]
private void performTraversals() {finalView host = mView;//还记得这mView吗?它就是DecorView喔booleaninitialized = false;booleancontentInsetsChanged = false;booleanvisibleInsetsChanged;try {//1 关键函数relayoutWindowrelayoutResult = relayoutWindow(params, viewVisibility,insetsPending);}......//2 开始绘制draw(fullRedrawNeeded);......
}
1. relayoutWindow的分析
performTraversals函数比较复杂,暂时只关注其中的两个函数relayoutWindow和draw即可。先看第一个relayoutWindow,代码如下所示:
[-->ViewRoot.java]
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException {//原来是调用IWindowSession的relayOut,暂且记住这个调用int relayoutResult = sWindowSession.relayout(mWindow, params,(int) (mView.mMeasuredWidth * appScale + 0.5f),(int) (mView.mMeasuredHeight * appScale + 0.5f),viewVisibility, insetsPending, mWinFrame,mPendingContentInsets, mPendingVisibleInsets,mPendingConfiguration, mSurface); //mSurface做为参数传进去了。}......
}
relayoutWindow中会调用IWindowSession的relayout函数,暂且记住这个调用,在精简流程后再进行分析。
2. draw的分析
再来看draw函数。这个函数非常重要,它可是Acitivity漂亮脸蛋的塑造大师啊,代码如下所示:
[-->ViewRoot.java]
private void draw(boolean fullRedrawNeeded) {Surface surface = mSurface;//mSurface是ViewRoot的成员变量......Canvas canvas;try {int left = dirty.left;int top = dirty.top;int right = dirty.right;int bottom = dirty.bottom;//从mSurface中lock一块Canvascanvas = surface.lockCanvas(dirty);......mView.draw(canvas);//调用DecorView的draw函数,canvas就是画布的意思啦!......//unlock画布,屏幕上马上就会见到漂亮宝贝的长相了。surface.unlockCanvasAndPost(canvas);}......
}
UI的显示好像很简单嘛!真的是这样的吗?在这之前我们先总结一下Activity的显示流程。
2.3 Activity总结
关于Activity的创建和显示,前面几节的信息可提炼成如下几条:
· Activity的顶层View是DecorView,而我们在onCreate函数中通过setContentView设置的View只不过是这个DecorView中的一部分罢了。DecorView是一个FrameLayout类型的ViewGroup。
· Activity和UI有关,它包含一个Window(真实类型是PhoneWindow)和一个WindowManager(真实类型是LocalWindowManager)对象。这两个对象将控制整个Activity的显示。
· LocalWindowManager使用了WindowManagerImpl做为最终的处理对象(Proxy模式),这个WindowManagerImpl中有一个ViewRoot对象。
· ViewRoot实现了ViewParent接口,它有两个重要的成员变量,一个是mView,它指向Activity顶层UI单元的DecorView,另外有一个mSurface,这个Surface包含了一个Canvas(画布)。除此之外,ViewRoot还通过Binder系统和WindowManagerService进行了跨进程交互。
· ViewRoot能处理Handler的消息,Activity的显示就是由ViewRoot在它的performTraversals函数中完成的。
· 整个Activity的绘图流程就是从mSurface中lock一块Canvas,然后交给mView去自由发挥画画的才能,最后unlockCanvasAndPost释放这块Canvas。
这里和显示有关的就是最后三条了,其中最重要的内容都和Surface相关,既然mSurface是ViewRoot的本地变量,那就直接去看Surface。
3 初识Surface
本节将介绍Surface对象。它可是纵跨Java/JNI层的对象。
3.1 和Surface有关的流程总结
这里,先总结一下前面讲解中和Surface有关的流程:
· 在ViewRoot构造时,会创建一个Surface,它使用无参构造函数,代码如下所示:
private final Surface mSurface = new Surface();
· ViewRoot通过IWindowSession和WMS交互,而WMS中会调用的一个attach函数,会构造一个SurfaceSession,代码如下所示:
void windowAddedLocked() {if(mSurfaceSession == null) {mSurfaceSession = new SurfaceSession();mNumWindow++;}
}
· ViewRoot在performTransval的处理过程中会调用IWindowSession的relayout函数。这个函数还没有分析。
· ViewRoot调用Surface的lockCanvas,得到一块画布。
· ViewRoot调用Surface的unlockCanvasAndPost释放这块画布。
这里从relayout函数开始分析,来看。
3.2 Surface之乾坤大挪移
1. 乾坤大挪移的表象
relayout的函数是一个跨进程的调用,由WMS完成实际处理。先到ViewRoot中看看调用方的用法,代码如下所示:
[-->ViewRoot.java]
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException {int relayoutResult = sWindowSession.relayout(mWindow, params,(int) (mView.mMeasuredWidth * appScale + 0.5f),(int) (mView.mMeasuredHeight * appScale + 0.5f),viewVisibility, insetsPending, mWinFrame,mPendingContentInsets, mPendingVisibleInsets,mPendingConfiguration, mSurface); //mSurface传了进去......return relayoutResult;
}
再看接收方的处理。它在WMS的Session中,代码如下所示:
[-->WindowManagerService.java::Session]
public int relayout(IWindow window,WindowManager.LayoutParams attrs,int requestedWidth, int requestedHeight, int viewFlags,boolean insetsPending, Rect outFrame, Rect outContentInsets,Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {//注意最后这个参数的名字,叫outSurface//调用外部类对象的relayoutWindowreturn relayoutWindow(this, window, attrs,requestedWidth,requestedHeight, viewFlags, insetsPending,outFrame, outContentInsets,outVisibleInsets, outConfig, outSurface);
}
[-->WindowManagerService.java]
public int relayoutWindow(Session session,IWindow client,WindowManager.LayoutParams attrs, int requestedWidth,int requestedHeight, int viewVisibility, boolean insetsPending,Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {.....try {//win就是WinState,这里将创建一个本地的Surface对象Surface surface = win.createSurfaceLocked();if (surface != null) {//先创建一个本地surface,然后在outSurface的对象上调用copyFrom//将本地Surface的信息拷贝到outSurface中,为什么要这么麻烦呢?outSurface.copyFrom(surface);......}}
}
[-->WindowManagerService.java::WindowState]
Surface createSurfaceLocked() {......try {//mSurfaceSession就是在Session上创建的SurfaceSession对象//这里,以它为参数,构造一个新的Surface对象mSurface = new Surface(mSession.mSurfaceSession, mSession.mPid,mAttrs.getTitle().toString(),0, w, h, mAttrs.format, flags);}Surface.openTransaction();//打开一个事务处理......Surface.closeTransaction();//关闭一个事务处理。关于事务处理以后再分析......
}
用图7来表示一下这个流程:
图7 复杂的Surface创建流程
根据图7可知:
· WMS中的Surface是乾坤中的乾,它的构造使用了带SurfaceSession参数的构造函数。
· ViewRoot中的Surface是乾坤中的坤,它的构造使用了无参构造函数。
· copyFrom就是挪移,它将乾中的Surface信息,拷贝到坤中的Surface即outSurface里。
要是觉得乾坤大挪移就是这两三下,未免就太小看它了。为彻底揭示这期间的复杂过程,我们将使用必杀技——aidl工具。
2. 揭秘Surface的乾坤大挪移
aidl可以把XXX.aidl文件转换成对应的Java文件。刚才所说的乾坤大挪移发生在ViewRoot调用IWindowSession的relayout函数中,它在IWindowSession.adil中的定义如下:
[-->IWindowSesson.aidl]
interface IWindowSession {......int relayout(IWindow window, in WindowManager.LayoutParams attrs,int requestedWidth, int requestedHeight, int viewVisibility,boolean insetsPending, out Rect outFrame, out Rect outContentInsets,out Rect outVisibleInsets, out Configuration outConfig,out Surface outSurface);
}
下面,拿必杀技aidl来编译一下这个aidl文件,其使用方法如下:
在命令行下可以输入:
aidl –Ie:\froyo\source\frameworks\base\core\java\ -Ie:\froyo\source\frameworks\base\Graphics\java e:\froyo\source\frameworks\base\core\java\android\view\IWindowSession.aidl test.java
新生成的Java文件叫test.java。其中,-I参数指定include目录,例如aidl文件中使用了别的Java文件中的类,所以需要指定这些Java文件所在的目录。
先看ViewRoot这个客户端生成的代码,如下所示:
[-->test.java::Bp端::relayout]
public int relayout(android.view.IWindow window,android.view.WindowManager.LayoutParams attrs,int requestedWidth, intrequestedHeight,int viewVisibility, boolean insetsPending,android.graphics.Rect outFrame,android.graphics.Rect outContentInsets,android.graphics.Rect outVisibleInsets,android.content.res.Configuration outConfig,android.view.Surface outSurface)//outSurface是第11个参数throwsandroid.os.RemoteException
{android.os.Parcel_data = android.os.Parcel.obtain();android.os.Parcel_reply = android.os.Parcel.obtain();int_result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeStrongBinder((((window!=null))?(window.asBinder()):(null)));if((attrs!=null)) {_data.writeInt(1);attrs.writeToParcel(_data,0);} else {_data.writeInt(0);}_data.writeInt(requestedWidth);_data.writeInt(requestedHeight);_data.writeInt(viewVisibility);_data.writeInt(((insetsPending)?(1):(0)));//奇怪,outSurface的信息没有写到请求包_data中,就直接发送请求消息了mRemote.transact(Stub.TRANSACTION_relayout,_data, _reply, 0);_reply.readException();_result= _reply.readInt();if((0!=_reply.readInt())) {outFrame.readFromParcel(_reply);}....if((0!=_reply.readInt())) {outSurface.readFromParcel(_reply); //从Parcel中读取信息来填充outSurface}}......return _result;
}
奇怪!ViewRoot调用requestlayout竟然没有把outSurface信息传进去,这么说,服务端收到的Surface对象应该就是空吧?那怎么能调用copyFrom呢?还是来看服务端的处理,先看首先收到消息的onTransact函数,代码如下所示:
[-->test.java::Bn端::onTransact]
public boolean onTransact(int code,android.os.Parcel data, android.os.Parcel reply, int flags) throwsandroid.os.RemoteException
{switch(code){case TRANSACTION_relayout:{data.enforceInterface(DESCRIPTOR);android.view.IWindow_arg0;android.view.Surface_arg10;//刚才讲了,Surface信息并没有传过来,那么在relayOut中看到的outSurface是怎么//出来的呢?看下面这句可知,原来在服务端这边竟然new了一个新的Surface!!!_arg10= new android.view.Surface();int _result = this.relayout(_arg0, _arg1, _arg2, _arg3, _arg4,_arg5,_arg6, _arg7, _arg8, _arg9, _arg10);reply.writeNoException();reply.writeInt(_result);//_arg10就是调用copyFrom的那个outSurface,那怎么传到客户端呢?if((_arg10!=null)) {reply.writeInt(1);//调用Surface的writeToParcel,把信息写到reply包中。//注意最后一个参数为PARCELABLE_WRITE_RETURN_VALUE_arg10.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);}}}......return true;
}
3. 乾坤大挪移的真相
这里,总结一下乾坤大挪移的整个过程,如图8表示:
图8 乾坤大挪移的真面目
上图非常清晰地列出了乾坤大挪移的过程,我们可结合代码来加深理解。
注意,BpWindowSession是IWindowSessionBinder在客户端的代表。
3.3 分析乾坤大挪移的JNI层
前文讲述的内容都集中在Java层,下面要按照流程顺序分析JNI层的内容。
1. Surface的无参构造分析
在JNI层,第一个被调用的是Surface的无参构造函数,其代码如下所示:
[-->Surface.java] 对应3.2.1[-->WindowManagerService.java::WindowState]中的new Surface()!
public Surface() {......//CompatibleCanvas从Canvas类派生mCanvas = new CompatibleCanvas();
}
Canvas是什么?根据SDK文档的介绍可知,画图需要“四大金刚”相互合作,这四大金刚是:
· Bitmap:用于存储像素,也就是画布。可把它当做一块数据存储区域。
· Canvas:用于记载画图的动作,比如画一个圆,画一个矩形等。Canvas类提供了这些基本的绘图函数。
· Drawing primitive:绘图基元,例如矩形、圆、弧线、文本、图片等。
· Paint:它用来描述绘画时使用的颜色、风格(如实线、虚线等)等。
在一般情况下,Canvas会封装一块Bitmap,而作图就是基于这块Bitmap的。前面说的画布,其实指的就是Canvas中的这块Bitmap。
这些知识稍了解即可,不必去深究。Surface的无参构造函数没有什么有价值的内容,接着看下面的内容。
2. SurfaceSession的构造
现在要分析的是SurfaceSession,其构造函数如下所示:
[-->SurfaceSession.java]
public SurfaceSession() {init();//这是一个native函数
}
init是一个native函数。去看看它的JNI实现,它在android_view_Surface.cpp中,代码如下所示:
[-->android_view_Surface.cpp]
static void SurfaceSession_init(JNIEnv* env,jobject clazz)
{//创建一个SurfaceComposerClient对象sp<SurfaceComposerClient> client = new SurfaceComposerClient;client->incStrong(clazz);//在Java对象中保存这个client对象的指针,类型为SurfaceComposerClientenv->SetIntField(clazz, sso.client, (int)client.get());
}
3. Surface的有参构造
下一个调用的是Surface的有参构造,其参数中有一个SurfaceSession。先看Java层的代码,如下所示:
[-->Surface.java]
public Surface(SurfaceSession s,//传入一个SurfaceSession对象int pid, String name, int display, int w, int h, int format, int flags)throws OutOfResourcesException {......mCanvas = new CompatibleCanvas();//又一个native函数,注意传递的参数:display以后再说,w,h代表绘图区域的宽高值init(s,pid,name,display,w,h,format,flags);mName = name;
}
Surface的native init函数的JNI实现,也在android_view_Surface.cpp中,一起来看:
[-->android_view_Surface.cpp]
static void Surface_init(JNIEnv*env, jobject clazz, jobject session,jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jintflags)
{//从SurfaceSession对象中取出之前创建的那个SurfaceComposerClient对象SurfaceComposerClient* client = (SurfaceComposerClient*)env->GetIntField(session, sso.client);sp<SurfaceControl> surface;//注意它的类型是SurfaceControlif (jname == NULL) {/*调用SurfaceComposerClient的createSurface函数,返回的surface是一个SurfaceControl类型。*/surface = client->createSurface(pid, dpy, w, h, format, flags);} else{......}//把这个surfaceControl对象设置到Java层的Surface对象中,对这个函数就不再分析了setSurfaceControl(env, clazz, surface);
}
4. copyFrom的分析
现在要分析的就是copyFrom了。它就是一个native函数。看它的JNI层代码:
[-->android_view_Surface.cpp]
static void Surface_copyFrom(JNIEnv* env, jobject clazz, jobject other)
{//根据JNI函数的规则,clazz是copyFrom的调用对象,而other是copyFrom的参数。//目标对象此时还没有设置SurfaceControl,而源对象在前面已经创建了SurfaceControlconst sp<SurfaceControl>& surface = getSurfaceControl(env, clazz);const sp<SurfaceControl>& rhs = getSurfaceControl(env, other);if (!SurfaceControl::isSameSurface(surface, rhs)) {//把源SurfaceControl对象设置到目标Surface中。setSurfaceControl(env, clazz, rhs);}
}
5. writeToParcel的分析
多亏了必杀技aidl工具的帮忙,才挖出这个隐藏的writeToParcel函数调用,下面就来看看它,代码如下所示:
[-->android_view_Surface.cpp]
static void Surface_writeToParcel(JNIEnv* env,jobject clazz, jobject argParcel, jint flags)
{Parcel* parcel = (Parcel*)env->GetIntField(argParcel, no.native_parcel);//clazz就是Surface对象,从这个Surface对象中取出保存的SurfaceControl对象const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));/*把SurfaceControl中的信息写到Parcel包中,然后利用Binder通信传递到对端,对端通过readFromParcel来处理Parcel包。*/SurfaceControl::writeSurfaceToParcel(control, parcel);if (flags & PARCELABLE_WRITE_RETURN_VALUE) {//还记得PARCELABLE_WRITE_RETURN_VALUE吗?flags的值就等于它//所以本地Surface对象的SurfaceControl值被置空了setSurfaceControl(env, clazz, 0);}
}
6. readFromParcel的分析
再看作为客户端的ViewRoot所调用的readFromParcel函数。它也是一个native函数,JNI层的代码如下所示:
[-->android_view_Surface.cpp]
static void Surface_readFromParcel(JNIEnv* env, jobject clazz, jobject argParcel)
{Parcel* parcel = (Parcel*)env->GetIntField(argParcel,no.native_parcel);//注意下面定义的变量类型是Surface,而不是SurfaceControlconst sp<Surface>& control(getSurface(env, clazz));//根据服务端传递的Parcel包来构造一个新的surface。sp<Surface> rhs = new Surface(*parcel);if (!Surface::isSameSurface(control, rhs)) {//把这个新surface赋给ViewRoot中的mSurface对象。setSurface(env,clazz, rhs);}
}
7. Surface乾坤大挪移的小结
可能有人会问,乾坤大挪移怎么这么复杂?这期间出现了多少对象?来总结一下,在此期间一共有三个关键对象(注意我们这里只考虑JNI层的Native对象),它们分别是:
· SurfaceComposerClient。
· SurfaceControl。
· Surface,这个Surface对象属于Native层,和Java层的Surface相对应。
其中转移到ViewRoot成员变量mSurface中的,就是最后这个Surface对象了。这一路走来,真是异常坎坷。来回顾并概括总结一下这段历程。至于它的作用应该是很清楚了。以后要破解SurfaceFlinger,靠的就是这个精简的流程。
· 创建一个SurfaceComposerClient。
· 调用SurfaceComposerClient的createSurface得到一个SurfaceControl对象。
· 调用SurfaceControl的writeToParcel把一些信息写到Parcel包中。
· 根据Parcel包的信息构造一个Surface对象。这个Surface对象保存到Java层的mSurface对象中。这样,大挪移的结果是ViewRoot得到一个Native的Surface对象。
这个Surface对象非常重要,可它到底有什么用呢?这正是下一节要讲的内容。
3.4 Surface和画图
下面,来看最后两个和Surface相关的函数调用:一个是lockCanvas;另外一个是unlockCanvasAndPost。
1. lockCanvas的分析
要对lockCanvas进行分析,须先来看Java层的函数,代码如下所示:
[-->Surface.java::lockCanvas()]
public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException,IllegalArgumentException
{return lockCanvasNative(dirty);//调用native的lockCanvasNative函数。
}
[-->android_view_Surface.cpp::Surface_lockCanvas()]
static jobject Surface_lockCanvas(JNIEnv* env,jobject clazz, jobject dirtyRect)
{//从Java中的Surface对象中,取出费尽千辛万苦得到的Native的Surface对象const sp<Surface>& surface(getSurface(env, clazz));......// dirtyRect表示需要重绘的矩形块,下面根据这个dirtyRect设置dirtyRegionRegiondirtyRegion;if(dirtyRect) {Rect dirty;dirty.left =env->GetIntField(dirtyRect, ro.l);dirty.top =env->GetIntField(dirtyRect, ro.t);dirty.right = env->GetIntField(dirtyRect, ro.r);dirty.bottom=env->GetIntField(dirtyRect, ro.b);if(!dirty.isEmpty()) {dirtyRegion.set(dirty); }} else{dirtyRegion.set(Rect(0x3FFF,0x3FFF));}//调用NativeSurface对象的lock函数,//传入了一个参数Surface::SurfaceInfo info和一块表示脏区域的dirtyRegionSurface::SurfaceInfo info;status_t err = surface->lock(&info, &dirtyRegion);......//Java的Surface对象构造的时候会创建一个CompatibleCanvas。//这里就取出这个CompatibleCanvas对象jobject canvas = env->GetObjectField(clazz, so.canvas);env->SetIntField(canvas, co.surfaceFormat, info.format);//从Canvas对象中取出SkCanvas对象SkCanvas* nativeCanvas =(SkCanvas*)env->GetIntField(canvas, no.native_canvas); SkBitmap bitmap;ssize_t bpr = info.s *bytesPerPixel(info.format);bitmap.setConfig(convertPixelFormat(info.format), info.w, info.h, bpr);......if (info.w > 0 && info.h > 0) {//info.bits指向一块存储区域。bitmap.setPixels(info.bits);} else{bitmap.setPixels(NULL);}//给这个SkCanvas设置一个Bitmap,还记得前面说的,画图需要的四大金刚吗?//这里将Bitmap设置到这个Canvas中,这样UI绘画时就有画布了。nativeCanvas->setBitmapDevice(bitmap);......return canvas;
}
lockCanvas还算比较简单:
· 先获得一块存储区域,然后将它和Canvas绑定到一起,这样,UI绘画的结果就记录在这块存储区域里了。
注意,本书不拟讨论Android系统上Skia和OpenGL方面的知识,有兴趣的读者可自行研究。
接下来看unlockCanvasAndPost函数,它也是一个native函数:
2. unlockCanvasAndPost的分析
来看unlockCanvasAndPost的代码,如下所示:
[-->android_view_Surface.cpp]
static void Surface_unlockCanvasAndPost(JNIEnv*env, jobject clazz, jobject argCanvas)
{jobjectcanvas = env->GetObjectField(clazz, so.canvas);//取出Native的Surface对象const sp<Surface>& surface(getSurface(env,clazz));//下面这些内容,不拟讨论,读者若有兴趣,可结合Skia库,自行研究。SkCanvas* nativeCanvas =(SkCanvas*)env->GetIntField(canvas, no.native_canvas);int saveCount = env->GetIntField(clazz, so.saveCount);nativeCanvas->restoreToCount(saveCount);nativeCanvas->setBitmapDevice(SkBitmap());env->SetIntField(clazz, so.saveCount, 0);//调用Surface对象的unlockAndPost函数。status_t err = surface->unlockAndPost();......
}
3.5 初识Surface总结
在本节的最后,我们来概括总结一下这一节所涉及到和Surface相关的调用流程,以备攻克下一个难关,如图9所示 :
图9 Surface的精简流程图
4 深入分析Surface
这一节,拟基于图9中的流程,对Surface进行深入分析。在分析之前,还需要介绍一些Android平台上图形/图像显示方面的知识,这里统称之为与Surface相关的基础知识。
4.1 与Surface相关的基础知识介绍
1. 显示层(Layer)和屏幕组成
你了解屏幕显示的漂亮界面是如何组织的吗?来看图10所展示的屏幕组成示意图:
图10 屏幕组成示意图
从图10中可以看出:
· 屏幕位于一个三维坐标系中,其中Z轴从屏幕内指向屏幕外。
· 编号为①②③的矩形块叫显示层(Layer)。每一层有自己的属性,例如颜色、透明度、所处屏幕的位置、宽、高等。除了属性之外,每一层还有自己对应的显示内容,也就是需要显示的图像。
在Android中,Surface系统工作时,会由SurfaceFlinger对这些按照Z轴排好序的显示层进行图像混合,混合后的图像就是在屏幕上看到的美妙画面了。这种按Z轴排序的方式符合我们在日常生活中的体验,例如前面的物体会遮挡住后面的物体。
Surface系统提供了三种属性,一共四种不同的显示层。简单介绍一下:
· 第一种属性是eFXSurfaceNormal属性,大多数的UI界面使用的就是这种属性。它有两种模式:
1)Normal模式,这种模式的数据,是通过前面的mView.draw(canvas)画上去的。这也是绝大多数UI所采用的方式。
2)PushBuffer模式,这种模式对应于视频播放、摄像机摄录/预览等应用场景。以摄像机为例,当摄像机运行时,来自Camera的预览数据直接push到Buffer中,无须应用层自己再去draw了。
· 第二种属性是eFXSurfaceBlur属性,这种属性的UI有点朦胧美,看起来很像隔着一层毛玻璃。
· 第三种属性是eFXSurfaceDim属性,这种属性的UI看起来有点暗,好像隔了一层深色玻璃。从视觉上讲,虽然它的UI看起来有点暗,但并不模糊。而eFXSurfaceBlur不仅暗,还有些模糊。
本章将重点分析第一种属性的两类显示层的工作原理。
2. FrameBuffer和PageFlipping
我们知道,在Audio系统中,音频数据传输的过程是:
· 由客户端把数据写到共享内存中。
· 然后由AudioFlinger从共享内存中取出数据再往Audio HAL中发送。
根据以上介绍可知,在音频数据传输的过程中,共享内存起到了数据承载的重要作用。无独有偶,Surface系统中的数据传输也存在同样的过程,但承载图像数据的是鼎鼎大名的FrameBuffer(简称FB)。下面先来介绍FrameBuffer,然后再介绍Surface的数据传输过程。
(1)FrameBuffer的介绍
FrameBuffer的中文名叫帧缓冲,它实际上包括两个不同的方面:
· Frame:帧,就是指一幅图像。在屏幕上看到的那幅图像就是一帧。
· Buffer:缓冲,就是一段存储区域,可这个区域存储的是帧。
FrameBuffer的概念很清晰,它就是一个存储图形/图像帧数据的缓冲。这个缓冲来自哪里?理解这个问题,需要简单介绍一下Linux平台的虚拟显示设备FrameBuffer Device(简称FBD)。FBD是Linux系统中的一个虚拟设备,设备文件对应为/dev/fb%d(比如/dev/fb0)。这个虚拟设备将不同硬件厂商实现的真实设备统一在一个框架下,这样应用层就可以通过标准的接口进行图形/图像的输入和输出了。图12展示了FBD示意图:
图12 Linux系统中的FBD示意图
从上图中可以看出,应用层通过标准的ioctl或mmap等系统调用,就可以操作显示设备,用起来非常方便。这里,把mmap的调用列出来,相信大部分读者都知道它的作用了。FrameBuffer中的Buffer,就是通过mmap把设备中的显存映射到用户空间的,在这块缓冲上写数据,就相当于在屏幕上绘画。
注意:上面所说的框架将引出另外一个概念Linux FrameBuffer(简称LFB)。LFB是Linux平台提供的一种可直接操作FB的机制,依托这个机制,应用层通过标准的系统调用,就可以操作显示设备了。从使用的角度来看,它和Linux Audio中的OSS有些类似。
在继续分析前,先来问一个问题:前面在Audio系统中讲过,CB对象通过读写指针来协调生产者/消费者的步调,那么Surface系统中的数据传输过程,是否也需通过读写指针来控制呢?答案是肯定的,但不像Audio中的CB那样复杂。
(2)PageFlipping
图形/图像数据和音频数据不太一样,我们一般把音频数据叫音频流,它是没有边界的, 而图形/图像数据是一帧一帧的,是有边界的。
PageFlipping的中文名叫画面交换,其操作过程如下所示:
· 分配一个能容纳两帧数据的缓冲,前面一个缓冲叫FrontBuffer,后面一个缓冲叫BackBuffer。
· 消费者使用FrontBuffer中的旧数据,而生产者用新数据填充BackBuffer,二者互不干扰。
· 当需要更新显示时,BackBuffer变成FrontBuffer,FrontBuffer变成BackBuffer。如此循环,这样就总能显示最新的内容了。这个过程很像我们平常的翻书动作,所以它被形象地称为PageFlipping。
3. 图像混合
我们知道,在AudioFlinger中有混音线程,它能将来自多个数据源的数据混合后输出,那么,SurfaceFlinger是不是也具有同样的功能呢?
答案是肯定的,否则它就不会叫Flinger了。Surface系统支持软硬两个层面的图像混合:
· 软件层面的混合:例如使用copyBlt进行源数据和目标数据的混合。
· 硬件层面的混合:使用Overlay系统提供的接口。
总体来说,Surface是一个比较庞大的系统,由于篇幅和精力所限,本章后面的内容将重点关注Surface系统的框架和工作流程。在掌握框架和流程后,读者就可以在大的脉络中迅速定位到自己感兴趣的地方,然后展开更深入的研究了。
下面通过图9所示的精简流程,深入分析Android的Surface系统。
4.2 SurfaceComposerClient的分析
Java层SurfaceSession对象的构造函数会调用Native的SurfaceSession_init函数,而该函数的主要目的就是创建SurfaceComposerClient。
先回顾一下SurfaceSession_init函数,代码如下所示:
[-->android_view_Surface.cpp]
static void SurfaceSession_init(JNIEnv* env,jobject clazz)
{//new 一个SurfaceComposerClient对象sp<SurfaceComposerClient> client = new SurfaceComposerClient;//sp的使用也有让人烦恼的地方,有时需要显式地增加强弱引用计数,要是忘记,可就麻烦了client->incStrong(clazz);env->SetIntField(clazz, sso.client, (int)client.get());
}
上面代码中,显式地构造了一个SurfaceComposerClient对象。接下来看它是何方神圣。
1. 创建SurfaceComposerClient
SurfaceComposerClient这个名字隐含的意思是:这个对象会和SurfaceFlinger进行交互,因为SurfaceFlinger派生于SurfaceComposer。
通过它的构造函数来看是否是这样的。代码如下所示:
[-->SurfaceComposerClient.cpp]
SurfaceComposerClient::SurfaceComposerClient()
{//getComposerService()将返回SF的Binder代理端的BpSurfaceFlinger对象sp<ISurfaceComposer> sm(getComposerService());//先调用SF的createConnection,再调用_init_init(sm, sm->createConnection());if(mClient != 0) {Mutex::Autolock _l(gLock);//gActiveConnections是全局变量,把刚才创建的client保存到这个map中去gActiveConnections.add(mClient->asBinder(), this);}
}
果然如此,SurfaceComposerClient建立了和SF的交互通道,下面直接转到SF的createConnection函数去观察。
(1)createConnection的分析
直接看代码,如下所示:
[-->SurfaceFlinger.cpp]
sp<ISurfaceFlingerClient> SurfaceFlinger::createConnection()
{Mutex::Autolock _l(mStateLock);uint32_t token = mTokens.acquire();//先创建一个Client。sp<Client> client = new Client(token, this);//把这个Client对象保存到mClientsMap中,token是它的标识。status_t err = mClientsMap.add(token, client);/*创建一个用于Binder通信的BClient,BClient派生于ISurfaceFlingerClient,它的作用是接受客户端的请求,然后把处理提交给SF,注意,并不是提交给Client。Client会创建一块共享内存,该内存由getControlBlockMemory函数返回*/sp<BClient> bclient = new BClient(this, token, client->getControlBlockMemory());return bclient;
}
上面代码中提到,Client会创建一块共享内存。熟悉Audio的读者或许会认为,这可能是Surface的ControlBlock对象了!是的。CB对象在协调生产/消费步调时,起到了决定性的控制作用,所以非常重要,下面来看:
[-->SurfaceFlinger.cpp]
Client::Client(ClientID clientID, constsp<SurfaceFlinger>& flinger):ctrlblk(0), cid(clientID), mPid(0), mBitmap(0), mFlinger(flinger)
{const int pgsize = getpagesize();//下面这个操作会使cblksize为页的大小,目前是4096字节。constint cblksize = ((sizeof(SharedClient)+(pgsize-1))&~(pgsize-1));//MemoryHeapBase是我们的老朋友了,不熟悉的读者可以回顾Audio系统中所介绍的内容mCblkHeap = new MemoryHeapBase(cblksize, 0, "SurfaceFlinger Clientcontrol-block");ctrlblk = static_cast<SharedClient *>(mCblkHeap->getBase());if(ctrlblk) {new(ctrlblk) SharedClient; //再一次觉得眼熟吧?使用了placement new}
}
原来,Surface的CB对象就是在共享内存中创建的这个SharedClient对象。先来认识一下这个SharedClient。
(2)SharedClient的分析
SharedClient定义了一些成员变量,代码如下所示:
class SharedClient
{
public:SharedClient();~SharedClient();status_t validate(size_t token) const;uint32_t getIdentity(size_t token) const; //取出标识本Client的token
private:Mutex lock;Condition cv; //支持跨进程的同步对象//NUM_LAYERS_MAX为31,SharedBufferStack是什么?SharedBufferStack surfaces[ NUM_LAYERS_MAX ];
};//SharedClient的构造函数,没什么新意,不如Audio的CB对象复杂
SharedClient::SharedClient():lock(Mutex::SHARED), cv(Condition::SHARED)
{
}
SharedClient的定义似乎简单到极致了,不过不要高兴得过早,在这个SharedClient的定义中,没有发现和读写控制相关的变量,那怎么控制读写呢?
答案就在看起来很别扭的SharedBufferStack数组中,它有31个元素。
一个Client最多支持31个显示层。每一个显示层的生产/消费步调都由会对应的SharedBufferStack来控制。而它内部就用了几个成员变量来控制读写位置。
认识一下SharedBufferStack的这几个控制变量,如下所示:
[-->SharedBufferStack.h]
class SharedBufferStack {......//Buffer是按块使用的,每个Buffer都有自己的编号,其实就是数组中的索引号。volatile int32_t head; //FrontBuffer的编号volatile int32_t available; //空闲Buffer的个数volatile int32_t queued; //脏Buffer的个数,脏Buffer表示有新数据的Buffervolatile int32_t inUse; //SF当前正在使用的Buffer的编号 volatile status_t status; //状态码......
}
注意,上面定义的SharedBufferStack是一个通用的控制结构,而不仅是针对于只有两个Buffer的情况。根据前面介绍的PageFlipping知识,如果只有两个FB,那么,SharedBufferStack的控制就比较简单了:
要么SF读1号Buffer,客户端写0号Buffer,要么SF读0号Buffer,客户端写1号Buffer。
图13是展示了SharedClient的示意图:
图13 SharedClient的示意图
从上图可知:
· SF的一个Client分配一个跨进程共享的SharedClient对象。这个对象有31个SharedBufferStack元素,每一个SharedBufferStack对应于一个显示层。
· 一个显示层将创建两个Buffer,后续的PageFlipping就是基于这两个Buffer展开的。
另外,每一个显示层中,其数据的生产和消费并不是直接使用SharedClient对象来进行具体控制的,而是基于SharedBufferServer和SharedBufferClient两个结构,由这两个结构来对该显示层使用的SharedBufferStack进行操作,这些内容在以后的分析中还会碰到。
来接着分析后面的_init函数。
(3)_init函数的分析
先回顾一下之前的调用,代码如下所示:
[-->SurfaceComposerClient.cpp]
SurfaceComposerClient::SurfaceComposerClient()
{......_init(sm, sm->createConnection());......
}
来看这个_init函数,代码如下所示:
[-->SurfaceComposerClient.cpp]
void SurfaceComposerClient::_init(const sp<ISurfaceComposer>& sm, const sp<ISurfaceFlingerClient>& conn)
{mPrebuiltLayerState = 0;mTransactionOpen = 0;mStatus = NO_ERROR;mControl = 0;mClient = conn; //mClient就是BClient的客户端mControlMemory = mClient->getControlBlock();mSignalServer = sm; //mSignalServer就是BpSurfaceFlinger//mControl就是那个创建于共享内存之中的SharedClientmControl = static_cast<SharedClient*>(mControlMemory->getBase());
}
_init函数的作用,就是初始化SurfaceComposerClient中的一些成员变量。最重要的是得到了三个成员:
· mSignalServer ,它其实是SurfaceFlinger在客户端的代理BpSurfaceFlinger,它的主要作用是,在客户端更新完BackBuffer后(也就是刷新了界面后),通知SF进行PageFlipping和输出等工作。
· mControl,它是跨进程共享的SharedClient,是Surface系统的ControlBlock对象。
· mClient,它是BClient在客户端的对应物BpClient。
2. 到底有多少种对象?
这一节,出现了好几种类型的对象,通过图14来看看它们:
图14 类之间关系展示图
从上图中可以看出:
· SurfaceFlinger是从Thread派生的,所以它会有一个单独运行的工作线程。
· BClient和SF之间采用了Proxy模式,BClient支持Binder通信,它接收客户端的请求,并派发给SF执行。
· SharedClient构建于一块共享内存中,SurfaceComposerClient和Client对象均持有这块共享内存。
在精简流程中,关于SurfaceComposerClient就分析到这里,下面分析第二个步骤中的SurfaceControl对象。
4.3 SurfaceControl的分析
1. SurfaceControl的来历
根据精简的流程可知,这一节要分析的是SurfaceControl对象。先回顾一下这个对象的创建过程,代码如下所示:
[-->android_view_Surface.cpp]
static void Surface_init(JNIEnv* env, jobjectclazz, jobject session,jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jintflags)
{SurfaceComposerClient* client = (SurfaceComposerClient*)env->GetIntField(session, sso.client);//注意这个变量,类型是SurfaceControl,名字却叫surface,稍不留神就出错了。sp<SurfaceControl> surface;if (jname == NULL) {//调用Client的createSurface函数,得到一个SurfaceControl对象。surface = client->createSurface(pid, dpy, w, h, format, flags);}......//将这个SurfaceControl对象设置到Java层的对象中保存。setSurfaceControl(env, clazz, surface);
}
通过上面的代码可知,SurfaceControl对象由createSurface得来,下面看看这个函数。
此时,读者或许会被代码中随意起的变量名搞糊涂,因为我的处理方法碰到了容易混淆的地方,尽量以对象类型来表示这个对象。
(1)分析createSurface的请求端
在createSurface内部会使用Binder通信将请求发给SF,所以它分为请求和响应两端,先看请求端,代码如下所示:
[-->SurfaceComposerClient.cpp]
sp<SurfaceControl> SurfaceComposerClient::createSurface(int pid,DisplayID display,//DisplayID是什么意思?uint32_t w,uint32_t h,PixelFormat format,uint32_t flags)
{String8 name;constsize_t SIZE = 128;charbuffer[SIZE];snprintf(buffer, SIZE, "<pid_%d>", getpid());name.append(buffer);//调用另外一个createSurface,多一个name参数return SurfaceComposerClient::createSurface(pid, name, display, w, h, format, flags);
}
DisplayID是一个int整型,它的意义是屏幕编号,例如双屏手机就有内屏和外屏两块屏幕。由于目前Android的Surface系统只支持一块屏幕,所以这个变量的取值都是0。
再分析另外一个createSurface函数,它的代码如下所示:
[-->SurfaceComposerClient.cpp]
sp<SurfaceControl> SurfaceComposerClient::createSurface(int pid,const String8& name,DisplayID display,uint32_t w,uint32_t h,PixelFormat format,uint32_t flags)
{sp<SurfaceControl> result;if(mStatus == NO_ERROR) {ISurfaceFlingerClient::surface_data_t data;//调用BpSurfaceFlingerClient的createSurface函数sp<ISurface> surface = mClient->createSurface(&data, pid, name, display, w, h,format, flags);if(surface != 0) {if (uint32_t(data.token) < NUM_LAYERS_MAX) {//以返回的ISurface对象创建一个SurfaceControl对象result = new SurfaceControl(this, surface, data, w, h, format, flags);}}}return result;//返回的是SurfaceControl对象
}
请求端的处理比较简单:
· 调用跨进程的createSurface函数,得到一个ISurface对象,根据Binder一章的知识可知,这个对象的真实类型是BpSurface。不过以后统称之为ISurface。
· 以这个ISurface对象为参数,构造一个SurfaceControl对象。
createSurface函数的响应端在SurfaceFlinger进程中,下面去看这个函数。
在Surface系统定义了很多类型:
Native层有Surface、ISurface、SurfaceControl、SurfaceComposerClient。
Java层有Surface、SurfaceSession。
(2)分析createSurface的响应端
前面讲过,可把BClient看作是SF的Proxy,它会把来自客户端的请求派发给SF处理,通过代码来看看,是不是这样的?如下所示:
[-->SurfaceFlinger.cpp]
sp<ISurface> BClient::createSurface(ISurfaceFlingerClient::surface_data_t* params, int pid,const String8& name,DisplayID display, uint32_t w, uint32_t h, PixelFormat format,uint32_t flags)
{//果然是交给SF处理,以后我们将跳过BClient这个代理。return mFlinger->createSurface(mId, pid,name, params, display, w, h,format, flags);
}
来看createSurface函数,它的目的就是创建一个ISurface对象,不过这中间的玄机还挺多,代码如下所示:
[-->SurfaceFlinger.cpp]
sp<ISurface> SurfaceFlinger::createSurface(ClientID clientId, int pid,const String8& name, ISurfaceFlingerClient::surface_data_t* params,DisplayID d, uint32_t w, uint32_t h, PixelFormat format,uint32_t flags)
{sp<LayerBaseClient> layer;//LayerBaseClient是Layer家族的基类//这里又冒出一个LayerBaseClient的内部类,它也叫Surface,是不是有点头晕了?sp<LayerBaseClient::Surface> surfaceHandle;Mutex::Autolock _l(mStateLock);//根据clientId找到createConnection时加入的那个Client对象sp<Client> client = mClientsMap.valueFor(clientId);......//注意这个id,它的值表示Client创建的是第几个显示层,根据图14可以看出,这个id//同时也表示将使用SharedBufferStatck数组的第id个元素。int32_t id = client->generateId(pid);//一个Client不能创建多于NUM_LAYERS_MAX个的Layer。if(uint32_t(id) >= NUM_LAYERS_MAX) {return surfaceHandle;}//根据flags参数来创建不同类型的显示层,我们在4.1节介绍过相关知识switch(flags & eFXSurfaceMask) {case eFXSurfaceNormal:if (UNLIKELY(flags & ePushBuffers)) {//创建PushBuffer类型的显示层,我们将在拓展思考部分分析它layer = createPushBuffersSurfaceLocked(client, d, id, w, h, flags);} else {//1 创建Normal类型的显示层,我们分析待会这个layer = createNormalSurfaceLocked(client, d, id, w, h, flags, format);}break;case eFXSurfaceBlur://创建Blur类型的显示层layer = createBlurSurfaceLocked(client, d, id, w, h, flags);break;case eFXSurfaceDim://创建Dim类型的显示层layer = createDimSurfaceLocked(client, d, id, w, h, flags);break;}if(layer != 0) {layer->setName(name);setTransactionFlags(eTransactionNeeded);//从显示层对象中取出一个ISurface对象赋值给SurfaceHandlesurfaceHandle = layer->getSurface();if(surfaceHandle != 0) {params->token = surfaceHandle->getToken();params->identity = surfaceHandle->getIdentity();params->width = w;params->height = h;params->format = format;}}return surfaceHandle;//ISurface的Bn端就是这个对象。
}
用文字总结一下:
· LayerBaseClient:前面提到的显示层在代码中的对应物,就是这个LayerBaseClient,不过这是一个大家族,不同类型的显示层将创建不同类型的LayerBaseClient。
· LayerBaseClient中有一个内部类,名字叫Surface,这是一个支持Binder通信的类,它派生于ISurface。
关于Layer的故事,后面会有单独的章节来介绍。这里先继续分析createNormalSurfaceLocked函数。它的代码如下所示:
[-->SurfaceFlinger.cpp]
sp<LayerBaseClient> SurfaceFlinger::createNormalSurfaceLocked(const sp<Client>& client, DisplayID display,int32_t id, uint32_t w, uint32_t h, uint32_t flags,PixelFormat& format)
{switch(format) { //一些图像方面的参数设置,可以不去管它。case PIXEL_FORMAT_TRANSPARENT:case PIXEL_FORMAT_TRANSLUCENT:format = PIXEL_FORMAT_RGBA_8888;break;case PIXEL_FORMAT_OPAQUE:format = PIXEL_FORMAT_RGB_565;break;}//1 创建一个Layer类型的对象sp<Layer> layer = new Layer(this, display,client, id);//2 设置Bufferstatus_t err = layer->setBuffers(w, h, format, flags);if (LIKELY(err == NO_ERROR)) {//初始化这个新layer的一些状态layer->initStates(w, h, flags);//3 还记得在图10中提到的Z轴吗?下面这个函数把这个layer加入到Z轴大军中。addLayer_l(layer);}......return layer;
}
createNormalSurfaceLocked函数有三个关键点,它们是:
· 构造一个Layer对象。
· 调用Layer对象的setBuffers函数。
· 调用SF的addLayer_l函数。
暂且记住这三个关键点,后文有单独章节分析它们。先继续分析SurfaceControl的流程。
(3)创建SurfaceControl对象
当跨进程的createSurface调用返回一个ISurface对象时,将通过下面的代码创建一个SurfaceControl对象:
result = new SurfaceControl(this, surface, data,w, h,format, flags);
下面来看这个SurfaceControl对象为何物。它的代码如下所示:
[-->SurfaceControl.cpp]
SurfaceControl::SurfaceControl(const sp<SurfaceComposerClient>& client,const sp<ISurface>& surface,const ISurfaceFlingerClient::surface_data_t& data,uint32_t w, uint32_t h, PixelFormat format, uint32_t flags)//mClient为SurfaceComposerClient,而mSurface指向跨进程createSurface调用返回的ISurface对象。:mClient(client), mSurface(surface),mToken(data.token), mIdentity(data.identity),mWidth(data.width), mHeight(data.height), mFormat(data.format),mFlags(flags)
{
}
SurfaceControl类可以看作是一个wrapper类:它封装了一些函数,通过这些函数可以方便地调用mClient或ISurface提供的函数。
在SurfaceControl的分析过程中,还遗留了和Layer相关的部分,下面就来解决它们。
2. Layer和它的家族
我们在createSurface中创建的是Normal的Layer,下面先看这个Layer的构造函数。
(1)Layer的构造
Layer是从LayerBaseClient派生的,其代码如下所示:
[-->Layer.cpp]
Layer::Layer(SurfaceFlinger* flinger, DisplayIDdisplay,const sp<Client>& c, int32_t i)//这个i表示SharedBufferStack数组的索引:LayerBaseClient(flinger, display, c, i),//先调用基类构造函数mSecure(false),mNoEGLImageForSwBuffers(false),mNeedsBlending(true),mNeedsDithering(false)
{//getFrontBuffer实际取出的是FrontBuffer的位置mFrontBufferIndex = lcblk->getFrontBuffer();
}
再来看基类LayerBaseClient的构造函数,代码如下所示:
[-->LayerBaseClient.cpp]
LayerBaseClient::LayerBaseClient(SurfaceFlinger*flinger, DisplayID display,const sp<Client>& client, int32_t i):LayerBase(flinger, display), lcblk(NULL), client(client), mIndex(i),mIdentity(uint32_t(android_atomic_inc(&sIdentity)))
{/*创建一个SharedBufferServer对象,注意它使用了SharedClient对象,并且传入了表示SharedBufferStack数组索引的i和一个常量NUM_BUFFERS*/lcblk = new SharedBufferServer(client->ctrlblk, i, NUM_BUFFERS,//该值为常量2,在Layer.h中定义mIdentity);
}
SharedBufferServer是什么?它和SharedClient有什么关系?
其实,之前在介绍SharedClient时曾提过与此相关的内容,这里再来认识一下,先看图15:
图15 ShardBufferServer的示意图
根据上图并结合前面的介绍,可以得出以下结论:
· 在SF进程中,Client的一个Layer将使用SharedBufferStack数组中的一个成员,并通过SharedBufferServer结构来控制这个成员,SF是消费者,所以可由SharedBufferServer来控制数据的读取。
· 与之相对应,客户端的进程也会有一个对象来使用这个SharedBufferStatck,可它是通过另外一个叫SharedBufferClient的结构来控制的。客户端为SF提供数据,所以可由SharedBufferClient控制数据的写入。在后文的分析中还会碰到SharedBufferClient。
注意,在拓展思考部分,会有单独章节来分析生产/消费过程中的读写控制。
通过前面的代码可知,Layer对象被new出来后,传给了一个sp对象,读者还记得sp中的onFirstRef函数吗?Layer家族在这个函数中还有一些处理。一起去看看,但这个函数由基类LayerBaseClient实现。
[-->LayerBase.cpp]
void LayerBaseClient::onFirstRef()
{ sp<Client> client(this->client.promote());if (client != 0) {//把自己加入client对象的mLayers数组中,这部分内容比较简单,读者可以自行研究client->bindLayer(this, mIndex);}
}
好,Layer创建完毕,下面来看第二个重要的函数setBuffers。
(2)setBuffers的分析
这个函数的目的就是创建用于PageFlipping的FrontBuffer和BackBuffer。一起来看,代码如下所示:
[-->Layer.cpp]
status_t Layer::setBuffers( uint32_t w, uint32_th,PixelFormat format,uint32_t flags)
{PixelFormatInfo info;status_t err = getPixelFormatInfo(format, &info);if(err) return err;//DisplayHardware是代表显示设备的HAL对象,0代表第一块屏幕的显示设备。//这里将从HAL中取出一些和显示相关的信息。const DisplayHardware& hw(graphicPlane(0).displayHardware());uint32_t const maxSurfaceDims = min(hw.getMaxTextureSize(), hw.getMaxViewportDims());PixelFormatInfo displayInfo;getPixelFormatInfo(hw.getFormat(),&displayInfo);const uint32_t hwFlags = hw.getFlags();....../*创建Buffer,这里将创建两个GraphicBuffer。这两个GraphicBuffer就是我们前面所说的FrontBuffer和BackBuffer。*/for (size_t i=0 ; i<NUM_BUFFERS ; i++) {//注意,这里调用的是GraphicBuffer的无参构造函数,mBuffers是一个二元数组。mBuffers[i] = new GraphicBuffer();}//又冒出来一个SurfaceLayer类型,#¥%……&*!@mSurface = new SurfaceLayer(mFlinger, clientIndex(), this);returnNO_ERROR;
}
setBuffers函数的工作内容比较简单,就是:
· 创建一个GraphicBuffer缓冲数组,元素个数为2,即FrontBuffer和BackBuffer。
· 创建一个SurfaceLayer,关于它的身世我们后续再介绍。
GraphicBuffer是Android提供的显示内存管理类,关于它的故事,将在4.7节中介绍。我们暂把它当做普通的Buffer即可。
setBuffers中出现的SurfaceLayer类是什么?读者可能对此感觉有些晕乎。待把最后一个关键函数addLayer_l介绍完,或许就不太晕了。
(3)addLayer_l的分析
addLayer_l把这个新创建的layer加入自己的Z轴大军,下面来看:
[-->SurfaceFlinger.cpp]
status_t SurfaceFlinger::addLayer_l(const sp<LayerBase>& layer)
{/*mCurrentState是SurfaceFlinger定义的一个结构,它有一个成员变量叫layersSortedByZ,其实就是一个排序数组。下面这个add函数将把这个新的layer按照它在Z轴的位置加入到排序数组中。mCurrentState保存了所有的显示层。*/ssize_t i = mCurrentState.layersSortedByZ.add(layer, &LayerBase::compareCurrentStateZ);sp<LayerBaseClient> lbc = LayerBase::dynamicCast<LayerBaseClient*>(layer.get());if(lbc != 0) {mLayerMap.add(lbc->serverIndex(), lbc);}return NO_ERROR;
}
对Layer的三个关键函数都已分析过了,下面正式介绍Layer家族。
(4)Layer家族的介绍
前面的内容确让人头晕眼花,现在应该帮大家恢复清晰的头脑。见图16:
图16 Layer家族
通过上图可知:
· LayerBaseClient从LayerBase类派生。
· LayerBaseClient还有四个派生类,分别是Layer、LayerBuffer、LayerDim和LayerBlur。
· LayerBaseClient定义了一个内部类Surface,这个Surface从ISurface类派生,它支持Binder通信。
· 针对不同的类型,Layer和LayerBuffer分别有一个内部类SurfaceLayer和SurfaceLayerBuffer,它们继承了LayerBaseClient的Surface类。所以对于Normal类型的显示层来说,getSurface返回的ISurface对象的真正类型是SurfaceLayer。
· LayerDim和LayerBlur类没有定义自己的内部类,所以对于这两种类型的显示层来说,它们直接使用了LayerBaseClient的Surface。
· ISurface接口提供了非常简单的函数,如requestBuffer、postBuffer等。
这里大量使用了内部类。我们知道,内部类最终都会把请求派发给外部类对象来处理,既然如此,在以后分析中,如果没有特殊情况,就会直接跳到外部类的处理函数中。
3. SurfaceControl总结
SurfaceControl创建后得到了什么呢?可用图17来表示:
图17 SurfaceControl创建后的结果图
通过上图可以知道:
· mClient成员变量指向SurfaceComposerClient。
· mSurface的Binder通信响应端为SurfaceLayer。
· SurfaceLayer有一个变量mOwner指向它的外部类Layer,而Layer有一个成员变量mSurface指向SurfaceLayer。这个SurfaceLayer对象由getSurface函数返回。
注意,mOwner变量由SurfaceLayer的基类Surface(LayBaseClient的内部类)定义。
接下来就是writeToParcel分析和Native Surface对象的创建了。注意,这个Native的Surface可不是LayBaseClient的内部类Surface。
4.4 writeToParcel和Surface对象的创建
从乾坤大挪移的知识可知,前面创建的所有对象都在WindowManagerService所在的进程system_server中,而writeToParcel则需要把一些信息打包到Parcel后,发送到Activity所在的进程。到底哪些内容需要回传给Activity所在的进程呢?后文将Activity所在的进程简称为Activity端。
1. writeToParcel分析
writeToParcel比较简单,就是把一些信息写到Parcel中去。代码如下所示:
[-->SurfaceControl.cpp]
status_t SurfaceControl::writeSurfaceToParcel(const sp<SurfaceControl>& control, Parcel* parcel)
{uint32_t flags = 0;uint32_t format = 0;SurfaceID token = -1;uint32_t identity = 0;uint32_t width = 0;uint32_t height = 0;sp<SurfaceComposerClient> client;sp<ISurface> sur;if(SurfaceControl::isValid(control)) {token = control->mToken;identity = control->mIdentity;client = control->mClient;sur = control->mSurface;width = control->mWidth;height = control->mHeight;format = control->mFormat;flags = control->mFlags;}//SurfaceComposerClient的信息需要传递到Activity端,这样客户端那边会构造一个SurfaceComposerClient对象parcel->writeStrongBinder(client!=0 ? client->connection() : NULL);//把ISurface对象信息也写到Parcel中,这样Activity端那边也会构造一个ISurface对象parcel->writeStrongBinder(sur!=0 ? sur->asBinder(): NULL);parcel->writeInt32(token);parcel->writeInt32(identity);parcel->writeInt32(width);parcel->writeInt32(height);parcel->writeInt32(format);parcel->writeInt32(flags);return NO_ERROR;
}
Parce包发到Activity端后,readFromParcel将根据这个Parcel包构造一个Native的Surface对象,一起来看相关代码。
2. 分析Native的Surface创建过程
[-->android_view_Surface.cpp]
static void Surface_readFromParcel(JNIEnv* env, jobject clazz, jobject argParcel)
{Parcel* parcel = (Parcel*)env->GetIntField(argParcel, no.native_parcel);const sp<Surface>& control(getSurface(env,clazz));//根据服务端的parcel信息来构造客户端的Surfacesp<Surface> rhs = new Surface(*parcel);if(!Surface::isSameSurface(control, rhs)) {setSurface(env, clazz, rhs);}
}
Native的Surface是怎么利用这个Parcel包的?代码如下所示:
[-->Surface.cpp]
Surface::Surface(const Parcel& parcel)
:mBufferMapper(GraphicBufferMapper::get()),
mSharedBufferClient(NULL)
{/*Surface定义了一个mBuffers变量,它是一个sp<GraphicBuffer>的二元数组,也就是说Surface也存在二个GraphicBuffer,而之前在创建Layer的时候也有两个GraphicBuffer,难道一共有四个 GraphicBuffer?这个问题,后面再解答。*/sp<IBinder> clientBinder =parcel.readStrongBinder();//得到ISurface的Bp端BpSurface。mSurface =interface_cast<ISurface>(parcel.readStrongBinder());mToken = parcel.readInt32();mIdentity = parcel.readInt32();mWidth = parcel.readInt32();mHeight = parcel.readInt32();mFormat = parcel.readInt32();mFlags = parcel.readInt32();if (clientBinder != NULL) {/*根据ISurfaceFlingerClient对象构造一个SurfaceComposerClient对象,注意我们现在位于Activity端,这里还没有创建SurfaceComposerClient对象,所以需要创建一个*/mClient = SurfaceComposerClient::clientForConnection(clientBinder);//SharedBuffer家族的最后一员ShardBufferClient终于出现了。mSharedBufferClient = new SharedBufferClient(mClient->mControl, mToken, 2,mIdentity);}init();//做一些初始化工作。
}
在Surface创建完后,得到什么了呢?看图18就可知道:
图18 Native Surface的示意图
上图很清晰地说明:
· SharedBuffer家族依托共享内存结构SharedClient与它共同组成了Surface系统生产/消费协调的中枢控制机构,它在SF端的代表是SharedBufferServer,在Activity端的代表是SharedBufferClient。
· Native的Surface将和SF中的SurfaceLayer建立Binder联系。
另外,图中还特意画出了承载数据的GraphicBuffer数组,在代码的注释中也针对GraphicBuffer提出了一个问题:Surface中有两个GraphicBuffer,Layer也有两个,一共就有四个GraphicBuffer了,可是为什么这里只画出两个呢?答案是,咱们不是有共享内存吗?这四个GraphicBuffer其实操纵的是同一段共享内存,所以为了简单,就只画了两个GraphicBuffer。在4.7节再介绍GraphicBuffer的故事。
下面,来看中枢控制机构的SharedBuffer家族。
3. SharedBuffer家族介绍
(1)SharedBuffer家族成员
SharedBuffer是一个家族名称,它包括多少成员呢?来看SharedBuffer的家族图谱,如图19所示:
图19 SharedBuffer家族介绍
从上图可以知道:
· XXXCondition、XXXUpdate等都是内部类,它们主要是用来更新读写位置的。不过这些操作,为什么要通过类来封装呢?因为SharedBuffer的很多操作都使用了C++中的Function Object(函数对象),而这些内部类的实例就是函数对象。函数对象是什么?它怎么使用?对此,在以后的分析中会介绍。
(2)SharedBuffer家族和SharedClient的关系
前面介绍过,SharedBufferServer和SharedBufferClient控制的其实只是SharedBufferStack数组中的一个,下面通过SharedBufferBase的构造函数,来看是否如此。
[-->SharedBufferStack.cpp]
SharedBufferBase::SharedBufferBase(SharedClient*sharedClient,int surface, int num, int32_t identity)
: mSharedClient(sharedClient),mSharedStack(sharedClient->surfaces+ surface),mNumBuffers(num), //根据前面PageFlipping的知识可知,num值为2mIdentity(identity)
{/*上面的赋值语句中最重要的是第二句:mSharedStack(sharedClient->surfaces +surface)这条语句使得这个SharedBufferXXX对象,和SharedClient中SharedBufferStack数组的第surface个元素建立了关系*/
}
4. Native Surface总结
至此,Activity端Java的Surface对象,终于和一个Native Surface对象挂上了钩,并且这个Native Surface还准备好了绘图所需的一切,其中包括:
· 两个GraphicBuffer,这就是PageFlipping所需要的FrontBuffer和BackBuffer。
· SharedBufferServer和SharedBufferClient结构,这两个结构将用于生产/消费的过程控制。
· 一个ISurface对象,这个对象连接着SF中的一个SurfaceLayer对象。
· 一个SurfaceComposerClient对象,这个对象连接着SF中的一个BClient对象。
资源都已经准备好了,可以开始绘制UI了。下面,分析两个关键的函数lockCanvas和unlockCanvasAndPost。
4.5 lockCanvas和unlockCanvasAndPost的分析
这一节,分析精简流程中的最后两个函数lockCanvas和unlockCanvasAndPost。
1. lockCanvas分析
据前文分析可知,UI在绘制前都需要通过lockCanvas得到一块存储空间,也就是所说的BackBuffer。这个过程中最终会调用Surface的lock函数。其代码如下所示:
[-->Surface.cpp]
status_t Surface::lock(SurfaceInfo* other,Region* dirtyIn, bool blocking)
{//传入的参数中,other用来接收一些返回信息,dirtyIn表示需要重绘的区域 ......if (mApiLock.tryLock() != NO_ERROR) {//多线程的情况下要锁住......return WOULD_BLOCK;}//设置usage标志,这个标志在GraphicBuffer分配缓冲时有指导作用setUsage(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);//定义一个GraphicBuffer,名字就叫backBuffer。sp<GraphicBuffer> backBuffer;//1 还记得我们说的2个元素的缓冲队列吗?下面的dequeueBuffer将取出一个空闲缓冲status_t err = dequeueBuffer(&backBuffer);if (err== NO_ERROR) {//2 锁住这块buffererr = lockBuffer(backBuffer.get());if(err == NO_ERROR) {const Rect bounds(backBuffer->width, backBuffer->height);Region scratch(bounds);Region& newDirtyRegion(dirtyIn ? *dirtyIn : scratch);......//mPostedBuffer是上一次绘画时使用的Buffer,也就是现在的frontBufferconst sp<GraphicBuffer>& frontBuffer(mPostedBuffer);if (frontBuffer !=0 &&backBuffer->width == frontBuffer->width &&backBuffer->height == frontBuffer->height &&!(mFlags & ISurfaceComposer::eDestroyBackbuffer)){const Region copyback(mOldDirtyRegion.subtract(newDirtyRegion));if (!copyback.isEmpty() && frontBuffer!=0) {//3 把frontBuffer中的数据拷贝到BackBuffer中,这是为什么?copyBlt(backBuffer, frontBuffer, copyback);}}mDirtyRegion = newDirtyRegion;mOldDirtyRegion = newDirtyRegion;void* vaddr;//调用GraphicBuffer的lock得到一块内存,内存地址被赋值给了vaddr,//后续的作画将在这块内存上展开status_t res = backBuffer->lock(GRALLOC_USAGE_SW_READ_OFTEN |GRALLOC_USAGE_SW_WRITE_OFTEN,newDirtyRegion.bounds(),&vaddr);mLockedBuffer = backBuffer;//other用来接收一些信息。other->w = backBuffer->width; //宽度信息other->h = backBuffer->height;other->s = backBuffer->stride;other->usage = backBuffer->usage;other->format = backBuffer->format;other->bits = vaddr; //最重要的是这个内存地址}}mApiLock.unlock();return err;
}
在上面的代码中,列出了三个关键点:
· 调用dequeueBuffer得到一个空闲缓冲,也可以叫空闲缓冲出队。
· 调用lockBuffer。
· 调用copyBlt函数,把frontBuffer数据拷贝到backBuffer中,这是为什么?
来分析这三个关键点。
(1)dequeueBuffer的分析
dequeueBuffer的目的很简单,就是选取一个空闲的GraphicBuffer,其代码如下所示:
[-->Surface.cpp]
status_t Surface::dequeueBuffer(sp<GraphicBuffer>* buffer) {android_native_buffer_t * out;status_t err = dequeueBuffer(&out);//调用另外一个dequeueBufferif(err == NO_ERROR) {*buffer = GraphicBuffer::getSelf(out);}return err;
}
这其中又调用了另外一个dequeueBuffer函数。它的代码如下所示:
[-->Surface.cpp]
int Surface::dequeueBuffer(android_native_buffer_t** buffer)
{sp<SurfaceComposerClient> client(getClient());//1 调用SharedBufferClient的dequeue函数,它返回当前空闲的缓冲号ssize_t bufIdx = mSharedBufferClient->dequeue();const uint32_t usage(getUsage());/*mBuffers就是我们前面在Surface创建中介绍的那个二元sp<GraphicBuffer>数组。这里定义的backBuffer是一个引用类型,也就是说如果修改backBuffer的信息,就相当于修改了mBuffers[bufIdx]*/const sp<GraphicBuffer> &backBuffer(mBuffers[bufIdx]);//mBuffers定义的GraphicBuffer使用的也是无参构造函数,所以此时还没有真实的存储被创建if(backBuffer == 0 || //第一次进来满足backBuffer为空这个条件((uint32_t(backBuffer->usage) & usage) != usage) ||mSharedBufferClient->needNewBuffer(bufIdx)){//调用getBufferLocked,需要进去看看。err = getBufferLocked(bufIdx, usage);if(err == NO_ERROR) {mWidth = uint32_t(backBuffer->width);mHeight = uint32_t(backBuffer->height);}}......
}
上面列出了一个关键点,就是SharedBufferClient的dequeue函数,暂且记住这个调用,后面会有单独章节分析生产/消费步调控制。先看getBufferLocked函数,其代码如下所示:
[-->Surface.cpp]
status_t Surface::getBufferLocked(int index, int usage)
{sp<ISurface> s(mSurface);status_t err = NO_MEMORY;//注意这个currentBuffer也被定义为引用类型sp<GraphicBuffer> ¤tBuffer(mBuffers[index]);//终于用上了ISurface对象,调用它的requestBuffer得到指定索引index的Buffersp<GraphicBuffer> buffer =s->requestBuffer(index, usage);if (buffer != 0) {err = mSharedBufferClient->getStatus();if(!err && buffer->handle != NULL) {//getBufferMapper返回GraphicBufferMapper对象//调用它的registerBuffer干什么?这个问题我们在4.7节回答err = getBufferMapper().registerBuffer(buffer->handle);if (err == NO_ERROR) {//把requestBuffer得到的值赋给currentBuffer,由于currentBuffer是引用类型,//实际上相当于mBuffers[index]=buffercurrentBuffer = buffer;//设置currentBuffer的编号currentBuffer->setIndex(index);mNeedFullUpdate = true;}} else {err = err<0 ? err : NO_MEMORY;}return err;
}
getBufferLocked的目的,已比较清晰了:
· 调用ISurface的requestBuffer得到一个GraphicBuffer对象,这个GraphicBuffer对象被设置到本地的mBuffers数组中。
我们已经知道,ISurface的Bn端实际上是定义在Layer.类中的SurfaceLayer,下面来看它实现的requestBuffer。由于SurfaceLayer是Layer的内部类,它的工作最终都会交给Layer来处理,所以这里可直接看Layer的requestBuffer函数:
[-->Layer.cpp]
sp<GraphicBuffer> Layer::requestBuffer(int index, int usage)
{sp<GraphicBuffer> buffer;sp<Client> ourClient(client.promote());//l cblk就是那个SharedBufferServer对象,下面这个调用确保index号GraphicBuffer//没有被SF当做FrontBuffer使用。status_t err = lcblk->assertReallocate(index);......if(err != NO_ERROR) {return buffer;}uint32_t w, h;{Mutex::Autolock _l(mLock);w= mWidth;h= mHeight;/*mBuffers是SF端创建的一个二元数组,这里取出第index个元素,之前说过,mBuffers使用的也是GraphicBuffer的无参构造函数,所以此时也没有真实存储被创建。*/buffer = mBuffers[index];mBuffers[index].clear();}const uint32_t effectiveUsage = getEffectiveUsage(usage);if(buffer!=0 && buffer->getStrongCount() == 1) {//2 分配物理存储,后面会分析这个。err = buffer->reallocate(w, h, mFormat, effectiveUsage);} else {buffer.clear();//使用GraphicBuffer的有参构造,这也使得物理存储被分配buffer = new GraphicBuffer(w, h, mFormat, effectiveUsage);err = buffer->initCheck();}......if(err == NO_ERROR && buffer->handle != 0) {Mutex::Autolock _l(mLock);if(mWidth && mHeight) {mBuffers[index] = buffer;mTextures[index].dirty = true;}else {buffer.clear();}}return buffer;
}
不管怎样,此时跨进程的这个requestBuffer返回的GraphicBuffer,已经和一块物理存储绑定到一起了。所以dequeueBuffer顺利返回了它所需的东西。接下来则需调用lockBuffer。
(2)lockBuffer的分析
lockBuffer的代码如下所示:
[-->Surface.cpp]
int Surface::lockBuffer(android_native_buffer_t*buffer)
{sp<SurfaceComposerClient> client(getClient());status_t err = validate();int32_t bufIdx = GraphicBuffer::getSelf(buffer)->getIndex();err = mSharedBufferClient->lock(bufIdx); //调用SharedBufferClient的lockreturn err;
}
来看这个lock函数:
[-->SharedBufferStack.cpp]
status_t SharedBufferClient::lock(int buf)
{LockCondition condition(this, buf);//这个buf是BackBuffer的索引号status_t err = waitForCondition(condition);return err;
}
注意,给waitForCondition函数传递的是一个LockCondition类型的对象,前面所说的函数对象的作用将在这里见识到,先看waitForCondition函数:
[-->SharedBufferStack.h]
template <typename T> //这是一个模板函数
status_t SharedBufferBase::waitForCondition(T condition)
{constSharedBufferStack& stack( *mSharedStack );SharedClient& client( *mSharedClient );constnsecs_t TIMEOUT = s2ns(1);Mutex::Autolock _l(client.lock);while((condition()==false) && //注意这个condition()的用法(stack.identity == mIdentity) &&(stack.status == NO_ERROR)){status_t err = client.cv.waitRelative(client.lock, TIMEOUT);if(CC_UNLIKELY(err != NO_ERROR)) {if (err == TIMED_OUT) {if (condition()) { //注意这个:condition(),condition是一个对象break;} else {}} else {return err;}}}return (stack.identity != mIdentity) ? status_t(BAD_INDEX) : stack.status;
}
waitForCondition函数比较简单,就是等待一个条件为真,这个条件是否满足由condition()这条语句来判断。但这个condition不是一个函数,而是一个对象,这又是怎么回事?这就是Funcition Object(函数对象)的概念。函数对象的本质是一个对象,不过是重载了操作符(),这和重载操作符+、-等没什么区别。可以把它当作是一个函数来看待。
为什么需要函数对象呢?因为对象可以保存信息,所以调用这个对象的()函数就可以利用这个对象的信息了。
来看condition对象的()函数。刚才传进来的是LockCondition,它的()定义如下:
[-->SharedBufferStack.cpp]
bool SharedBufferClient::LockCondition::operator()() {//stack、buf等都是这个对象的内部成员,这个对象的目的就是根据读写位置判断这个buffer是否空闲。return(buf != stack.head || (stack.queued > 0 && stack.inUse != buf));
}
SharedBufferStack的读写控制,比Audio中的环形缓冲看起来要简单,实际上它却比较复杂。本章会在扩展部分进行分析。这里给读者准备一个问题,也是我之前百思不得其解的问题:既然已经调用dequeue得到了一个空闲缓冲,为什么这里还要lock呢?
(3)拷贝旧数据
在第三个关键点中,可看到这样的代码:
[-->Surface.cpp]
//3 把frontBuffer中的数据拷贝到BackBuffer中,这是为什么?
copyBlt(backBuffer, frontBuffer, copyback);
上面这段代码所解决的,其实是下面这个问题:
在大部分情况下,UI只有一小部分会发生变化(例如一个按钮被按下去,导致颜色发生变化),这一小部分UI只对应整个GraphicBuffer中的一小块存储(就是在前面代码中见到的dirtyRegion),如果整块存储都更新,则会极大地浪费资源。怎么办?
这就需要将变化的图像和没有发生变化的图像进行叠加。上一次绘制的信息保存在mPostedBuffer中,而这个mPostedBuffer则要在unLockAndPost函数中设置。这里将根据需要,把mPostedBuffer中的旧数据拷贝到BackBuffer中。后续的绘画只要更新脏区域就可以了,这会节约不少资源。
OK,lockCanvas返回后,应用层将在这块画布上尽情作画。假设现在已经在BackBuffer上绘制好了图像,下面就要通过unlockCanvasAndPost进行后续工作了。一起来看。
2. unlockCanvasAndPost的分析
进入精简流程的最后一步,就是unlockCanvasAndPost函数,它的代码如下所示:
[-->Surface.cpp]
status_t Surface::unlockAndPost()
{//调用GraphicBuffer的unlock函数status_t err = mLockedBuffer->unlock();//get返回这个GraphicBuffer的编号,queueBuffer将含有新数据的缓冲加入队中。err = queueBuffer(mLockedBuffer.get());mPostedBuffer = mLockedBuffer; //保存这个BackBuffer为mPostedBuffermLockedBuffer = 0;returnerr;
}
来看queueBuffer调用,代码如下所示:
[-->Surface.cpp]
int Surface::queueBuffer(android_native_buffer_t* buffer)
{ sp<SurfaceComposerClient> client(getClient());int32_t bufIdx =GraphicBuffer::getSelf(buffer)->getIndex();//设置脏RegionmSharedBufferClient->setDirtyRegion(bufIdx,mDirtyRegion);//更新写位置。err =mSharedBufferClient->queue(bufIdx);if (err== NO_ERROR) {//client是BpSurfaceFlinger,调用它的signalServer,这样SF就知道新数据准备好了client->signalServer();}return err;
}
这里,与读写控制有关的是queue函数,其代码如下所示:
[-->SharedBufferStack.cpp]
status_t SharedBufferClient::queue(int buf)
{//QueueUpdate也是一个函数对象QueueUpdate update(this);//调用updateCondition函数。status_t err = updateCondition( update );SharedBufferStack& stack( *mSharedStack );const nsecs_t now = systemTime(SYSTEM_TIME_THREAD);stack.stats.totalTime = ns2us(now - mDequeueTime[buf]);return err;
}
这个updateCondition函数的代码如下所示:
[-->SharedBufferStack.h]
template <typename T>
status_t SharedBufferBase::updateCondition(Tupdate) {SharedClient& client( *mSharedClient );Mutex::Autolock _l(client.lock);ssize_t result = update();//调用update对象的()函数client.cv.broadcast(); //触发同步对象return result;
}
updateCondition函数和前面介绍的waitForCondition函数一样,都是使用的函数对象。queue操作使用的是QueueUpdate类,关于它的故事,将在拓展部分讨论。
3. lockCanvas和unlockCanvasAndPost的总结
总结一下lockCanvas和unlockCanvasAndPost这两个函数的工作流程,用图20表示:
图20 lockCanvas和unlockCanvasAndPost流程总结
4.6 GraphicBuffer的介绍
GraphicBuffer是Surface系统中一个高层次的显示内存管理类,它封装了和硬件相关的一些细节,简化了应用层的处理逻辑。先来认识一下它。
1. 初识GraphicBuffer
GraphicBuffer的代码如下所示:
[-->GraphicBuffer.h]
class GraphicBuffer:public EGLNativeBase<android_native_buffer_t, GraphicBuffer, LightRefBase<GraphicBuffer>>,public Flattenable
其中,EGLNativeBase是一个模板类。它的定义,代码如下所示:
[-->Android_natives.h]
template <typename NATIVE_TYPE, typenameTYPE, typename REF>
class EGLNativeBase : public NATIVE_TYPE, public REF
通过替换,可得到GraphicBuffer的派生关系,如图21所示:
图21 GraphicBuffer派生关系的示意图
从图中可以看出:
· 从LightRefBase派生使GraphicBuffer支持轻量级的引用计数控制。
· 从Flattenable派生使GraphicBuffer支持序列化,它的flatten和unflatten函数用于序列化和反序列化,这样,GraphicBuffer的信息就可以存储到Parcel包中并被Binder传输了。
另外,图中的android_native_buffer_t是GraphicBuffer的父类,它是一个struct结构体。可以将C++语言中的struct和class当作同一个东西,所以GraphicBuffer能从它派生。其代码如下所示:
[-->android_native_buffer.h]
typedef struct android_native_buffer_t
{
#ifdef __cplusplusandroid_native_buffer_t() {common.magic = ANDROID_NATIVE_BUFFER_MAGIC;common.version = sizeof(android_native_buffer_t);memset(common.reserved, 0, sizeof(common.reserved));}
#endif//这个android_native_base_t是struct的第一个成员,根据C/C++编译的特性,这个成员//在它的派生类对象所占有的内存中也是排第一个。struct android_native_base_t common;int width;int height;int stride;int format;int usage;void* reserved[2];//这是一个关键成员,保存一些和显示内存分配/管理相关的内容buffer_handle_t handle;void *reserved_proc[8];
} android_native_buffer_t;
GraphicBuffer和显示内存分配相关的部分主要集中在buffer_handle_t这个变量上,它实际上是一个指针,定义如下:
[-->gralloc.h]
typedef const native_handle* buffer_handle_t;
native_handle的定义如下:
[-->native_handle.h]
typedef struct
{int version; /* version值为sizeof(native_handle_t) */int numFds; int numInts; int data[0]; /* data是数据存储空间的首地址 */
} native_handle_t;
typedef native_handle_t native_handle;
读者可能要问,一个小小的GraphicBuffer为什么这么复杂?要回答这个问题,应先对GraphicBuffer有比较全面的了解。按照图8-20中的流程来看GraphicBuffer。
2. GraphicBuffer和存储的分配
GraphicBuffer的构造函数最有可能分配存储了。注意,流程中使用的是无参构造函数,所以应先看无参构造函数。
(1)无参构造函数的分析
代码如下所示:
[-->GraphicBuffer.cpp]
GraphicBuffer::GraphicBuffer():BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),mInitCheck(NO_ERROR), mVStride(0), mIndex(-1)
{/*其中mBufferMapper为GraphicBufferMapper类型,它的创建采用的是单例模式,也就是每个进程只有一个GraphicBufferMapper对象,读者可以去看看get的实现。*/width =height=stride=format=usage = 0;handle= NULL; //handle为空
}
在无参构造函数中没有发现和存储分配有关的操作。那么,根据流程,下一个有可能的地方就是reallocate函数了。
(2)reallocate的分析
Reallocate的代码如下所示:
[-->GraphicBuffer.cpp]
status_t GraphicBuffer::reallocate(uint32_t w, uint32_t h, PixelFormat f, uint32_t reqUsage)
{if(mOwner != ownData)return INVALID_OPERATION;if(handle) { //handle值在无参构造函数中初始化为空,所以不满足if的条件GraphicBufferAllocator& allocator(GraphicBufferAllocator::get());allocator.free(handle);handle = 0;}return initSize(w, h, f, reqUsage);//调用initSize函数
}
InitSize函数的代码如下所示:
[-->GraphicBuffer.cpp]
status_t GraphicBuffer::initSize(uint32_t w,uint32_t h, PixelFormat format,uint32_t reqUsage)
{if(format == PIXEL_FORMAT_RGBX_8888)format = PIXEL_FORMAT_RGBA_8888;/*GraphicBufferAllocator才是真正的存储分配的管理类,它的创建也是采用的单例模式,也就是每个进程只有一个GraphicBufferAllocator对象*/GraphicBufferAllocator& allocator =GraphicBufferAllocator::get();//调用GraphicBufferAllocator的alloc来分配存储,注意handle作为指针//被传了进去,看来handle的值会被修改status_t err = allocator.alloc(w, h, format, reqUsage, &handle,&stride);if(err == NO_ERROR) {this->width = w;this->height = h;this->format = format;this->usage = reqUsage;mVStride = 0;}return err;
}
(3)GraphicBufferAllocator的介绍
从上面的代码中可以发现,GraphicBuffer的存储分配和GraphicBufferAllocator有关。一个小小的存储分配为什么需要经过这么多道工序呢?还是先来看GraphicBufferAllocator,代码如下所示:
[-->GraphicBufferAllocator.cpp]
GraphicBufferAllocator::GraphicBufferAllocator():mAllocDev(0)
{hw_module_t const* module;//调用hw_get_module,得到hw_module_tint err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);if (err == 0) {//调用gralloc_open函数,注意我们把module参数传了进去。gralloc_open(module, &mAllocDev);}
}
GraphicBufferAllocator在创建时,会首先调用hw_get_module取出一个hw_module_t类型的对象。从名字上看,它和硬件平台有关系。它会加载一个叫libgralloc.硬件平台名.so的动态库。比如,我的HTC G7手机上加载的库是/system/lib/hw/libgraolloc.qsd-8k.so。这个库的源代码在hardware/msm7k/libgralloc-qsd8k目录下。
这个库有什么用呢?简言之,就是为了分配一块用于显示的内存,但为什么需要这种层层封装呢?答案很简单:封装的目的就是为了屏蔽不同硬件平台的差别。
读者可通过执行adb getprop ro.board.platform命令,得到具体手机上硬件平台的名字。图22总结了GraphicBufferAllocator分配内存的途径。
图22 GraphicBufferAllocator内存的分配途径
注意,这里是以G7的libgralloc.qsk-8k.so为示例的。其中pmem设备用来创建一块连续的内存,因为有些硬件设备(例如Camera)工作时需要使用一块连续的内存,对于这种情况,一般就会使用pmem设备来分配内存。
这里,仅讨论图22中与硬件无关的分配方式。在这种情况下,将使用ashmem分配共享内存。下面看GraphicBufferAllocator的alloc函数,其代码如下所示:
[-->GraphicBufferAllocator.cpp]
status_t GraphicBufferAllocator::alloc(uint32_tw, uint32_t h, PixelFormat format, int usage, buffer_handle_t* handle, int32_t* stride)
{//根据前面的定义可知buffer_handle_t为native_handle_t*类型status_t err;if (usage & GRALLOC_USAGE_HW_MASK) {err = mAllocDev->alloc(mAllocDev, w, h, format, usage, handle, stride);} else {//SW分配,可以做到和HW无关了。err = sw_gralloc_handle_t::alloc(w, h, format, usage, handle, stride);}......return err;
}
下面,来看软件分配的方式:
[-->GraphicBufferAllocator.cpp]
status_t sw_gralloc_handle_t::alloc(uint32_t w,uint32_t h, int format,int usage, buffer_handle_t* pHandle, int32_t*pStride)
{int align = 4;int bpp = 0;......//格式转换size_t bpr = (w*bpp + (align-1)) & ~(align-1);size_t size = bpr * h;size_t stride = bpr / bpp;size = (size + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1);//直接使用了ashmem创建共享内存int fd = ashmem_create_region("sw-gralloc-buffer", size);......//进行内存映射,得到共享内存起始地址void*base = mmap(0, size, prot, MAP_SHARED, fd, 0);sw_gralloc_handle_t* hnd = new sw_gralloc_handle_t();hnd->fd = fd;//保存文件描述符hnd->size = size;//保存共享内存的大小hnd->base = intptr_t(base);//intptr_t将void*类型转换成int*类型hnd->prot = prot;//保存属性*pStride = stride;*pHandle = hnd; //pHandle就是传入的那个handle变量的指针,这里对它进行赋值return NO_ERROR;
}
我们知道,调用GraphicBuffer的reallocate函数后,会导致物理存储被分配。前面曾说过,Layer会创建两个GraphicBuffer,而Native Surface端也会创建两个GraphicBuffer,那么这两个GraphicBuffer是怎么建立联系的呢?
(4)flatten和unflatten的分析
试想,Native Surface的GraphicBuffer是怎么和Layer的GraphicBuffer建立联系的:
先通过requestBuffer函数返回一个GraphicBuffer,然后这个GraphicBuffer被Native Surface保存。
这中间的过程其实是一个mini版的乾坤挪移,来看看,代码如下所示:
[-->ISurface.cpp]
//requestBuffer的响应端
status_t BnSurface::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{switch(code) {case REQUEST_BUFFER: {CHECK_INTERFACE(ISurface, data, reply);int bufferIdx = data.readInt32();int usage = data.readInt32();sp<GraphicBuffer> buffer(requestBuffer(bufferIdx, usage));....../*requestBuffer的返回值被写到Parcel包中,由于GraphicBuffer从Flattenable类派生,这将导致它的flatten函数被调用*/return reply->write(*buffer);}......
}//再来看请求端的处理,在BpSurface中
virtual sp<GraphicBuffer> requestBuffer(intbufferIdx, int usage)
{Parcel data, reply;data.writeInterfaceToken(ISurface::getInterfaceDescriptor());data.writeInt32(bufferIdx);data.writeInt32(usage);remote()->transact(REQUEST_BUFFER, data, &reply);sp<GraphicBuffer> buffer = new GraphicBuffer();reply.read(*buffer);//Parcel调用unflatten函数把信息反序列化到这个buffer中。return buffer;//requestBuffer实际上返回的是本地new出来的这个GraphicBuffer
}
通过上面的代码可以发现,挪移的关键体现在flatten和unflatten函数上。请看:
[1] flatten的分析
flatten的代码如下所示:
[-->GraphicBuffer.cpp]
status_t GraphicBuffer::flatten(void* buffer,size_t size, int fds[], size_t count) const
{//buffer是装载数据的缓冲区,由Parcel提供......if(handle) {buf[6] = handle->numFds;buf[7] = handle->numInts;native_handle_t const* const h = handle;//把handle的信息也写到buffer中memcpy(fds, h->data, h->numFds*sizeof(int));memcpy(&buf[8], h->data + h->numFds,h->numInts*sizeof(int));}return NO_ERROR;
}
flatten的工作就是把GraphicBuffer的handle变量信息拷贝到Parcel包中。那么接收端如何使用这个包呢?这就是unflatten的工作了。
[2] unflatten分析
unflatten的代码如下所示:
[-->GraphicBuffer.cpp]
status_t GraphicBuffer::unflatten(void const*buffer, size_t size, int fds[], size_t count)
{......if(numFds || numInts) {width = buf[1];height = buf[2];stride = buf[3];format = buf[4];usage = buf[5];native_handle* h =native_handle_create(numFds, numInts);memcpy(h->data, fds, numFds*sizeof(int));memcpy(h->data + numFds, &buf[8],numInts*sizeof(int));handle = h;//根据Parcel包中的数据还原一个handle} else{width = height = stride = format = usage = 0;handle = NULL;}mOwner = ownHandle;return NO_ERROR;
}
unflatten最重要的工作是,根据Parcel包中native_handle的信息,在Native Surface端构造一个对等的GraphicBuffer。这样,Native Surface端的GraphicBuffer实际上就和Layer端的GraphicBuffer管理着同一块共享内存。
3. registerBuffer的分析
registerBuffer有什么用呢?上一步调用unflatten后得到了代表共享内存的文件句柄,regiserBuffer的目的就是对它进行内存映射,代码如下所示:
[-->GraphicBufferMapper.cpp]
status_t sw_gralloc_handle_t::registerBuffer(sw_gralloc_handle_t* hnd)
{if (hnd->pid != getpid()) {//原来是做一次内存映射操作void* base = mmap(0, hnd->size, hnd->prot, MAP_SHARED, hnd->fd,0);......//base保存着共享内存的起始地址hnd->base = intptr_t(base);}return NO_ERROR;
}
4. lock和unlock的分析
GraphicBuffer在使用前需要通过lock来得到内存地址,使用完后又会通过unlock释放这块地址。在SW分配方案中,这两个函数实现却非常简单,如下所示:
[-->GraphicBufferMapper.cpp]
//lock操作
int sw_gralloc_handle_t::lock(sw_gralloc_handle_t*hnd, int usage,int l, int t, int w, int h, void** vaddr)
{*vaddr= (void*)hnd->base; //得到共享内存的起始地址,后续作画就使用这块内存了。return NO_ERROR;
}//unlock操作
status_t sw_gralloc_handle_t::unlock(sw_gralloc_handle_t* hnd)
{return NO_ERROR;//没有任何操作
}
对GraphicBuffer的介绍就到这里。虽然采用的是SW方式,但是相信读者也能通过树木领略到森林的风采。从应用层角度看,可以把GraphicBuffer当做一个构架在共享内存之上的数据缓冲。
4.7 深入分析Surface总结
Surface系统最难的部分,是这个Native Surface的创建和使用,它包括三个方面:
· Activity的UI和Surface的关系是怎样的?这是第2节回答的问题。
· Activity中所使用的Surface是怎么和SurfaceFlinger挂上关系的?这是第3节回答的问题。
· 本节对第2个问题进行了较深入的研究,分析了Surface和SurfaceFlinger之间的关系,以及生产/消费步调的中枢控制机构SharedBuffer家族和数据的承载者GraphicBuffer。
从上面分析可看出,本章前四节均围绕着这个Surface讲解,一路下来确实遇到了不少曲折和坎坷,望读者跟着源码反复阅读,体会。
5 SurfaceFlinger的分析
这一节要对SurfaceFlinger进行分析。相比较而言,SurfaceFlinger不如AudioFlinger复杂。
5.1 SurfaceFlinger的诞生
SurfaceFlinger驻留于system_server进程,这一点和Audio系统的几个Service不太一样。它创建的位置在SystemServer的init1函数中。虽然位于SystemServer这个重要进程中,但是SF创建的代码却略显波澜不惊,没有什么特别之处。SF的创建首先会调用instantiate函数,代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::instantiate() {defaultServiceManager()->addService(String16("SurfaceFlinger"), new SurfaceFlinger());
}
前面在图14中指出了SF,同时从BnSurfaceComposer和Thread类中派生,相关代码如下所示:
class SurfaceFlinger : public BnSurfaceComposer, protected Thread
从Thread派生这件事给了我们一个很明确的提示:
· SurfaceFlinger会单独启动一个工作线程。
我们知道,Thread类的工作线程要通过调用它的run函数来创建,那这个run函数是在什么地方调用的呢?当然,最有可能的就是在构造函数中:
[-->SurfaceFlinger.cpp]
SurfaceFlinger::SurfaceFlinger(): BnSurfaceComposer(), Thread(false),mTransactionFlags(0),mTransactionCount(0),mResizeTransationPending(false),mLayersRemoved(false),mBootTime(systemTime()),mHardwareTest("android.permission.HARDWARE_TEST"),mAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER"),mDump("android.permission.DUMP"),mVisibleRegionsDirty(false),mDeferReleaseConsole(false),mFreezeDisplay(false),mFreezeCount(0),mFreezeDisplayTime(0),mDebugRegion(0),mDebugBackground(0),mDebugInSwapBuffers(0),mLastSwapBufferTime(0),mDebugInTransaction(0),mLastTransactionTime(0),mBootFinished(false),mConsoleSignals(0),mSecureFrameBuffer(0)
{init();//上面没有调用run。必须到init去检查一番。
}//init函数更简单了。
void SurfaceFlinger::init()
{char value[PROPERTY_VALUE_MAX];property_get("debug.sf.showupdates", value, "0");mDebugRegion = atoi(value);property_get("debug.sf.showbackground", value, "0");mDebugBackground = atoi(value);
}
上面的代码竟然没有创建工作线程?难道在其他地方?读者别急着在文件中搜索“run”,先猜测一下答案。
· 根据之前所学的知识,另外一个最有可能的地方就是onFirstRef函数了,这个函数在对象第一次被sp化后调用,很多初始化的工作也可以在这个函数中完成。
事实是这样吗?一起来看。
1. onFirstRef的分析
onFirstRef的代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::onFirstRef()
{//真是梦里寻他千百度,果然是在onFirstRef中创建了工作线程run("SurfaceFlinger",PRIORITY_URGENT_DISPLAY);/*mReadyToRunBarrier类型为Barrier,这个类就是封装了一个Mutex对象和一个Condition对象。如果读者还记得有关同步类的介绍,理解这个Barrier就非常简单了。下面调用的wait函数表示要等待一个同步条件的满足。*/mReadyToRunBarrier.wait();
}
onFirstRef创建工作线程后,将等待一个同步条件,那么这个同步条件在哪里被触发呢?相信不用多说 大家也知道:
在工作线程中被触发,而且极有可能是在readyToRun函数中。
2. readyToRun的分析
SF的readyToRun函数将完成一些初始化工作,代码如下所示:
[-->SurfaceFlinger.cpp]
status_t SurfaceFlinger::readyToRun()
{int dpy = 0;{//1 GraphicPlane是什么?GraphicPlane& plane(graphicPlane(dpy));//2 为这个GraphicPlane设置一个HAL对象——DisplayHardwareDisplayHardware* const hw = new DisplayHardware(this, dpy);plane.setDisplayHardware(hw);}//创建Surface系统中的“CB”对象,按照老规矩,应该先创建一块共享内存,然后使用placement newmServerHeap = new MemoryHeapBase(4096,MemoryHeapBase::READ_ONLY,"SurfaceFlingerread-only heap");/*注意这个“CB“对象的类型是surface_flinger_cblk_t。为什么在CB上打引号呢?因为这个对象谈不上什么控制,只不过被用来存储一些信息罢了。其控制作用完全达不到audio_track_cblk_t的程度。基于这样的事实,我们把前面提到的SharedBuffer家族称之为CB对象。*/mServerCblk = static_cast<surface_flinger_cblk_t*>(mServerHeap->getBase());//placement new创建surface_flinger_cblk_tnew(mServerCblk) surface_flinger_cblk_t;const GraphicPlane& plane(graphicPlane(dpy));const DisplayHardware& hw = plane.displayHardware();const uint32_t w = hw.getWidth();const uint32_t h = hw.getHeight();const uint32_t f = hw.getFormat();hw.makeCurrent();//当前只有一块屏mServerCblk->connected |= 1<<dpy;//屏幕在“CB”对象中的代表是display_cblk_tdisplay_cblk_t* dcblk = mServerCblk->displays + dpy;memset(dcblk, 0, sizeof(display_cblk_t));dcblk->w = plane.getWidth();dcblk->h = plane.getHeight();......//获取屏幕信息//还用上了内联汇编语句。asmvolatile ("":::"memory");/*下面是一些和OpenGL相关的函数调用。读者如感兴趣,可以研究一下,至少SurfaceFlinger.cpp中所涉及的相关代码还不算难懂*/glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, 0);......glOrthof(0, w, h, 0, 0, 1);//LayerDim是Dim类型的LayerLayerDim::initDimmer(this, w, h);//还记得在onFirstRef函数中的wait吗?下面的open将触发这个同步条件mReadyToRunBarrier.open();//资源准备好后,init将启动bootanim程序,这样就见到开机动画了。property_set("ctl.start", "bootanim");return NO_ERROR;
}
在上面的代码中,列出了两个关键点,下面一一进行分析。
(1)GraphicPlane的介绍
GraphicPlane是屏幕在SF代码中的对应物,根据前面的介绍,目前Android只支持一块屏幕,所以SF定义了一个一元数组:
GraphicPlane mGraphicPlanes[1];
GraphicPlane虽无什么特别之处,但它有一个重要的函数,叫setDisplayHardware,这个函数把代表显示设备的HAL对象和GraphicPlane关联起来。这也是下面要介绍的第二个关键点DisplayHardware。
(2)DisplayHardware的介绍
从代码上看,这个和显示相关的HAL对象是在工作线程中new出来的,先看它的构造函数,代码如下所示:
[-->DisplayHardware.cpp]
DisplayHardware::DisplayHardware(const sp<SurfaceFlinger>& flinger,uint32_t dpy):DisplayHardwareBase(flinger, dpy)
{init(dpy); //最重要的是这个init函数。
}
init函数非常重要,应进去看看。下面先思考一个问题。
前面在介绍FrameBuffer时说过,显示这一块需要使用FrameBuffer,但在GraphicBuffer中用的却是ashmem创建的共享内存。也就是说,之前在共享内存中绘制的图像和FrameBuffer没有什么关系。那么FrameBuffer是在哪里创建的呢?
答案就在init函数中,代码如下所示:
[-->DisplayHardware.cpp]
void DisplayHardware::init(uint32_t dpy)
{//FrameBufferNativeWindow实现了对FrameBuffer的管理和操作,该类中创建了两个//FrameBuffer,分别起到FrontBuffer和BackBuffer的作用。mNativeWindow = new FramebufferNativeWindow();framebuffer_device_t const * fbDev = mNativeWindow->getDevice();mOverlayEngine = NULL;hw_module_t const* module;//Overlay相关if(hw_get_module(OVERLAY_HARDWARE_MODULE_ID, &module) == 0) {overlay_control_open(module, &mOverlayEngine);}......EGLint w, h, dummy;EGLint numConfigs=0;EGLSurface surface;EGLContext context;mFlags = CACHED_BUFFERS;//EGLDisplay在EGL中代表屏幕EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);....../*surface是EGLSurface类型,下面这个函数会将EGL和Android中的Display系统绑定起来,后续就可以利用OpenGL在这个Surface上绘画,然后通过eglSwappBuffers输出图像了。*/surface = eglCreateWindowSurface(display, config, mNativeWindow.get(),NULL);......mDisplay = display;mConfig = config;mSurface = surface;mContext = context;mFormat = fbDev->format;mPageFlipCount = 0;
}
根据上面的代码,现在可以回答前面的问题了:
· SF创建FrameBuffer,并将各个Surface传输的数据(通过GraphicBuffer共享内存)混合后,再由自己传输到FrameBuffer中进行显示。
5.2 SF工作线程的分析
SF中的工作线程就是来做图像混合的,比起AudioFlinger来,它相当简单,下面是它的代码:
[-->SurfaceFlinger.cpp]
bool SurfaceFlinger::threadLoop()
{waitForEvent();//1 等待什么事件呢?if (UNLIKELY(mConsoleSignals)) {handleConsoleEvents();}if(LIKELY(mTransactionCount == 0)) {const uint32_t mask = eTransactionNeeded | eTraversalNeeded;uint32_t transactionFlags = getTransactionFlags(mask);if(LIKELY(transactionFlags)) {//Transaction(事务)处理,放到本节最后来讨论handleTransaction(transactionFlags);}}//2 处理PageFlipping工作handlePageFlip();constDisplayHardware& hw(graphicPlane(0).displayHardware());if (LIKELY(hw.canDraw() && !isFrozen())) {//3 处理重绘handleRepaint();hw.compositionComplete();//4 投递BackBufferunlockClients();postFramebuffer();} else{unlockClients();usleep(16667);}return true;
}
ThreadLoop一共有四个关键点,这里,分析除Transaction外的四个关键点。
1. waitForEvent
SF工作线程一上来就等待事件,它会是什么事件呢?来看代码:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::waitForEvent()
{while(true) {nsecs_t timeout = -1;const nsecs_t freezeDisplayTimeout = ms2ns(5000);......MessageList::value_type msg = mEventQueue.waitMessage(timeout);......//还有一些和冻屏相关的内容if(msg != 0) {switch (msg->what) {//千辛万苦就等这一个重绘消息case MessageQueue::INVALIDATE:return;}}}
}
SF收到重绘消息后,将退出等待。那么,是谁发送的这个重绘消息呢?还记得在unlockCanvasAndPost函数中调用的signal吗?它在SF端的实现代码如下:
[-->SurfaceFlinger]
void SurfaceFlinger::signal() const {const_cast<SurfaceFlinger*>(this)->signalEvent();
}void SurfaceFlinger::signalEvent() {mEventQueue.invalidate(); //往消息队列中加入INVALIDATE消息
}
2. 分析handlePageFlip
SF工作线程从waitForEvent中返回后,下一步要做的就是处理事务和handlePageFlip了。先看handlePageFlip,从名字上可知,它和PageFlipping工作有关。
注意:事务处理将在5.3节中介绍。
代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::handlePageFlip()
{bool visibleRegions = mVisibleRegionsDirty;/*还记得前面所说的mCurrentState吗?它保存了所有显示层的信息,而绘制的时候使用的mDrawingState则保存了当前需要显示的显示层信息。*/LayerVector& currentLayers = const_cast<LayerVector&>(mDrawingState.layersSortedByZ);//1 调用lockPageFlipvisibleRegions |= lockPageFlip(currentLayers);const DisplayHardware& hw =graphicPlane(0).displayHardware();//取得屏幕的区域const Region screenRegion(hw.bounds());if (visibleRegions) {Region opaqueRegion;computeVisibleRegions(currentLayers, mDirtyRegion,opaqueRegion);mWormholeRegion = screenRegion.subtract(opaqueRegion);mVisibleRegionsDirty = false;}//2 调用unlockPageFlipunlockPageFlip(currentLayers);mDirtyRegion.andSelf(screenRegion);
}
hanldePageFlip调用了两个看起来是一对的函数:lockPageFlip和unlockPageFlip。这两个函数会干些什么呢?
(1)lockPageFlip的分析
先看lockPageFlip函数,代码如下所示:
[-->SurfaceFlinger.cpp]
bool SurfaceFlinger::lockPageFlip(const LayerVector& currentLayers)
{bool recomputeVisibleRegions = false;size_t count = currentLayers.size();sp<LayerBase> const* layers = currentLayers.array();for(size_t i=0 ; i<count ; i++) {const sp<LayerBase>& layer = layers[i];//调用每个显示层的lockPageFliplayer->lockPageFlip(recomputeVisibleRegions);}return recomputeVisibleRegions;
}
假设当前的显示层是Layer类型,那么得转到Layer类去看它的lockPageFlip函数,代码如下所示:
[-->Layer.cpp]
void Layer::lockPageFlip(bool&recomputeVisibleRegions)
{//lcblk是SharedBufferServer类型,调用retireAndLock函数将返回FrontBuffer的索引号ssize_t buf = lcblk->retireAndLock();......mFrontBufferIndex = buf;//得到FrontBuffer对应的GraphicBuffersp<GraphicBuffer> newFrontBuffer(getBuffer(buf));if (newFrontBuffer != NULL) {//取出脏区域const Region dirty(lcblk->getDirtyRegion(buf));//和GraphicBuffer所表示的区域进行裁剪,得到一个脏区域mPostedDirtyRegion = dirty.intersect( newFrontBuffer->getBounds() );const Layer::State& front(drawingState());if(newFrontBuffer->getWidth() ==front.requested_w &&newFrontBuffer->getHeight() ==front.requested_h){if ((front.w != front.requested_w) ||(front.h != front.requested_h)){...... //需要重新计算可见区域recomputeVisibleRegions = true;}mFreezeLock.clear();}} else{mPostedDirtyRegion.clear();}if(lcblk->getQueuedCount()) {mFlinger->signalEvent();}/*如果脏区域不为空,则需要绘制成纹理,reloadTexture将绘制一张纹理保存在mTextures数组中,里边涉及很多OpenGL的操作,读者有兴趣可以自己研究。*/if(!mPostedDirtyRegion.isEmpty()) {reloadTexture( mPostedDirtyRegion );}
}
我们知道,Layer的lockPageFlip将根据FrontBuffer的内容生成一张纹理。那么,unlockPageFlip会做些什么呢?
(2)unlockPageFlip的分析
unlockPageFlip的代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers)
{const GraphicPlane& plane(graphicPlane(0));const Transform& planeTransform(plane.transform());size_t count = currentLayers.size();sp<LayerBase> const* layers = currentLayers.array();for(size_t i=0 ; i<count ; i++) {const sp<LayerBase>& layer = layers[i];//调用每个显示层的unlockPageFlip,Layer的unlockPageFlip主要做一些区域的清理工作,读者可以自己看看。layer->unlockPageFlip(planeTransform, mDirtyRegion);}
}
(3)handlePageFlip的总结
handlePageFlip的工作其实很简单,以Layer类型为例来总结一下:
各个Layer需要从FrontBuffer中取得新数据,并生成一张OpenGL中的纹理。纹理可以看做是一个图片,这个图片的内容就是FrontBuffer中的图像。
现在每一个Layer都准备好了新数据,下一步的工作当然就是绘制了。来看handleRepaint函数。
3. 分析handleRepaint函数
handleRepaint函数的代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::handleRepaint()
{mInvalidRegion.orSelf(mDirtyRegion);if(mInvalidRegion.isEmpty()) {return;}......const DisplayHardware& hw(graphicPlane(0).displayHardware());glMatrixMode(GL_MODELVIEW);glLoadIdentity();uint32_t flags = hw.getFlags();if((flags & DisplayHardware::SWAP_RECTANGLE) ||(flags & DisplayHardware::BUFFER_PRESERVED)){......//计算mDirtyRegion}// 在脏区域上进行绘制composeSurfaces(mDirtyRegion);mDirtyRegion.clear();
}
其中,composeSurfaces将不同的显示层内容进行混合,其实就是按Z轴的顺序由里到外依次绘制。当然,最后绘制的数据有可能遮盖前面绘制的数据,代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::composeSurfaces(constRegion& dirty)
{const SurfaceFlinger& flinger(*this);const LayerVector& drawingLayers(mDrawingState.layersSortedByZ);const size_t count = drawingLayers.size();sp<LayerBase> const* const layers = drawingLayers.array();for(size_t i=0 ; i<count ; ++i) {const sp<LayerBase>& layer = layers[i];const Region& visibleRegion(layer->visibleRegionScreen);if(!visibleRegion.isEmpty()) {const Region clip(dirty.intersect(visibleRegion));if (!clip.isEmpty()) {layer->draw(clip); //调用各个显示层的draw函数}}}
}
draw函数在LayerBase类中实现,代码如下所示:
[-->LayerBase.cpp]
void LayerBase::draw(const Region& inClip)const
{......glEnable(GL_SCISSOR_TEST);onDraw(clip); //调用子类的onDraw函数
}
至于Layer是怎么实现这个onDraw函数的,代码如下所示:
[-->Layer.cpp]
void Layer::onDraw(const Region& clip) const
{int index = mFrontBufferIndex;if(mTextures[index].image == EGL_NO_IMAGE_KHR)index = 0;GLuint textureName = mTextures[index].name;....Region holes(clip.subtract(under));if(!holes.isEmpty()) {clearWithOpenGL(holes);}return;}//index是FrontBuffer对应生成的纹理,在lockPageFlip函数中就已经生成了。drawWithOpenGL(clip,mTextures[index]);//将纹理画上去,里面有很多和OpenGL相关内容
}
drawWithOpenGL函数由LayerBase实现,看它是不是使用了这张纹理,代码如下所示:
[-->LayerBase.cpp]
void LayerBase::drawWithOpenGL(const Region&clip, const Texture& texture) const
{const DisplayHardware& hw(graphicPlane(0).displayHardware());const uint32_t fbHeight = hw.getHeight();const State& s(drawingState());//validateTexture函数内部将绑定指定的纹理 glBindTexturevalidateTexture(texture.name);//下面就是OpenGL操作函数了glEnable(GL_TEXTURE_2D);......glMatrixMode(GL_TEXTURE);glLoadIdentity();//坐标旋转switch(texture.transform) {case HAL_TRANSFORM_ROT_90:glTranslatef(0, 1, 0);glRotatef(-90, 0, 0, 1);break;case HAL_TRANSFORM_ROT_180:glTranslatef(1, 1, 0);glRotatef(-180, 0, 0, 1);break;case HAL_TRANSFORM_ROT_270:glTranslatef(1, 0, 0);glRotatef(-270, 0, 0, 1);break;}if (texture.NPOTAdjust) {//缩放处理glScalef(texture.wScale, texture.hScale, 1.0f);}//使能纹理坐标glEnableClientState(GL_TEXTURE_COORD_ARRAY);//设置顶点坐标glVertexPointer(2, GL_FIXED, 0, mVertices);//设置纹理坐标glTexCoordPointer(2, GL_FIXED, 0, texCoords);while(it != end) {const Rect& r = *it++;const GLint sy = fbHeight - (r.top + r.height());//裁剪glScissor(r.left, sy, r.width(), r.height());//画矩形glDrawArrays(GL_TRIANGLE_FAN, 0, 4);}//禁止纹理坐标glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
handleRepaint这个函数基本上就是按Z轴的顺序对每一层进行重绘,重绘的方法就是使用OpenGL。
4. unlockClients和postFrameBuffer的分析
在绘制完图后,还有两项工作需要做,一个涉及unlockClients函数,另外一个涉及postFrameBuffer函数,这两个函数分别干了什么呢?unlockClients的代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::unlockClients()
{const LayerVector& drawingLayers(mDrawingState.layersSortedByZ);const size_t count = drawingLayers.size();sp<LayerBase> const* const layers = drawingLayers.array();for (size_t i=0 ; i<count ; ++i) {const sp<LayerBase>& layer = layers[i];layer->finishPageFlip();}
}
再看Layer的finishPageFlip函数,代码如下所示:
[-->Layer.cpp]
void Layer::finishPageFlip()
{//释放FrontBufferIndexstatus_t err = lcblk->unlock( mFrontBufferIndex );
}
原来,unlockClients会释放之前占着的FrontBuffer的索引号。下面看最后一个函数postFrameBuffer,代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::postFramebuffer()
{if(!mInvalidRegion.isEmpty()) {const DisplayHardware& hw(graphicPlane(0).displayHardware());const nsecs_t now = systemTime();mDebugInSwapBuffers = now;//调用这个函数后,混合后的图像就会传递到屏幕中显示了hw.flip(mInvalidRegion);mLastSwapBufferTime = systemTime() - now;mDebugInSwapBuffers = 0;mInvalidRegion.clear();}
}
flip将调用在DisplayHardware一节中提到的eglSwapBuffer函数,来完成FrameBuffer的PageFlip操作,代码如下所示:
[-->DisplayHardware.cpp]
void DisplayHardware::flip(const Region&dirty) const
{checkGLErrors();EGLDisplay dpy = mDisplay;EGLSurface surface = mSurface;......if(mFlags & PARTIAL_UPDATES) {mNativeWindow->setUpdateRectangle(dirty.getBounds());}mPageFlipCount++;eglSwapBuffers(dpy, surface);//PageFlipping,此后图像终于显示在屏幕上了!
}
5.3 Transaction的分析
Transaction是“事务”的意思。在我脑海中,关于事务的知识来自于数据库。在数据库操作中,事务意味着一次可以提交多个SQL语句,然后一个commit就可让它们集中执行,而且数据库中的事务还可以回滚,即恢复到事务提交前的状态。
SurfaceFlinger为什么需要事务呢?从上面对数据库事务的描述来看,是不是意味着一次执行多个请求呢?如直接盯着SF的源码来分析,可能不太容易搞清楚事务的前因后果,我想还是用老办法,从一个例子入手吧。
在WindowManagerService.java中,有一个函数之前分析过,现在再看看,代码如下所示:
[-->WindowManagerService.java::WinState]
Surface createSurfaceLocked() { Surface.openTransaction(); //开始一次transactiontry {try {mSurfaceX = mFrame.left + mXOffset;mSurfaceY = mFrame.top + mYOffset;//设置Surface的位置mSurface.setPosition(mSurfaceX, mSurfaceY);......}} finally {Surface.closeTransaction(); //关闭这次事务}
}
这个例子很好地展示了事务的调用流程,它会依次调用:
· openTransaction
· setPosition
· closeTransaction
下面就来分析这几个函数的调用。
1. openTransaction的分析
看JNI对应的函数,代码如下所示:
[-->android_View_Surface.cpp]
static void Surface_openTransaction(JNIEnv* env,jobject clazz)
{//调用SurfaceComposerClient的openGlobalTransaction函数SurfaceComposerClient::openGlobalTransaction();
}
下面转到SurfaceComposerClient,代码如下所示:
[-->SurfaceComposerClient.cpp]
void SurfaceComposerClient::openGlobalTransaction()
{Mutex::Autolock _l(gLock);......const size_t N = gActiveConnections.size();for(size_t i=0; i<N; i++) {sp<SurfaceComposerClient> client(gActiveConnections.valueAt(i).promote());//gOpenTransactions存储当前提交事务请求的Clientif(client != 0 && gOpenTransactions.indexOf(client) < 0) {//Client是保存在全局变量gActiveConnections中的SurfaceComposerClient对象,调用它的openTransaction。if (client->openTransaction() == NO_ERROR) {if (gOpenTransactions.add(client) < 0) {client->closeTransaction();}}......}}
}
上面是一个静态函数,内部调用了各个SurfaceComposerClient对象的openTranscation,代码如下所示:
[-->SurfaceComposerClient.cpp]
status_t SurfaceComposerClient::openTransaction()
{if(mStatus != NO_ERROR)return mStatus;Mutex::Autolock _l(mLock);mTransactionOpen++; //一个计数值,用来控制事务的提交。if(mPrebuiltLayerState == 0) {mPrebuiltLayerState = new layer_state_t;}return NO_ERROR;
}
layer_state_t是用来保存Surface的一些信息的,比如位置、宽、高等信息。实际上,调用的setPosition等函数,就是为了改变这个layer_state_t中的值。
2. setPosition的分析
上文说过,SFC中有一个layer_state_t对象用来保存Surface的各种信息。这里以setPosition为例,来看它的使用情况。这个函数是用来改变surface在屏幕上的位置的,代码如下所示:
[-->android_View_Surface.cpp]
static void Surface_setPosition(JNIEnv* env, jobject clazz, jint x, jint y)
{const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));if(surface == 0) return;status_t err = surface->setPosition(x, y);
}
[-->Surface.cpp]
status_t SurfaceControl::setPosition(int32_t x,int32_t y) {const sp<SurfaceComposerClient>& client(mClient);status_t err = validate();if (err < 0) return err;//调用SurfaceComposerClient的setPosition函数return client->setPosition(mToken, x, y);
}
[-->SurfaceComposerClient.cpp]
status_t SurfaceComposerClient::setPosition(SurfaceID id, int32_t x, int32_t y)
{layer_state_t* s = _lockLayerState(id); //找到对应的layer_state_tif(!s) return BAD_INDEX;s->what |= ISurfaceComposer::ePositionChanged;s->x = x;s->y = y; //上面几句修改了这块layer的参数_unlockLayerState(); //该函数将unlock一个同步对象,其他没有做什么工作return NO_ERROR;
}
setPosition就是修改了layer_state_t中的一些参数,那么,这个状态是什么时候传递到SurfaceFlinger中的呢?
3. 分析closeTransaction
相信读者此时已明白为什么叫“事务”了。原来,在openTransaction和closeTransaction中可以有很多操作,然后由closeTransaction一次性地把这些修改提交到SF上,来看代码:
[-->android_View_Surface.cpp]
static void Surface_closeTransaction(JNIEnv*env, jobject clazz)
{SurfaceComposerClient::closeGlobalTransaction();
}
[-->SurfaceComposerClient.cpp]
void SurfaceComposerClient::closeGlobalTransaction()
{......const size_t N = clients.size();sp<ISurfaceComposer>sm(getComposerService());//1 先调用SF的openGlobalTransactionsm->openGlobalTransaction();for (size_t i=0; i<N; i++) {//2 然后调用每个SurfaceComposerClient对象的closeTransactionclients[i]->closeTransaction();}//3 最后调用SF的closeGlobalTransactionsm->closeGlobalTransaction();
}
上面一共列出了三个函数,它们都是跨进程的调用,下面对其一一进行分析。
(1)SurfaceFlinger的openGlobalTransaction分析
这个函数其实很简单,略看就行了。
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::openGlobalTransaction()
{android_atomic_inc(&mTransactionCount);//又是一个计数控制
}
(2)SurfaceComposerClient的closeTransaction分析
代码如下所示:
[-->SurfaceComposerClient.cpp]
status_t SurfaceComposerClient::closeTransaction()
{if(mStatus != NO_ERROR)return mStatus;Mutex::Autolock _l(mLock);......const ssize_t count = mStates.size();if (count) {//mStates是这个SurfaceComposerClient中保存的所有layer_state_t数组,也就是//每个Surface一个。然后调用跨进程的setStatemClient->setState(count, mStates.array());mStates.clear();}return NO_ERROR;
}
BClient的setState,最终会转到SF的setClientState上,代码如下所示:
[-->SurfaceFlinger.cpp]
status_t SurfaceFlinger::setClientState(ClientIDcid, int32_t count,const layer_state_t*states)
{Mutex::Autolock _l(mStateLock);uint32_t flags = 0;cid <<= 16;for(int i=0 ; i<count ; i++) {const layer_state_t& s = states[i];sp<LayerBaseClient> layer(getLayerUser_l(s.surface | cid));if(layer != 0) {const uint32_t what = s.what;if (what & ePositionChanged) {if (layer->setPosition(s.x, s.y))//eTraversalNeeded表示需要遍历所有显示层flags |= eTraversalNeeded;}....}}if(flags) {setTransactionFlags(flags);//这里将会触发threadLoop的事件。}return NO_ERROR;
}
[-->SurfaceFlinger.cpp]
uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags, nsecs_t delay)
{uint32_t old = android_atomic_or(flags, &mTransactionFlags);if((old & flags)==0) {if(delay > 0) {signalDelayedEvent(delay);}else {signalEvent(); //设置完mTransactionFlags后,触发事件。}}return old;
}
(3)SurfaceFlinger的closeGlobalTransaction分析
来看代码:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::closeGlobalTransaction()
{//原子操作返回旧值if (android_atomic_dec(&mTransactionCount) ==1) {//注意下面语句的执行条件,当mTransactionCount变为零时才执行,这意味着//openGlobalTransaction两次的话,只有最后一个closeGlobalTransaction调用才会真正地提交事务signalEvent();Mutex::Autolock _l(mStateLock);//如果这次事务涉及尺寸调整,则需要等一段时间while (mResizeTransationPending) {status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));if (CC_UNLIKELY(err != NO_ERROR)) {mResizeTransationPending = false;break;}}}
}
关于事务的目的,相信读者已经比较清楚了:
· 就是将一些控制操作(例如setPosition)的修改结果,一次性地传递给SF进行处理。
那么,哪些操作需要通过事务来传递呢?通过查看Surface.h可以知道,下面这些操作需要通过事务来传递(这里只列出了几个经常用的函数):setPosition、setAlpha、show/hide、setSize、setFlag等。
由于这些修改不像重绘那么简单,有时它会涉及其他的显示层,例如在显示层A的位置调整后,之前被A遮住的显示层B,现在可能变得可见了。对于这种情况,所提交的事务会设置eTraversalNeeded标志,这个标志表示要遍历所有显示层进行处理。关于这一点,来看工作线程中的事务处理。
4. 工作线程中的事务处理
还是从代码入手分析,如下所示:
[-->SurfaceFlinger.cpp]
bool SurfaceFlinger::threadLoop()
{waitForEvent();if(LIKELY(mTransactionCount == 0)) {const uint32_t mask = eTransactionNeeded | eTraversalNeeded;uint32_ttransactionFlags = getTransactionFlags(mask);if(LIKELY(transactionFlags)) {handleTransaction(transactionFlags);}}...
}
getTransactionFlags函数的实现蛮有意思,不妨看看其代码,如下所示:
[-->SurfaceFlinger.cpp]
uint32_t SurfaceFlinger::getTransactionFlags(uint32_t flags)
{//先通过原子操作去掉mTransactionFlags中对应的位。//而后返回原子操作返回的旧值(mTransactionFlags原来的值)和flags进行与操作return android_atomic_and(~flags, &mTransactionFlags) & flags;
}
getTransactionFlags所做的工作不仅仅是get那么简单,它还设置了mTransactionFlags,从这个角度来看,getTransactionFlags这个名字有点名不副实。
接着来看最重要的handleTransaction函数,代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::handleTransaction(uint32_t transactionFlags)
{Vector< sp<LayerBase> > ditchedLayers;{Mutex::Autolock _l(mStateLock);//调用handleTransactionLocked函数处理handleTransactionLocked(transactionFlags, ditchedLayers);}const size_t count = ditchedLayers.size();for(size_t i=0 ; i<count ; i++) {if(ditchedLayers[i] != 0) {//ditch是丢弃的意思,有些显示层可能被hide了,所以这里做些收尾的工作ditchedLayers[i]->ditch();}}
}
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags, Vector< sp<LayerBase> >&ditchedLayers)
{//这里使用了mCurrentState,它的layersSortedByZ数组存储了SF中所有的显示层const LayerVector& currentLayers(mCurrentState.layersSortedByZ);const size_t count = currentLayers.size();const bool layersNeedTransaction = transactionFlags & eTraversalNeeded;//如果需要遍历所有显示的话。if(layersNeedTransaction) {for (size_t i=0 ; i<count ; i++) {const sp<LayerBase>& layer = currentLayers[i];uint32_t trFlags = layer->getTransactionFlags(eTransactionNeeded);if (!trFlags) continue;//调用各个显示层的doTransaction函数。constuint32_t flags = layer->doTransaction(0);if (flags & Layer::eVisibleRegion)mVisibleRegionsDirty = true;}}if(transactionFlags & eTransactionNeeded) {if(mCurrentState.orientation != mDrawingState.orientation) {//横竖屏如果发生切换,需要对应变换设置。const int dpy = 0;const int orientation = mCurrentState.orientation;const uint32_t type = mCurrentState.orientationType;GraphicPlane& plane(graphicPlane(dpy));plane.setOrientation(orientation);......}/*mLayersRemoved变量在显示层被移除的时候设置,例如removeLayer函数,这些函数也会触发handleTranscation函数的执行*/if(mLayersRemoved) {mLayersRemoved = false;mVisibleRegionsDirty = true;const LayerVector& previousLayers(mDrawingState.layersSortedByZ);const size_t count = previousLayers.size();for (size_t i=0 ; i<count ; i++) {const sp<LayerBase>& layer(previousLayers[i]);if (currentLayers.indexOf( layer ) < 0) {ditchedLayers.add(layer);mDirtyRegionRemovedLayer.orSelf(layer->visibleRegionScreen);}}}free_resources_l();}//提交事务处理,有必要进去看看。commitTransaction();
}
每个显示层对事务的具体处理,都在它们的doTranscation函数中,读者若有兴趣,可进去看看。需要说明的是,每个显示层内部也有一个状态变量,doTransaction会更新这些状态变量。
回到上面的函数,最后它将调用commitTransaction提交事务,代码如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::commitTransaction()
{//mDrawingState将使用更新后的mCurrentStatemDrawingState = mCurrentState;mResizeTransationPending = false;//触发一个条件变量,这样等待在closeGlobalTransaction函数中的线程可以放心地返回了。mTransactionCV.broadcast();
}
5.4 SurfaceFlinger的总结
通过前面的分析,使我们感受了SurfaceFlinger的风采。从整体上看,SurfaceFlinger不如AudioFlinger复杂,它的工作集中在工作线程中,下面用图23来总线一下SF工作线程:
图23 SF工作线程的流程总结
6 拓展思考
本章的拓展思考分三个部分:
· 介绍SharedBufferServer和SharedBufferClient的工作流程。
· 关于ViewRoot一些问题的总结。
· LayerBuffer的工作原理分析。
6.1 Surface系统的CB对象分析
根据前文分析可知,Surface系统中的CB,其实是指SharedBuffer家族,它们是Surface系统中对生产者和消费者进行步调控制的中枢机构。先通过图24来观察整体的工作流程是怎样的。
图24 SharedBuffer家族使用流程
为书写方便起见,我们简称:
· SharedBufferServer为SBS。
· SharedBufferClient为SBC。
· SharedBufferStack为SBT。
其中SBC和SBS都是建立在同一个SBT上的,所以应先看SBT,下面代码列出了其中几个与读写控制有关的成员变量:
[-->SharedBufferStack.h]
class SharedBufferStack{....../*虽然PageFlipping使用Front和Back两个Buffer就可以了,但是SBT的结构和相关算法是支持多个缓冲的。另外,缓冲是按照块来获取的,也就是一次获得一块缓冲,每块缓冲用一个编号表示(这一点在之前的分析已经介绍过了)。*/int32_t head; int32_t available; //当前可用的空闲缓冲个数int32_t queued; //SBC投递的脏缓冲个数int32_t inUse; //SBS当前正在使用的缓冲编号......//上面这几个参数联合SBC中的tail,我称之为控制参数。
}
SBT创建好后,下面就是SBS和SBC的创建了,它们会做什么特殊工作吗?
1. SBS和SBC的创建
下面分别看SBS和SBC的创建,代码如下所示:
[-->SharedBufferStack.cpp]
SharedBufferServer::SharedBufferServer(SharedClient*sharedClient,int surface, int num, int32_t identity):SharedBufferBase(sharedClient, surface, num, identity)
{mSharedStack->init(identity);//这个函数将设置inUse为-1//下面设置SBT中的参数,我们关注前三个mSharedStack->head = num-1;mSharedStack->available = num;mSharedStack->queued = 0;//设置完后,head=2-1=1, available=2, queued=0,inUse=-1mSharedStack->reallocMask = 0;memset(mSharedStack->dirtyRegion, 0, sizeof(mSharedStack->dirtyRegion));
}
再看SBC的创建,代码如下所示:
[-->SharedBufferStack.cpp]
SharedBufferClient::SharedBufferClient(SharedClient*sharedClient,int surface, int num, int32_t identity):SharedBufferBase(sharedClient, surface, num, identity), tail(0)
{tail = computeTail(); //tail是SBC定义的变量,注意它不是SBT定义的。
}
看computeTail函数的代码:
[-->SharedBufferStack.cpp]
int32_t SharedBufferClient::computeTail() const
{SharedBufferStack& stack( *mSharedStack );int32_t newTail;int32_t avail;int32_t head;do {avail = stack.available; //available=2,head=1head = stack.head;} while (stack.available != avail);newTail = head - avail + 1;//newTail=1-2+1=0if(newTail < 0) {newTail += mNumBuffers;} elseif (newTail >= mNumBuffers) {newTail -= mNumBuffers;}return newTail;//计算得到newTail=0
}
来看在SBC和SBS创建后,控制参数的变化,如图25所示:
图25 SBC/SBS创建后的示意图
2. SBC端流程的分析
下面看SBC端的工作流程。
(1)dequeue分析
先看SBC的dequeue函数:
[-->SharedBufferStack.cpp]
ssize_t SharedBufferClient::dequeue()
{SharedBufferStack& stack( *mSharedStack );......//DequeueCondition函数对象DequeueCondition condition(this);status_t err = waitForCondition(condition);//成功以后,available减1,表示当前可用的空闲buffer只有1个if (android_atomic_dec(&stack.available) == 0) {......}int dequeued = tail; //tail值为0,所以dequeued的值为0。//tail加1。如果超过2,则重新置为0,这表明tail的值在0,1间循环。tail =((tail+1 >= mNumBuffers) ? 0 : tail+1);......//返回的这个dequeued值为零,也就是tail加1操作前的旧值。这一点请读者务必注意。return dequeued;
}
其中DequeueCondition的操作函数很简单,代码如下所示:
bool SharedBufferClient::DequeueCondition::operator()(){return stack.available > 0;//只要available大于0就算满足条件,第一次进来肯定满足
}
用图26来表示dequeue的结果:
图26 dequeue结果图
注意,在上图中,0号缓冲用虚线表示,SBC的dequeue函数的返回值用dequeued表示,它指向这个0号缓冲。正如代码中注释的那样,由于dequeued的值用的是tail的旧值,而tail是SBC定义的变量,不是SBT定义的变量,所以tail在SBS端是不可见的。这就带来了一个潜在危险,即0号缓冲不能保证当前是真正空闲的,因为SBS可能正在用它,怎么办?试看下面的lock。
(2)lock的分析
lock使用了LockCondition,其中传入的参数buf的值为0,也就是上图中的dequeue的值,代码如下所示:
[-->SharedBufferStack.cpp]
status_t SharedBufferClient::lock(int buf)
{LockCondition condition(this, buf);status_terr = waitForCondition(condition);returnerr;
}
看LockCondition的():
bool SharedBufferClient::LockCondition::operator()() {/*这个条件其实就是判断编号为buf的Buffer是不是被使用了。buf值为0,head值为1,queued为0,inUse为-1*/return(buf != stack.head ||(stack.queued > 0 && stack.inUse!= buf));
}
现在可以知道为什么SBC需要调用dequeue和lock函数了吗?原来:
· dequeue只是根据本地变量tail计算一个本次应当使用的Buffer编号,其实也就是在0,1之间循环。上次用0号缓冲,那么这次就用1号缓冲。
· lock函数要确保这个编号的Buffer没有被SF当做FrontBuffer使用。
(3)queue的分析
Activity端在绘制完UI后,将把BackBuffer投递出去以显示。接着上面的流程,这个BackBuffer的编号是0。待Activity投递完后,才会调用signal函数触发SF消费,所以在此之前格局不会发生变化。试看投递用的queue函数,注意传入的buf参数为0,代码如下所示:
[-->SharedBufferStack.cpp]
status_t SharedBufferClient::queue(int buf)
{QueueUpdate update(this);status_t err = updateCondition( update );......return err;
}
//直接看这个QueueUpdate函数对象
ssize_t SharedBufferClient::QueueUpdate::operator()() {android_atomic_inc(&stack.queued);//queued增加1,现在该值由零变为1returnNO_ERROR;
}
至此,SBC端走完一个流程了,结果是什么?如图27所示:
图27 queue结果图
0号缓冲被移到queue的区域了,可目前还没有变量指向它。假设SBC端此后没有绘制UI的需求,那么它就会沉默一段时间。
3. SBS端的分析
SBS的第一个函数是retireAndLock,它使用了RetireUpdate函数对象,代码如下所示:
[-->SharedBufferStack.cpp]
ssize_t SharedBufferServer::retireAndLock()
{RetireUpdate update(this, mNumBuffers);ssize_t buf = updateCondition( update );retur nbuf;
}
这个RetireUpdate对象的代码如下所示:
ssize_t SharedBufferServer::RetireUpdate::operator()() {//先取得head值,为1int32_t head = stack.head;//inUse被设置为1。表明要使用1吗?目前的脏缓冲应该是0才对android_atomic_write(head, &stack.inUse);int32_t queued;do {queued = stack.queued; //queued目前为1if(queued == 0) {return NOT_ENOUGH_DATA;}//下面这个原子操作使得stack.queued减1.}while (android_atomic_cmpxchg(queued, queued-1, &stack.queued));//while循环退出后,queued减1,又变为0。//head值也在0,1间循环,现在head值变为0了head =((head+1 >= numBuffers) ? 0 : head+1);// inUse被设置为0android_atomic_write(head, &stack.inUse);// head值被设为0android_atomic_write(head, &stack.head);// available加1,变成2.android_atomic_inc(&stack.available);return head;//返回0
}
retireAndLock的结果是什么呢?看看图28就知道了。
图28 retireAndLock结果图
注意上面的available区域,1号缓冲右边的0号缓冲是用虚线表示的,这表示该0号缓冲实际上并不存在于available区域,但available的个数却变成2了。这样不会出错吗?当然不会,因为SBC的lock函数要确保这个缓冲没有被SBS使用。
我们来看SBS端最后一个函数,它调用了SBS的unlock,这个unlock使用了UnlockUpdate函数对象,代码如下所示:
[-->SharedBufferStack.cpp]
ssize_t SharedBufferServer::UnlockUpdate::operator()() {......android_atomic_write(-1, &stack.inUse);//inUse被设置为-1return NO_ERROR;
}
unlock后最终的结果是什么呢?如图29所示:
图29 unlock结果图
比较一下图29和图25,可能会发现两图中tail和head刚好反了,这就是PageFlip。
6.2 ViewRoot的你问我答
· ViewRoot和View类的关系是什么?
ViewRoot是View视图体系的根。每一个Window(注意是Window,比如PhoneWindow)有一个ViewRoot,它的作用是处理layout和View视图体系的绘制。那么视图体系又是什么呢?它包括Views和ViewGroups,也就是SDK中能看到的View类都属于视图体系。根据前面的分析可知,这些View是需要通过draw画出来的。而ViewRoot就是用来draw它们的,ViewRoot本身没有draw/onDraw函数。
· ViewRoot和它所控制的View及其子View使用同一个Canvas吗?
我们在ViewRoot的performTraversals中见过。ViewRoot提供Canvas给它所控制的View,所以它们使用同一个Canvas。但Canvas使用的内存却不是固定的,而是通过Surface的lockCanvas得到的。
· View、Surface和Canvas之间的关系是怎样的?
一个Window将和一个Surface绑定在一起,绘制前ViewRoot会从Surface中lock出一个Canvas。
· Canvas有一个bitmap,那么绘制UI时,数据是画在Canvas的这个bitmap中吗?
答案是肯定的,bitmap实际上包括了一块内存,绘制的数据最终都在这块内存上。
· 同一个ViewRoot下,不同类型的View(不同类型指不同的UI单元,例如按钮、文本框等)使用同一个Surface吗?
是的,但是SurfaceView要除外。因为SurfaceView的绘制一般在单独的线程上,并且由应用层主动调用lockCanvas、draw和unlockCanvasAndPost来完成绘制流程。
6.3 LayerBuffer的分析
LayerBuffer会在视频播放和摄像机预览等场景中用到,就以Camera的preView(预览)为例,来分析LayerBuffer的工作原理。
1. LayerBuffer的创建
先看LayerBuffer的创建,它通过SF的createPushBuffersSurfaceLocked得到,代码如下所示:
[-->SurfaceFlinger.cpp]
sp<LayerBaseClient> SurfaceFlinger::createPushBuffersSurfaceLocked(const sp<Client>& client, DisplayID display,int32_t id, uint32_t w, uint32_t h, uint32_t flags)
{sp<LayerBuffer> layer = new LayerBuffer(this, display, client,id);layer->initStates(w, h, flags);addLayer_l(layer);return layer;
}
LayerBuffer的派生关系,如图30所示:
图30 LayerBuffer的派生关系示意图
从上图中可以发现:
· LayerBuffer定义了一个内部类Source类,它有两个派生类BufferSource和OverlaySource。根据它们的名字,可以猜测到Source代表数据的提供者。
· LayerBuffer中的mSurface其真实类型是SurfaceLayerBuffer。
LayerBuffer创建好了,不过该怎么用呢?和它相关的调用流程是怎样的呢?下面来分析Camera。
2. Camera preView的分析
Camera是一个单独的Service,全称是CameraService,先看CameraService的registerPreviewBuffers函数。这个函数会做什么呢?代码如下所示:
[-->CameraService.cpp]
status_t CameraService::Client::registerPreviewBuffers()
{int w, h;CameraParameters params(mHardware->getParameters());params.getPreviewSize(&w, &h);/*1 mHardware代表Camera设备的HAL对象。本书讨论CameraHardwareStub设备,它其实是一个虚拟的设备,不过其代码却具有参考价值。BufferHeap定义为ISurface的内部类,其实就是对IMemoryHeap的封装*/ISurface::BufferHeapbuffers(w, h, w, h,HAL_PIXEL_FORMAT_YCrCb_420_SP,mOrientation,0,mHardware->getPreviewHeap());//2 调用SurfaceLayerBuffer的registerBuffers函数。status_t ret = mSurface->registerBuffers(buffers);return ret;
}
上面代码中列出了两个关键点,逐一来分析它们。
(1)创建BufferHeap
BufferHeap是ISurface定义的一个内部类,它的声明如下所示:
[-->ISurface.h]
class BufferHeap {public:......//使用这个构造函数BufferHeap(uint32_t w, uint32_t h,int32_t hor_stride, int32_t ver_stride,PixelFormat format, const sp<IMemoryHeap>& heap);......~BufferHeap();uint32_t w;uint32_t h;int32_t hor_stride;int32_t ver_stride;PixelFormat format;uint32_t transform;uint32_t flags;sp<IMemoryHeap> heap; //heap指向真实的存储对象
};
从上面代码中可发现,BufferHeap基本上就是封装了一个IMemoryHeap对象,根据我们对IMemoryHeap的了解,它应该包含了真实的存储对象,这个值由CameraHardwareStub对象的getPreviewHeap得到,这个函数的代码如下所示:
[-->CameraHardwareStub.cpp]
sp<IMemoryHeap>CameraHardwareStub::getPreviewHeap() const
{return mPreviewHeap;//返回一个成员变量,它又是在哪创建的呢?
}//上面的mPreivewHeap对象由initHeapLocked函数创建,该函数在HAL对象创建的时候被调用
void CameraHardwareStub::initHeapLocked()
{....../*创建一个MemoryHeapBase对象,大小是mPreviewFrameSize * kBufferCount,其中kBufferCount为4。注意这是一段连续的缓冲。*/mPreviewHeap= new MemoryHeapBase(mPreviewFrameSize * kBufferCount);//mBuffer为MemoryBase数组,元素为4for (inti = 0; i < kBufferCount; i++) {mBuffers[i] = new MemoryBase(mPreviewHeap, i * mPreviewFrameSize, mPreviewFrameSize);}
}
从上面这段代码中可以发现,CameraHardwareStub对象创建的用于preView的内存结构是按图31所示的方式来组织的:
图31 CameraHardwareStub用于preView的内存结构图
其中:
· BufferHeap的heap变量指向一块MemoryHeap,这就是mPreviewHeap。
· 在这块MemoryHeap上构建了4个MemoryBase。
(2)registerBuffers的分析
BufferHeap准备好后,要调用ISurface的registerBuffers函数,ISurface在SF端的真实类型是SurfaceLayerBuffer,所以要直接看它的实现,代码如下所示:
[-->LayerBuffer.cpp]
status_t LayerBuffer::SurfaceLayerBuffer::registerBuffers(const ISurface::BufferHeap& buffers)
{sp<LayerBuffer> owner(getOwner());if (owner != 0)//调用外部类对象的registerBuffers,所以SurfaceLayerBuffer也是一个Proxy哦。return owner->registerBuffers(buffers);return NO_INIT;
}//外部类是LayerBuffer,调用它的registerBuffers函数
status_t LayerBuffer::registerBuffers(constISurface::BufferHeap& buffers)
{Mutex::Autolock _l(mLock);//创建数据的来源BufferSource,注意我们其实把MemoryHeap设置上去了sp<BufferSource> source = new BufferSource(*this, buffers);status_t result = source->getStatus();if(result == NO_ERROR) {mSource = source; //保存这个数据源为mSource。}return result;
}
而BufferSource(图30),它内部有一个成员变量mBufferHeap会指向传入的buffers参数,所以registerBuffers过后,就得到了图32:
图32 registerBuffers的结果示意图
请注意上图的箭头指向,不论中间有多少层封装,最终的数据存储区域还是mPreivewHeap。
3.数据的传输
至此,Buffer在SF和Camera两端都准备好了,那么数据是怎么从Camera传递到SF的呢?先来看数据源是怎么做的。
(1)数据传输的分析
CameraHardwareStub有一个preview线程,这个线程会做什么呢?代码如下所示:
[-->CameraHardwareStub.cpp]
//preview线程从Thread类派生,下面这个函数在threadLoop中循环调用
int CameraHardwareStub::previewThread()
{mLock.lock();//每次进来mCurrentPreviewFrame都会加1ssize_t offset = mCurrentPreviewFrame * mPreviewFrameSize;sp<MemoryHeapBase> heap = mPreviewHeap;FakeCamera* fakeCamera = mFakeCamera;//虚拟的摄像机设备//从mBuffers中取一块内存,用于接收来自硬件的数据sp<MemoryBase> buffer = mBuffers[mCurrentPreviewFrame];mLock.unlock();if(buffer != 0) {int delay = (int)(1000000.0f / float(previewFrameRate));void *base = heap->base();//base是mPreviewHeap的起始位置//下面这个frame代表buffer在mPreviewHeap中的起始位置,还记得图31吗?//四块MemoryBase的起始位置由下面这个代码计算得来uint8_t *frame = ((uint8_t *)base) + offset;//取出一帧数据,放到对应的MemoryBase中fakeCamera->getNextFrameAsYuv422(frame);//1 把含有帧数据的buffer传递到上层if(mMsgEnabled & CAMERA_MSG_PREVIEW_FRAME)mDataCb(CAMERA_MSG_PREVIEW_FRAME, buffer, mCallbackCookie);//mCurrentPreviewFrame 递增,在0到3之间循环mCurrentPreviewFrame = (mCurrentPreviewFrame + 1) % kBufferCount;usleep(delay);//模拟真实硬件的延时}return NO_ERROR;
}
读者是否明白Camera preview的工作原理了?
就是从四块内存中取一块出来接收数据,然后再把这块buffer内存传递到上层去处理。从缓冲使用的角度来看,mBuffers数组构成了一个成员个数为四的缓冲队列。preview通过mDataCb这个回调函数,把数据传递到上层,而CameraService实现了mData这个回调函数,这个回调函数最终会调用handlePreviewData,直接看handlePreviewData即可,代码如下所示:
[-->CameraService.cpp]
void CameraService::Client::handlePreviewData(const sp<IMemory>& mem)
{ssize_t offset;size_t size;//注意传入的mem参数,它实际上是Camera HAL创建的mBuffers数组中的一个//offset返回的是这个数组在mPreviewHeap中的偏移量sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);if (!mUseOverlay){Mutex::Autolock surfaceLock(mSurfaceLock);if(mSurface != NULL) {//调用ISurface的postBuffer,注意我们传入的参数是offset。mSurface->postBuffer(offset);}}......
}
· handlePreviewData就是传递了一个偏移量,这个偏移量是mBuffers数组成员的首地址。可用图33来表示:
图33 handlePreviewData示意图
下面看SurfaceLayerBuffer的postBuffer函数,不过它只是一个小小的代理,真正的工作由外部类LayerBuffer完成,直接看它好了,代码如下所示:
[-->LayerBuffer.cpp]
void LayerBuffer::postBuffer(ssize_t offset)
{sp<Source> source(getSource());//getSource返回mSource,为BufferSource类型if(source != 0)source->postBuffer(offset);//调用BufferSource的postBuffer函数。
}
[-->LayerBuffer.cpp]
void LayerBuffer::BufferSource::postBuffer(ssize_t offset)
{ ISurface::BufferHeap buffers;{Mutex::Autolock _l(mBufferSourceLock);buffers = mBufferHeap;//还记得图32吗?if(buffers.heap != 0) {//BufferHeap的heap变量指向MemoryHeap,下面取出它的大小const size_t memorySize = buffers.heap->getSize();//做一下检查,判断这个offset是不是有问题if ((size_t(offset) + mBufferSize) > memorySize) {LOGE("LayerBuffer::BufferSource::postBuffer() ""invalid buffer(offset=%d, size=%d, heap-size=%d",int(offset),int(mBufferSize), int(memorySize));return;}}}sp<Buffer> buffer;if (buffers.heap != 0) {//创建一个LayerBuffer::Bufferbuffer = new LayerBuffer::Buffer(buffers, offset, mBufferSize);if(buffer->getStatus() != NO_ERROR)buffer.clear();setBuffer(buffer);//setBuffer?我们要看看//mLayer就是外部类LayerBuffer,调用它的invalidate函数将触发SF的重绘mLayer.invalidate();}
}void LayerBuffer::BufferSource::setBuffer(const sp<LayerBuffer::Buffer>& buffer)
{//setBuffer函数就是简单地将new出来的Buffer设置给成员变量mBuffer,这么做会有问题吗?Mutex::Autolock_l(mBufferSourceLock);mBuffer = buffer; //将新的buffer设置为mBuffer,mBuffer原来指向的那个被delete
}
从数据生产者角度看,postBuffer函数将不断地new一个Buffer出来,然后将它赋值给成员变量mBuffer,也就是说,mBuffer会不断变化。现在从缓冲的角度来思考一下这种情况的结果:
· 数据生产者有一个含四个成员的缓冲队列,也就是mBuffers数组。
· 而数据消费者只有一个mBuffer。
这种情况会有什么后果呢?请记住这个问题,我们到最后再来揭示。下面先看mBuffer的类型Buffer是什么。
(2)数据使用的分析
Buffer被定义成LayerBuffer的内部类,代码如下所示:
[-->LayerBuffer.cpp]
LayerBuffer::Buffer::Buffer(const ISurface::BufferHeap& buffers, ssize_t offset, size_t bufferSize):mBufferHeap(buffers), mSupportsCopybit(false)
{//注意,这个src被定义为引用,所以修改src的信息相当于修改mNativeBuffer的信息NativeBuffer& src(mNativeBuffer);src.crop.l = 0;src.crop.t = 0;src.crop.r = buffers.w;src.crop.b = buffers.h;src.img.w =buffers.hor_stride ?: buffers.w;src.img.h =buffers.ver_stride ?: buffers.h;src.img.format = buffers.format;//这个base将指向对应的内存起始地址src.img.base =(void*)(intptr_t(buffers.heap->base()) + offset);src.img.handle = 0;gralloc_module_tconst * module = LayerBuffer::getGrallocModule();//做一些处理,有兴趣的读者可以去看看。if(module && module->perform) {int err = module->perform(module,GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER,buffers.heap->heapID(), bufferSize,offset, buffers.heap->base(),&src.img.handle);mSupportsCopybit = (err == NO_ERROR);}
}
上面是Buffer的定义,其中最重要的就是这个mNativeBuffer了,它实际上保存了mBuffers数组成员的首地址。
下面看绘图函数,也就是LayerBuffer的onDraw函数,这个函数由SF的工作线程调用,代码如下所示:
[-->LayerBuffer.cpp]
void LayerBuffer::onDraw(const Region& clip)const
{sp<Source> source(getSource());if(LIKELY(source != 0)) {source->onDraw(clip);//source实际类型是BufferSource,我们去看看。} else{clearWithOpenGL(clip);}
}void LayerBuffer::BufferSource::onDraw(const Region& clip) const
{sp<Buffer> ourBuffer(getBuffer());......//使用这个Buffer,注意使用的时候没有锁控制mLayer.drawWithOpenGL(clip, mTexture);//生成一个贴图,然后绘制它
}
其中getBuffer函数返回mBuffer,代码如下所示:
sp<LayerBuffer::Buffer>LayerBuffer::BufferSource::getBuffer() const
{Mutex::Autolock_l(mBufferSourceLock);return mBuffer;
}
此时生产者的队列有四个元素,而消费者的队列只有一个元素,它可用图34来表示:
图34 数据传递的问题示意图
7 本章小结
本章可能是全书难度最大的一章了。在这一章的讲解中,我们把打通任督二脉做为破解Surface系统的突破口:
· 应用程序和Surface的关系,这是任脉。
· Surface和SurfaceFlinger的关系,这是督脉。
其中,打通任脉的过程是比较曲折的,从应用程序的Activity开始,一路追踪到ViewRoot、WindowManagerService。任脉被打通后,还只是解决了Java层的问题,而督脉则集中在Native层。在必杀技aidl工具的帮助下,我们首先成功找到了Surface乾坤大挪移的踪迹。此后在精简流程方法的帮助下,对Surface以及SurfaceFlinger进行了深入分析。在拓展部分,对Surface系统中CB对象的工作流程、ViewRoot的一些问题、以及LayerBuffer进行了较为详细的介绍。
6 深入理解PackageManagerService
原文链接:https://blog.csdn.net/Innost/article/details/47253179
本章主要内容:
详细分析PackageManagerService
1 概述
PackageManagerService是本书分析的第一个核心服务,也是Android系统中最常用的服务之一。它负责系统中Package的管理,应用程序的安装、卸载、信息查询等。图1展示了PackageManagerService及客户端的类家族。
图1 PackageManagerService及客户端类家族
由图1可知:
· IPackageManager接口类中定义了服务端和客户端通信的业务函数(通过Proxy),还定义了内部类Stub,Stub类从Binder派生并实现了IPackageManager接口。
· PackageManagerService继承自IPackageManager.Stub类,由于Stub类从Binder派生,因此PackageManagerService将作为服务端参与Binder通信。
· Stub类中定义了一个内部类Proxy,该类有一个IBinder类型(实际类型为BinderProxy)的成员变量mRemote,mRemote用于和服务端PackageManagerService通信。
· IPackageManager接口类中定义了许多业务函数,但是出于安全等方面的考虑,Android对外(即SDK)提供的只是一个子集,该子集被封装在抽象类PackageManager中。客户端一般通过Context的getPackageManager函数返回一个类型为PackageManager的对象,该对象的实际类型是PackageManager的子类ApplicationPackageManager。这种基于接口编程的方式,虽然极大降低了模块之间的耦合性,却给代码分析带来了不小的麻烦。
· ApplicationPackageManager类继承自PackageManager类。它并没有直接参与Binder通信,而是通过mPM成员变量指向一个IPackageManager.Stub.Proxy类型的对象。
提示:读者在源码中可能找不到IPackageManager.java文件。该文件在编译过程中是经aidl工具处理IPackageManager.aidl后得到,最终的文件位置在Android源码/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content/pm/目录中。
aidl工具生成的结果文件有着相似的代码结构。读者不妨看看下面这个笔者通过编译生成的IPackageManager.java文件。
[-->IPackageManager.java]
public interface IPackageManager extends android.os.IInterface {//定义内部类Stub,派生自Binder,实现IPackageManager接口public static abstract class Stub extends android.os.Binder implements android.content.pm.IPackageManager {private static final java.lang.String DESCRIPTOR = "android.content.pm.IPackageManager";public Stub() {this.attachInterface(this,DESCRIPTOR);}......//定义Stub的内部类Proxy,实现IPackageManager接口private static class Proxy implements android.content.pm.IPackageManager{//通过mRemote变量和服务端交互private android.os.IBinder mRemote;Proxy(android.os.IBinderremote) {mRemote = remote;}......}......}
}
接下来分析PackageManagerService,为书写方便起见,以后将其简称为PKMS。
2 初识PackageManagerService
PKMS作为系统的核心服务,由SystemServer创建,相关代码如下:
[-->SystemServer.java]
//ServerThread的run函数
/*
4.0新增的一个功能,即设备加密(encrypting the device),该功能由系统属性vold.decrypt指定。这部分功能比较复杂,本书暂不讨论。
该功能对PKMS的影响就是通过onlyCore实现的,该变量用于判断是否只扫描系统库(包括APK和Jar包)
*/
String cryptState = SystemProperties.get("vold.decrypt");
boolean onlyCore = false;
//ENCRYPTING_STATE的值为"trigger_restart_min_framework"
if(ENCRYPTING_STATE.equals(cryptState)) {......onlyCore = true;
} else if(ENCRYPTED_STATE.equals(cryptState)) {......onlyCore = true;
}
//1 调用PKMS的main函数,第二个参数用于判断是否为工厂测试,我们不讨论的这种情况,假定onlyCore的值为false
pm = PackageManagerService.main(context, factoryTest !=SystemServer.FACTORY_TEST_OFF,onlyCore);
boolean firstBoot = false;
try {//判断本次是否为初次启动。这里的FirstBoot是指开机后的第一次启动firstBoot = pm.isFirstBoot();
}
......
try {//2 做dex优化,dex是Android上针对Java字节码的一种优化技术,可提高运行效率pm.performBootDexOpt();
}
......
try {//3 通知系统进入就绪状态pm.systemReady();
}
......
}//run函数结束
以上代码中共有4个关键调用,分别是:
· PKMS的main函数。这个函数是PKMS的核心,稍后会重点分析它。
· isFirstBoot、performBootDexOpt和systemReady。这3个函数比较简单。学完本章后,读者可完全自行分析它们,故这里不再赘述。
首先分析PKMS的main函数,它是核心函数,此处单独用一节进行分析。
3 PKMS的main函数分析
PKMS的main函数代码如下:
[-->PackageManagerService.java]
public static final IPackageManager main(Contextcontext, boolean factoryTest, boolean onlyCore) {//调用PKMS的构造函数,factoryTest和onlyCore的值均为falsePackageManagerService m = new PackageManagerService(context, factoryTest, onlyCore);//向ServiceManager注册PKMSServiceManager.addService("package", m);return m;
}
main函数很简单,只有短短几行代码,执行时间却较长,主要原因是PKMS在其构造函数中做了很多“重体力活”,这也是Android启动速度慢的主要原因之一。在分析该函数前,先简单介绍一下PKMS构造函数的功能。
PKMS构造函数的主要功能是,扫描Android系统中几个目标文件夹中的APK,从而建立合适的数据结构以管理诸如Package信息、四大组件信息、权限信息等各种信息。抽象地看,PKMS像一个加工厂,它解析实际的物理文件(APK文件)以生成符合自己要求的产品。例如,PKMS将解析APK包中的AndroidManifest.xml,并根据其中声明的Activity标签来创建与此对应的对象并加以保管。PKMS的工作流程相对简单,复杂的是其中用于保存各种信息的数据结构和它们之间的关系,以及影响最终结果的策略控制(例如前面代码中的onlyCore变量,用于判断是否只扫描系统目录)。
PKMS构造函数的工作流程大体可分三个阶段:
· 扫描目标文件夹之前的准备工作。
· 扫描目标文件夹。
· 扫描之后的工作。
该函数涉及到的知识点较多,代码段也较长,因此我们将通过分段讨论的方法,集中解决相关的重点问题。
3.1 构造函数分析之前期准备工作
下面开始分析构造函数第一阶段的工作,先看如下所示的代码。
[-->PackageManagerService.java::构造函数]
public PackageManagerService(Context context,boolean factoryTest, booleanonlyCore) {......if(mSdkVersion <= 0) {/*mSdkVersion是PKMS的成员变量,定义的时候进行赋值,其值取自系统属性“ro.build.version.sdk”,即编译的SDK版本。如果没有定义,则APK就无法知道自己运行在Android哪个版本上*/Slog.w(TAG, "**** ro.build.version.sdk not set!");//打印一句警告}mContext = context;mFactoryTest = factoryTest;//假定为false,即运行在非工厂模式下mOnlyCore = onlyCore;//假定为false,即运行在普通模式下//如果此系统是eng版,则扫描Package后,不对package做dex优化mNoDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));//mMetrics用于存储与显示屏相关的一些属性,例如屏幕的宽/高尺寸,分辨率等信息mMetrics = new DisplayMetrics();//Settings是一个非常重要的类,该类用于存储系统运行过程中的一些设置,//下面进行详细分析 mSettings = new Settings();//1 addSharedUserLPw是什么?马上来分析mSettings.addSharedUserLPw("android.uid.system",Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM);mSettings.addSharedUserLPw("android.uid.phone",MULTIPLE_APPLICATION_UIDS ? RADIO_UID :FIRST_APPLICATION_UID,ApplicationInfo.FLAG_SYSTEM);mSettings.addSharedUserLPw("android.uid.log",MULTIPLE_APPLICATION_UIDS ? LOG_UID :FIRST_APPLICATION_UID,ApplicationInfo.FLAG_SYSTEM);mSettings.addSharedUserLPw("android.uid.nfc",MULTIPLE_APPLICATION_UIDS ? NFC_UID :FIRST_APPLICATION_UID,ApplicationInfo.FLAG_SYSTEM);......//第一段结束
刚进入构造函数,就会遇到第一个较为复杂的数据结构Setting及它的addSharedUserLPw函数。Setting的作用是管理Android系统运行过程中的一些设置信息。到底是哪些信息呢?来看下面的分析。
1. 初识Settings
先分析addSharedUserLPw函数。此处截取该函数的调用代码,如下所示:
mSettings.addSharedUserLPw("android.uid.system",//字符串Process.SYSTEM_UID, //系统进程使用的用户id,值为1000ApplicationInfo.FLAG_SYSTEM//标志系统Package
);
以此处的函数调用为例,我们为addSharedUserLPw传递了3个参数:
第一个是字符串“android.uid.system“;第二个是SYSTEM_UID,其值为1000;第三个是FLAG_SYSTEM标志,用于标识系统Package。
在进入对addSharedUserLPw函数的分析前,先介绍一下SYSTEM_UID 及相关知识。
(1) Android系统中UID/GID介绍
UID为用户ID的缩写,GID为用户组ID的缩写,这两个概念均与Linux系统中进程的权限管理有关。一般说来,每一个进程都会有一个对应的UID(即表示该进程属于哪个user,不同user有不同权限)。一个进程也可分属不同的用户组(每个用户组都有对应的权限)。
在Android平台中,系统定义的UID/GID在Process.java文件中,如下所示:
[-->Process.java]
//系统进程使用的UID/GID,值为1000public static final int SYSTEM_UID = 1000;//Phone进程使用的UID/GID,值为1001public static final int PHONE_UID = 1001;//shell进程使用的UID/GID,值为2000public static final int SHELL_UID = 2000;//使用LOG的进程所在的组的UID/GID为1007public static final int LOG_UID = 1007;//供WIF相关进程使用的UID/GID为1010public static final int WIFI_UID = 1010;//mediaserver进程使用的UID/GID为1013public static final int MEDIA_UID = 1013;//设置能读写SD卡的进程的GID为1015public static final int SDCARD_RW_GID = 1015;//NFC相关的进程的UID/GID为1025public static final int NFC_UID = 1025;//有权限读写内部存储的进程的GID为1023public static final int MEDIA_RW_GID = 1023;//第一个应用Package的起始UID为10000public static final int FIRST_APPLICATION_UID = 10000;//系统所支持的最大的应用Package的UID为99999public static final int LAST_APPLICATION_UID = 99999;//和蓝牙相关的进程的GID为2000public static final int BLUETOOTH_GID = 2000;
下面分析addSharedUserLPw函数,代码如下:
[-->Settings.java]
SharedUserSetting addSharedUserLPw(String name,int uid, int pkgFlags) {/*注意这里的参数:name为字符串”android.uid.system”,uid为1000,pkgFlags为ApplicationInfo.FLAG_SYSETM(以后简写为FLAG_SYSTEM)*///mSharedUsers是一个HashMap,key为字符串,值为SharedUserSetting对象SharedUserSetting s = mSharedUsers.get(name);if(s != null) {if (s.userId == uid) {return s;}return null;}//创建一个新的SharedUserSettings对象,并设置的userId为uids = new SharedUserSetting(name, pkgFlags);s.userId = uid;if(addUserIdLPw(uid, s, name)) {mSharedUsers.put(name, s);//将name与s键值对添加到mSharedUsers中保存return s;}return null;
}
从以上代码可知,Settings中有一个mSharedUsers成员,该成员存储的是字符串与SharedUserSetting键值对,也就是说以字符串为key得到对应的SharedUserSetting对象。
(2) SharedUserSetting分析
该例子来源于SystemUI的AndroidManifest.xml,如下所示:
[-->SystemUI的AndroidManifest.xml]
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.android.systemui"coreApp="true"android:sharedUserId="android.uid.system"android:process="system">
......
在xml中,声明了一个名为android:sharedUserId的属性,其值为“android.uid.system”。sharedUserId看起来和UID有关,确实如此,它有两个作用:
· 两个或多个声明了同一种sharedUserIds的APK可共享彼此的数据,并且可运行在同一进程中。
· 更重要的是,通过声明特定的sharedUserId,该APK所在进程将被赋予指定的UID。例如,本例中的SystemUI声明了system的uid,运行SystemUI的进程就可享有system用户所对应的权限了(实际上就是将该进程的uid设置为system的uid)。
提示:除了在AndroidManifest.xml中声明sharedUserId外,APK在编译时还必须使用对应的证书进行签名。例如本例的SystemUI,在其Android.mk中需要额外声明LOCAL_CERTIFICATE := platform,如此,才可获得指定的UID。
来看Android是如何设计相应数据结构的,如图2所示:
图2 SharedUserSetting类的关系图
由图2可知:
· Settings类定义了一个mSharedUsers成员,它是一个HashMap,以字符串(如“android.uid.system”)为Key,对应的Value是一个SharedUserSettings对象。
· SharedUserSetting派生自GrantedPermissions类,从GrantedPermissions类的命名可知,它和权限有关。SharedUserSetting定义了一个成员变量packages,类型为HashSet,用于保存声明了相同sharedUserId的Package的权限设置信息。
· 每个Package有自己的权限设置。权限的概念由PackageSetting类表达。该类继承自PackagesettingBase,而PackageSettingBase又继承自GrantedPermissions。
· Settings中还有两个成员,一个是mUserIds,另一个是mOtherUserIds,这两位成员的类型分别是ArrayList和SparseArray。其目的是以UID为索引,得到对应的SharedUserSettings对象。在一般情况下,以索引获取数组元素的速度,比以key获取HashMap中元素的速度要快很多。对mUserIds和mOtherUserIds的描述,这是典型的以空间换时间的做法。
下边来分析addUserIdLPw函数,它的功能就是将SharedUserSettings对象保存到对应的数组中,代码如下:
[-->Settings.java]
private boolean addUserIdLPw(int uid, Object obj, Objectname) {//uid不能超出限制。Android对UID进行了分类,应用APK所在进程的UID从10000开始,而系统APK所在进程小于10000if(uid >= PackageManagerService.FIRST_APPLICATION_UID + PackageManagerService.MAX_APPLICATION_UIDS){return false;}if(uid >= PackageManagerService.FIRST_APPLICATION_UID) {int N = mUserIds.size();//计算索引,其值是uid和FIRST_APPLICATION_UID的差final int index = uid - PackageManagerService.FIRST_APPLICATION_UID;while (index >= N) {mUserIds.add(null);N++;}......//判断该索引位置的内容是否为空,为空才保存mUserIds.set(index, obj);//mUserIds保存应用Package的UID}else {......mOtherUserIds.put(uid, obj);//系统Package的UID由mOtherUserIds保存}return true;
}
2. XML文件扫描
下面继续分析PKMS的构造函数,代码如下:
[-->PackageMangerService.java::构造函数]
......//接前一段String separateProcesses = SystemProperties.get("debug.separate_processes");if(separateProcesses != null && separateProcesses.length() > 0) {......}else {mDefParseFlags = 0;mSeparateProcesses = null;}//创建一个Installer对象,该对象和Native进程installd交互,以后分析installd时再来讨论它的作用mInstaller = new Installer();//得到一个WindowManager对象WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);Display d = wm.getDefaultDisplay();d.getMetrics(mMetrics); //获取当前设备的显示屏信息synchronized (mInstallLock) {synchronized (mPackages) {//创建一个ThreadHandler对象,实际就是创建一个带消息循环处理的线程,//该线程的工作是:程序安装的和卸载等。以后分析程序安装时会和它亲密接触mHandlerThread.start();//以ThreadHandler线程的消息循环(Looper对象)为参数创建一个PackageHandler,可知该Handler的handleMessage函数将运行在此线程上mHandler = new PackageHandler(mHandlerThread.getLooper());File dataDir = Environment.getDataDirectory();// mAppDataDir指向/data/data目录mAppDataDir = new File(dataDir, "data");// mUserAppDataDir指向/data/user目录mUserAppDataDir = new File(dataDir, "user");// mDrmAppPrivateInstallDir指向/data/app-private目录mDrmAppPrivateInstallDir = new File(dataDir, "app-private");/*创建一个UserManager对象,目前没有什么作用,但其前途将不可限量。根据Google的设想,未来手机将支持多个User,每个User将安装自己的应用,该功能为Andorid智能手机推向企业用户打下坚实基础*/mUserManager = new UserManager(mInstaller, mUserAppDataDir);//1 从文件中读权限readPermissions();//2 readLPw分析mRestoredSettings = mSettings.readLPw();long startTime = SystemClock.uptimeMillis();
以上代码中调用了两个函数,分别是readPermission和Setttings的readLPw,它们有什么作用呢?
(1) readPermissions函数分析
先来分析readPermissions函数,从其函数名可猜测到它和权限有关,代码如下:
[-->PackageManagerService.java]
void readPermissions() {// 指向/system/etc/permission目录,该目录中存储了和设备相关的一些权限信息File libraryDir = new File(Environment.getRootDirectory(), "etc/permissions");......for(File f : libraryDir.listFiles()) {//先处理该目录下的非platform.xml文件if (f.getPath().endsWith("etc/permissions/platform.xml")) {continue;}......//调用readPermissionFromXml解析此XML文件readPermissionsFromXml(f);}finalFile permFile = new File(Environment.getRootDirectory(), "etc/permissions/platform.xml");//解析platform.xml文件,看来该文件优先级最高readPermissionsFromXml(permFile);
}
readPermissions函数不就是调用readPermissionFromXml函数解析/system/etc/permissions目录下的文件吗?这些文件似乎都是XML文件。该目录下都有哪些XML文件呢?如图3所示。
图3 /system/etc/permissions目录下的内容
[-->platform.xml]
<permissions><!--建立权限名与gid的映射关系。如下面声明的BLUTOOTH_ADMIN权限,它对应的用户组是net_bt_admin。注意,该文件中的permission标签只对那些需要通过读写设备(蓝牙/camera)创建socket等进程划分了gid。因为这些权限涉及和Linux内核交互,所以需要在底层权限(由不同的用户组界定)和Android层权限(由不同的字符串界定)之间建立映射关系 -->
<permission name="android.permission.BLUETOOTH_ADMIN" ><group gid="net_bt_admin" />
</permission>
<permission name="android.permission.BLUETOOTH" ><group gid="net_bt" />
</permission>......<!--赋予对应uid相应的权限。如果下面一行表示uid为shell,那么就赋予它SEND_SMS的权限,其实就是把它加到对应的用户组中--><assign-permission name="android.permission.SEND_SMS" uid="shell" /><assign-permission name="android.permission.CALL_PHONE" uid="shell" /><assign-permission name="android.permission.READ_CONTACTS" uid="shell" /><assign-permission name="android.permission.WRITE_CONTACTS" uid="shell" /><assign-permission name="android.permission.READ_CALENDAR" uid="shell" />......<!-- 系统提供的Java库,应用程序运行时候必须要链接这些库,该工作由系统自动完成 --><library name="android.test.runner"file="/system/frameworks/android.test.runner.jar"/><library name="javax.obex"file="/system/frameworks/javax.obex.jar"/>
</permissions>
platform.xml文件中主要使用了如下4个标签:
· permission和group用于建立Linux层gid和Android层pemission之间的映射关系。
· assign-permission用于向指定的uid赋予相应的权限。这个权限由Android定义,用字符串表示。
· library用于指定系统库。当应用程序运行时,系统会自动为这些进程加载这些库。
真实设备上/system/etc/permission目录中的文件是从哪里的呢?
答案是,在编译阶段由不同硬件平台根据自己的配置信息复制相关文件到目标目录中得来的。这里给出一个例子,如图4所示:
图4 /system/etc/permission目录中文件的来源
了解了与XML相关的知识后,再来分析readPermissionFromXml函数。它的作用就是将XML文件中的标签以及它们之间的关系转换成代码中的相应数据结构,代码如下:
[-->PackageManagerService.java]
private void readPermissionsFromXml(File permFile){FileReader permReader = null;try{permReader = new FileReader(permFile);}try{XmlPullParser parser = Xml.newPullParser();parser.setInput(permReader);XmlUtils.beginDocument(parser, "permissions");while (true) {......String name = parser.getName();//解析group标签,前面介绍的XML文件中没有单独使用该标签的地方if ("group".equals(name)) {String gidStr = parser.getAttributeValue(null, "gid");if (gidStr != null) {int gid =Integer.parseInt(gidStr);//转换XML中的gid字符串为整型,并保存到mGlobalGids中mGlobalGids =appendInt(mGlobalGids, gid);} ......} else if ("permission".equals(name)) {//解析permission标签String perm = parser.getAttributeValue(null, "name");......perm = perm.intern();//调用readPermission处理readPermission(parser, perm);//下面解析的是assign-permission标签} else if("assign-permission".equals(name)) {String perm = parser.getAttributeValue(null, "name");......String uidStr = parser.getAttributeValue(null, "uid");......//如果是assign-permission,则取出uid字符串,然后获得Linux平台上的整型uid值int uid = Process.getUidForName(uidStr);......perm = perm.intern();//和assign相关的信息保存在mSystemPermissions中HashSet<String> perms = mSystemPermissions.get(uid);if (perms == null) {perms = newHashSet<String>();mSystemPermissions.put(uid, perms);}perms.add(perm);......} else if ("library".equals(name)) {//解析library标签String lname = parser.getAttributeValue(null, "name");String lfile = parser.getAttributeValue(null, "file");if (lname == null) {......} else if (lfile == null) {......} else {//将XML中的name和library属性值存储到mSharedLibraries中mSharedLibraries.put(lname,lfile);} ......} else if ("feature".equals(name)) {//解析feature标签String fname = parser.getAttributeValue(null, "name"); //在XML中定义的feature由FeatureInfo表达FeatureInfo fi = newFeatureInfo();fi.name = fname;//存储feature名和对应的FeatureInfo到mAvailableFeatures中mAvailableFeatures.put(fname, fi);}......} ......} ......
}
总结相关的数据结构,如图4所示。在每个类图中,首行是数据结构名,第二行是数据结构的类型,第三行是注释。图4中各种数据结构的目的是为了保存XML中各种标签及它们之间的关系。
图4 通过readPermissions函数建立的数据结构及其关系
(2) readLPw的“佐料”
readLPw函数的功能也是解析文件,不过这些文件的内容却是在PKMS正常启动后生成的。这里仅介绍作为readLPw“佐料”的文件的信息。文件的具体位置在Settings构造函数中指明,其代码如下:
[-->Settings.java]
Settings() {File dataDir = Environment.getDataDirectory();File systemDir = new File(dataDir, "system");//指向/data/system目录systemDir.mkdirs();//创建该目录....../*一共有5个文件,packages.xml和packages-backup.xml为一组,用于描述系统中所安装的Package的信息,其中backup是临时文件。PKMS先把数据写到backup中,信息都写成功后再改名成非backup的文件。其目的是防止在写文件过程中出错,导致信息丢失。packages-stopped.xml和packages-stopped-backup.xml为一组,用于描述系统中强制停止运行的pakcage的信息,backup也是临时文件。如果此处存在该临时文件,表明此前系统因为某种原因中断了正常流程。packages.list列出当前系统中应用级(即UID大于10000)Package的信息。*/mSettingsFilename = new File(systemDir, "packages.xml");mBackupSettingsFilename = new File(systemDir,"packages-backup.xml");mPackageListFilename = new File(systemDir, "packages.list");mStoppedPackagesFilename = new File(systemDir,"packages-stopped.xml");mBackupStoppedPackagesFilename = new File(systemDir, "packages-stopped-backup.xml");
}
上面5个文件共分为三组,这里简单介绍一下这些文件的来历(不考虑临时的backup文件)。
· packages.xml: PKMS扫描完目标文件夹后会创建该文件。当系统进行程序安装、卸载和更新等操作时,均会更新该文件。该文件保存了系统中与package相关的一些信息。
· packages.list:描述系统中存在的所有非系统自带的APK的信息。当这些程序有变动时,PKMS就会更新该文件。
· packages-stopped.xml:从系统自带的设置程序中进入应用程序页面,然后在选择强制停止(ForceStop)某个应用时,系统会将该应用的相关信息记录到此文件中。也就是该文件保存系统中被用户强制停止的Package的信息。
readLPw的函数功能就是解析其中的XML文件的内容,然后建立并更新对应的数据结构。
3. 第一阶段工作总结
扫描并解析XML文件,将其中的信息保存到特定的数据结构中。
3.2 构造函数分析之扫描Package
PKMS构造函数第二阶段的工作就是扫描系统中的APK了。由于需要逐个扫描文件,因此手机上装的程序越多,PKMS的工作量越大,系统启动速度也就越慢。
1. 系统库的dex优化
接着对PKMS构造函数进行分析,代码如下:
[-->PackageManagerService.java]
......
mRestoredSettings = mSettings.readLPw();//接第一段的结尾
longstartTime = SystemClock.uptimeMillis();//记录扫描开始的时间
//定义扫描参数
intscanMode = SCAN_MONITOR | SCAN_NO_PATHS | SCAN_DEFER_DEX;
if(mNoDexOpt) {scanMode|= SCAN_NO_DEX; //在控制扫描过程中是否对APK文件进行dex优化
}
finalHashSet<String> libFiles = new HashSet<String>();
// mFrameworkDir指向/system/frameworks目录
mFrameworkDir = newFile(Environment.getRootDirectory(),"framework");
// mDalvikCacheDir指向/data/dalvik-cache目录
mDalvikCacheDir= new File(dataDir, "dalvik-cache");
boolean didDexOpt = false;
/*获取Java启动类库的路径,在init.rc文件中通过BOOTCLASSPATH环境变量输出,该值如下/system/framework/core.jar:/system/frameworks/core-junit.jar:/system/frameworks/bouncycastle.jar:/system/frameworks/ext.jar:/system/frameworks/framework.jar:/system/frameworks/android.policy.jar:/system/frameworks/services.jar:/system/frameworks/apache-xml.jar:/system/frameworks/filterfw.jar该变量指明了framework所有核心库及文件位置
*/
StringbootClassPath = System.getProperty("java.boot.class.path");
if(bootClassPath != null) {String[] paths = splitString(bootClassPath, ':');for(int i=0; i<paths.length; i++) {try{ //判断该jar包是否需要重新做dex优化if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) {/*将该jar包文件路径保存到libFiles中,然后通过mInstall对象发送命令给installd,让其对该jar包进行dex优化*/libFiles.add(paths[i]);mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true);didDexOpt = true;}} ......}} ......//将framework-res.apk添加到libFiles中。framework-res.apk定义了系统常用的//资源,还有几个重要的Activity,如长按Power键后弹出的选择框libFiles.add(mFrameworkDir.getPath() + "/framework-res.apk");//列举/system/frameworks目录中的文件String[] frameworkFiles = mFrameworkDir.list();if(frameworkFiles != null) {......//判断该目录下的apk或jar文件是否需要做dex优化。处理方式同上}
}
2. 扫描系统Package
清空cache文件后,PKMS终于进入重点段了。接下来看PKMS第二阶段工作的核心内容,即扫描Package,相关代码如下:
[-->PackageManagerService.java]
//创建文件夹监控对象,监视/system/frameworks目录。利用了Linux平台的inotify机制mFrameworkInstallObserver = new AppDirObserver(mFrameworkDir.getPath(),OBSERVER_EVENTS, true);mFrameworkInstallObserver.startWatching();/*调用scanDirLI函数扫描/system/frameworks目录,这个函数很重要,稍后会再分析。注意,在第三个参数中设置了SCAN_NO_DEX标志,因为该目录下的package在前面的流程中已经过判断并根据需要做过dex优化了*/scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR,scanMode | SCAN_NO_DEX, 0);//创建文件夹监控对象,监视/system/app目录mSystemAppDir = new File(Environment.getRootDirectory(),"app");mSystemInstallObserver = new AppDirObserver(mSystemAppDir.getPath(), OBSERVER_EVENTS, true);mSystemInstallObserver.startWatching();//扫描/system/app下的packagescanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);//监视并扫描/vendor/app目录mVendorAppDir = new File("/vendor/app");mVendorInstallObserver = new AppDirObserver(mVendorAppDir.getPath(), OBSERVER_EVENTS, true);mVendorInstallObserver.startWatching();//扫描/vendor/app下的packagescanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);//和installd交互。以后单独分析installdmInstaller.moveFiles();
由以上代码可知,PKMS将扫描以下几个目录。
· /system/frameworks:该目录中的文件都是系统库,例如framework.jar、services.jar、framework-res.apk。不过scanDirLI只扫描APK文件,所以framework-res.apk是该目录中唯一“受宠”的文件。
· /system/app:该目录下全是默认的系统应用,例如Browser.apk、SettingsProvider.apk等。
· /vendor/app:该目录中的文件由厂商提供,即厂商特定的APK文件,不过目前市面上的厂商都把自己的应用放在/system/app目录下。
注意:本书把这三个目录称为系统Package目录,以区分后面的非系统Package目录。
(1) scanDirLI函数分析
scanDirLI函数的代码如下:
[-->PackageManagerService.java]
private void scanDirLI(File dir, int flags, int scanMode, long currentTime) {String[] files = dir.list();//列举该目录下的文件......int i;for(i=0; i<files.length; i++) {File file = new File(dir, files[i]);if (!isPackageFilename(files[i])) {continue; //根据文件名后缀,判断是否为APK文件。这里只扫描APK文件}/*调用scanPackageLI函数扫描一个特定的文件,返回值是PackageParser的内部类Package,该类的实例代表一个APK文件,所以它就是和APK文件对应的数据结构*/PackageParser.Package pkg = scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime);if (pkg == null && (flags &PackageParser.PARSE_IS_SYSTEM) == 0 &&mLastScanError ==PackageManager.INSTALL_FAILED_INVALID_APK) {//注意此处flags的作用,只有非系统Package扫描失败,才会删除该文件file.delete();}}
}
接着来分析scanPackageLI函数。
(2) 初会scanPackageLI函数
首次相遇的scanPackageLI函数的代码如下:
[-->PackageManagerService.java]
private PackageParser.Package scanPackageLI(FilescanFile, int parseFlags,int scanMode, long currentTime)
{mLastScanError = PackageManager.INSTALL_SUCCEEDED;StringscanPath = scanFile.getPath();parseFlags |= mDefParseFlags;//默认的扫描标志,正常情况下为0//创建一个PackageParser对象PackageParser pp = new PackageParser(scanPath);pp.setSeparateProcesses(mSeparateProcesses);// mSeparateProcesses为空pp.setOnlyCoreApps(mOnlyCore);// mOnlyCore为false/*调用PackageParser的parsePackage函数解析APK文件。注意,这里把代表屏幕信息的mMetrics对象也传了进去*/finalPackageParser.Package pkg = pp.parsePackage(scanFile,scanPath, mMetrics, parseFlags);......PackageSetting ps = null;PackageSetting updatedPkg;....../*这里略去一大段代码,主要是关于Package升级方面的工作。读者可能会比较好奇:既然是升级,一定有新旧之分,如果这里刚解析后得到的Package信息是新,那么旧Package的信息从何得来?还记得”readLPw的‘佐料’”这一小节提到的package.xml文件吗?此文件中存储的就是上一次扫描得到的Package信息。对比这两次的信息就知道是否需要做升级了。这部分代码比较繁琐,但不影响我们正常分析。感兴趣的读者可自行研究*///判断是否需要设置PARSE_FORWARD_LOCK标志,这个标志针对资源文件和Class文件//不在同一个目录的情况。目前只有/vendor/app目录下的扫描会使用该标志。这里不讨论这种情况。if (ps != null &&!ps.codePath.equals(ps.resourcePath))parseFlags|= PackageParser.PARSE_FORWARD_LOCK;String codePath = null;String resPath = null;if((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) {......//这里不考虑PARSE_FORWARD_LOCK的情况。}else {resPath = pkg.mScanPath;}codePath = pkg.mScanPath; //mScanPath指向该APK文件所在位置//设置文件路径信息,codePath和resPath都指向APK文件所在位置setApplicationInfoPaths(pkg, codePath, resPath);//调用第二个scanPackageLI函数return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE, currentTime);
}
scanPackageLI函数首先调用PackageParser对APK文件进行解析。根据前面的介绍可知,PackageParser完成了从物理文件到对应数据结构的转换。下面来分析这个PackageParser。
(3) PackageParser分析
PackageParser主要负责APK文件的解析,即解析APK文件中的AndroidManifest.xml,代码如下:
[-->PackageParser.java]
public Package parsePackage(File sourceFile, String destCodePath,DisplayMetrics metrics, int flags) {mParseError = PackageManager.INSTALL_SUCCEEDED;mArchiveSourcePath = sourceFile.getPath();XmlResourceParser parser = null;AssetManager assmgr = null;Resources res = null;boolean assetError = true;try{assmgr = new AssetManager();int cookie = assmgr.addAssetPath(mArchiveSourcePath);if (cookie != 0) {res = new Resources(assmgr, metrics, null);assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0,Build.VERSION.RESOURCES_SDK_INT);/*获得一个XML资源解析对象,该对象解析的是APK中的AndroidManifest.xml文件。以后再讨论AssetManager、Resource及相关的知识*/parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);assetError = false;} ......//出错处理String[] errorText = new String[1];Package pkg = null;Exception errorException = null;try {//调用另外一个parsePackage函数pkg = parsePackage(res, parser, flags, errorText);} ............//错误处理parser.close();assmgr.close();//保存文件路径,都指向APK文件所在的路径pkg.mPath = destCodePath;pkg.mScanPath = mArchiveSourcePath;pkg.mSignatures = null;return pkg;
}
以上代码中调用了另一个同名的PackageParser函数,就是解析AndroidManifest.xml中的各种标签,这里只提取其中相关的代码:
[-->PackageParser.java]
private Package parsePackage(Resources res, XmlResourceParser parser, int flags, String[] outError)throws XmlPullParserException, IOException {AttributeSet attrs = parser;mParseInstrumentationArgs = null;mParseActivityArgs = null;mParseServiceArgs= null;mParseProviderArgs = null;//得到Package的名字,其实就是得到AndroidManifest.xml中package属性的值,每个APK都必须定义该属性String pkgName = parsePackageName(parser, attrs, flags, outError);......int type;......//以pkgName名字为参数,创建一个Package对象。后面的工作就是解析XML并填充该Package信息finalPackage pkg = new Package(pkgName);boolean foundApp = false;......//下面开始解析该文件中的标签,由于这段代码功能简单,所以这里仅列举相关函数while(如果解析未完成){......String tagName = parser.getName(); //得到标签名if(tagName.equals("application")){......//解析application标签parseApplication(pkg,res, parser, attrs, flags, outError);} elseif (tagName.equals("permission-group")) {......//解析permission-group标签parsePermissionGroup(pkg, res, parser, attrs, outError);} elseif (tagName.equals("permission")) {......//解析permission标签parsePermission(pkg, res, parser, attrs, outError);} else if(tagName.equals("uses-permission")){//从XML文件中获取uses-permission标签的属性sa= res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestUsesPermission);//取出属性值,也就是对应的权限使用声明String name = sa.getNonResourceString(com.android.internal.R.styleable.AndroidManifestUsesPermission_name);//添加到Package的requestedPermissions数组if(name != null && !pkg.requestedPermissions.contains(name)) {pkg.requestedPermissions.add(name.intern());}} else if (tagName.equals("uses-configuration")){/*该标签用于指明本package对硬件的一些设置参数,目前主要针对输入设备(触摸屏、键盘等)。游戏类的应用可能对此有特殊要求。*/ConfigurationInfocPref = new ConfigurationInfo();......//解析该标签所支持的各种属性pkg.configPreferences.add(cPref);//保存到Package的configPreferences数组}......//对其他标签解析和处理}
}
上面代码展示了AndroidManifest.xml解析的流程,其中比较重要的函数是parserApplication,它用于解析application标签及其子标签(Android的四大组件在application标签中声明)。
图5表示了PackageParser及其内部重要成员的信息。
图5 PackageParser大家族
由图5可知:
· PackageParser定了相当多的内部类,这些内部类的作用就是保存对应的信息。解析AndroidManifest.xml文件得到的信息由Package保存。从该类的成员变量可看出,和Android四大组件相关的信息分别由activites、receivers、providers、services保存。由于一个APK可声明多个组件,因此activites和receivers等均声明为ArrayList。
· 以PackageParser.Activity为例,它从Component<ActivityIntentInfo>派生。Component是一个模板类,元素类型是ActivityIntentInfo,此类的顶层基类是IntentFilter。PackageParser.Activity内部有一个ActivityInfo类型的成员变量,该变量保存的就是四大组件中Activity的信息。细心的读者可能会有疑问,为什么不直接使用ActivityInfo,而是通过IntentFilter构造出一个使用模板的复杂类型PackageParser.Activity呢?原来,Package除了保存信息外,还需要支持Intent匹配查询。例如,设置Intent的Action为某个特定值,然后查找匹配该Intent的Activity。由于ActivityIntentInfo是从IntentFilter派生的,因此它它能判断自己是否满足该Intent的要求,如果满足,则返回对应的ActivityInfo。在后续章节会详细讨论根据Intent查询特定Activity的工作流程。
· PackageParser定了一个轻量级的数据结构PackageLite,该类仅存储Package的一些简单信息。我们在介绍Package安装的时候,会遇到PackageLite。
(4) 与scanPackageLI再相遇
在PackageParser扫描完一个APK后,此时系统已经根据该APK中AndroidManifest.xml,创建了一个完整的Package对象,下一步就是将该Package加入到系统中。此时调用的函数就是另外一个scanPackageLI,其代码如下:
[-->PackageManagerService.java::scanPackageLI函数]
private PackageParser.PackagescanPackageLI(PackageParser.Package pkg,int parseFlags, int scanMode, long currentTime) {File scanFile = new File(pkg.mScanPath);......mScanningPath = scanFile;//设置package对象中applicationInfo的flags标签,用于标示该Package为系统Packageif((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;}//1 下面这句if判断极为重要,见下面的解释if(pkg.packageName.equals("android")) {synchronized (mPackages) {if (mAndroidApplication != null) {......mPlatformPackage = pkg;pkg.mVersionCode = mSdkVersion;mAndroidApplication = pkg.applicationInfo;mResolveActivity.applicationInfo = mAndroidApplication;mResolveActivity.name = ResolverActivity.class.getName();mResolveActivity.packageName = mAndroidApplication.packageName;mResolveActivity.processName = mAndroidApplication.processName;mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert;mResolveActivity.exported = true;mResolveActivity.enabled = true;//mResoveInfo的activityInfo成员指向mResolveActivitymResolveInfo.activityInfo = mResolveActivity;mResolveInfo.priority = 0;mResolveInfo.preferredOrder = 0;mResolveInfo.match = 0;mResolveComponentName = new ComponentName(mAndroidApplication.packageName, mResolveActivity.name);}}}
......
刚进入scanPackageLI函数,我们就发现了一个极为重要的内容,即单独判断并处理packageName为“android”的Package。和该Package对应的APK是framework-res.apk,有图为证,如图6所示为该APK的AndroidManifest.xml中的相关内容。
图6 framework-res.apk的AndroidManifest.xml
该Package和系统息息相关,因此它得到了PKMS的特别青睐,主要体现在以下几点。
· mPlatformPackage成员用于保存该Package信息。
· mAndroidApplication用于保存此Package中的ApplicationInfo。
· mResolveActivity指向用于表示ChooserActivity信息的ActivityInfo。
· mResolveInfo为ResolveInfo类型,它用于存储系统解析Intent(经IntentFilter的过滤)后得到的结果信息,例如满足某个Intent的Activity的信息。由前面的代码可知,mResolveInfo的activityInfo其实指向的就是mResolveActivity。
注意:在从PKMS中查询满足某个Intent的Activity时,返回的就是ResolveInfo,再根据ResolveInfo的信息得到具体的Activity。
继续对scanPackageLI函数的分析。
[-->PackageManagerService::scanPackageLI函数]
......//mPackages用于保存系统内的所有Package,以packageName为keyif(mPackages.containsKey(pkg.packageName) || mSharedLibraries.containsKey(pkg.packageName)) {return null;}File destCodeFile = new File(pkg.applicationInfo.sourceDir);File destResourceFile = new File(pkg.applicationInfo.publicSourceDir);SharedUserSettingsuid = null;//代表该Package的SharedUserSetting对象PackageSetting pkgSetting = null;//代表该Package的PackageSetting对象synchronized(mPackages) {......//此段代码大约有300行左右,主要做了以下几方面工作/*1 如果该Packge声明了” uses-librarie”话,那么系统要判断该library是否在mSharedLibraries中2 如果package声明了SharedUser,则需要处理SharedUserSettings相关内容,由Settings的getSharedUserLPw函数处理3 处理pkgSetting,通过调用Settings的getPackageLPw函数完成4 调用verifySignaturesLP函数,检查该Package的signature*/}final long scanFileTime = scanFile.lastModified();final boolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0;//确定运行该package的进程的进程名pkg.applicationInfo.processName = fixProcessName(pkg.applicationInfo.packageName,pkg.applicationInfo.processName,pkg.applicationInfo.uid);if(mPlatformPackage == pkg) {dataPath = new File (Environment.getDataDirectory(),"system");pkg.applicationInfo.dataDir = dataPath.getPath();} else {/*getDataPathForPackage函数返回该package的目录,一般是/data/data/packageName/*/dataPath = getDataPathForPackage(pkg.packageName, 0);if(dataPath.exists()) {......//如果该目录已经存在,则要处理uid的问题} else {......//向installd发送install命令,实际上就是在/data/data下建立packageName目录。后续将分析installd相关知识int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid);//为系统所有user安装此程序mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid);if (dataPath.exists()) {pkg.applicationInfo.dataDir = dataPath.getPath();} ......if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir!= null) {......//为该Package确定native library所在目录一般是/data/data/packagename/lib}}}//如果该APK包含了native动态库,则需要将它们从APK文件中解压并复制到对应目录中if(pkg.applicationInfo.nativeLibraryDir != null) {try {final File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);final String dataPathString = dataPath.getCanonicalPath();//从2.3开始,系统package的native库统一放在/system/lib下。所以系统不会提取系统Package目录下APK包中的native库if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryDir)){} else if (nativeLibraryDir.getParentFile().getCanonicalPath().equals(dataPathString)) {boolean isSymLink;try {isSymLink = S_ISLNK(Libcore.os.lstat(nativeLibraryDir.getPath()).st_mode);} ......//判断是否为链接,如果是,需要删除该链接if (isSymLink) {mInstaller.unlinkNativeLibraryDirectory(dataPathString);}//在lib下建立和CPU类型对应的目录,例如ARM平台的是arm/,MIPS平台的是mips/NativeLibraryHelper.copyNativeBinariesIfNeededLI(scanFile, nativeLibraryDir);} else {mInstaller.linkNativeLibraryDirectory(dataPathString, pkg.applicationInfo.nativeLibraryDir);}} ......}pkg.mScanPath= path;if((scanMode&SCAN_NO_DEX) == 0) {......//对该APK做dex优化performDexOptLI(pkg,forceDex, (scanMode&SCAN_DEFER_DEX);}//如果该APK已经存在,要先杀掉运行该APK的进程if((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {killApplication(pkg.applicationInfo.packageName, pkg.applicationInfo.uid);}....../*在此之前,四大组件信息都属于Package的私有财产,现在需要把它们登记注册到PKMS内部的财产管理对象中。这样,PKMS就可对外提供统一的组件信息,而不必拘泥于具体的Package*/synchronized(mPackages) {if ((scanMode&SCAN_MONITOR) != 0) {mAppDirs.put(pkg.mPath, pkg);}mSettings.insertPackageSettingLPw(pkgSetting, pkg);mPackages.put(pkg.applicationInfo.packageName,pkg);//处理该Package中的Provider信息int N = pkg.providers.size();int i;for (i=0;i<N; i++) {PackageParser.Providerp = pkg.providers.get(i);p.info.processName=fixProcessName(pkg.applicationInfo.processName, p.info.processName, pkg.applicationInfo.uid);//mProvidersByComponent提供基于ComponentName的Provider信息查询mProvidersByComponent.put(new ComponentName(p.info.packageName,p.info.name), p);......}//处理该Package中的Service信息N =pkg.services.size();r = null;for (i=0;i<N; i++) {PackageParser.Service s = pkg.services.get(i);mServices.addService(s);}//处理该Package中的BroadcastReceiver信息N =pkg.receivers.size();r = null;for (i=0;i<N; i++) {PackageParser.Activity a = pkg.receivers.get(i);mReceivers.addActivity(a,"receiver");......}//处理该Package中的Activity信息N = pkg.activities.size();r =null;for (i=0; i<N; i++) {PackageParser.Activity a =pkg.activities.get(i);mActivities.addActivity(a,"activity");//后续将详细分析该调用}//处理该Package中的PermissionGroups信息N = pkg.permissionGroups.size();......//permissionGroups处理N =pkg.permissions.size();......//permissions处理N =pkg.instrumentation.size();......//instrumentation处理if(pkg.protectedBroadcasts != null) {N = pkg.protectedBroadcasts.size();for(i=0; i<N; i++) {mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));}}......//Package的私有财产终于完成了公有化改造return pkg;
}
到此这个长达800行的代码就分析完了,下面总结一下Package扫描的流程。
(5) scanDirLI函数总结
scanDirLI用于对指定目录下的APK文件进行扫描,如图7所示为该函数的调用流程。
图7 scanDirLI工作流程总结
扫描完APK文件后,Package的私有财产就充公了。PKMS提供了好几个重要数据结构来保存这些财产,这些数据结构的相关信息如图8所示。
图8 PKMS中重要的数据结构
图8用UML的类图来表示PKMS中重要的数据结构。每个类图的第一行为成员变量名,第二行为数据类型,第三行为注释说明。
3. 扫描非系统Package
非系统Package就是指那些不存储在系统目录下的APK文件,这部分代码如下:
[-->PackageManagerService.java::构造函数第三部分]
if (!mOnlyCore) {//mOnlyCore用于控制是否扫描非系统PackageIterator<PackageSetting> psit = mSettings.mPackages.values().iterator();while (psit.hasNext()) {......//删除系统package中那些不存在的APK}mAppInstallDir = new File(dataDir,"app");......//删除安装不成功的文件及临时文件if (!mOnlyCore) {//在普通模式下,还需要扫描/data/app以及/data/app_private目录mAppInstallObserver = new AppDirObserver(mAppInstallDir.getPath(), OBSERVER_EVENTS, false);mAppInstallObserver.startWatching();scanDirLI(mAppInstallDir, 0, scanMode, 0);mDrmAppInstallObserver = newAppDirObserver(mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false);mDrmAppInstallObserver.startWatching();scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,scanMode,0);} else {mAppInstallObserver = null;mDrmAppInstallObserver = null;}}
结合前述代码,这里总结几个存放APK文件的目录。
· 系统Package目录包括:/system/frameworks、/system/app和/vendor/app。
· 非系统Package目录包括:/data/app、/data/app-private。
4. 第二阶段工作总结
PKMS构造函数第二阶段的工作任务非常繁重,要创建比较多的对象,所以它是一个耗时耗内存的操作。
在工作中,我们一直想优化该流程以加快启动速度,例如延时扫描不重要的APK,或者保存Package信息到文件中,然后在启动时从文件中恢复这些信息以减少APK文件读取并解析XML的工作量。但是一直没有一个比较完满的解决方案,原因有很多。比如APK之间有着比较微妙的依赖关系,因此到底延时扫描哪些APK,尚不能确定。
另外,笔者感到比较疑惑的一个问题是:对于多核CPU架构,PKMS可以启动多个线程以扫描不同的目录,但是目前代码中还没有寻找到相关的蛛丝马迹。难道此处真的就不能优化了吗?读者如果有更好的解决方案,不妨和大家分享一下。
3.3 构造函数分析之扫尾工作
下面分析PKMS第三阶段的工作,这部分任务比较简单,就是将第二阶段收集的信息再集中整理一次,比如将有些信息保存到文件中,相关代码如下:
[-->PackageManagerService.java::构造函数]
......mSettings.mInternalSdkPlatform= mSdkVersion;//汇总并更新和Permission相关的信息updatePermissionsLPw(null, null, true, regrantPermissions,regrantPermissions);//将信息写到package.xml、package.list及package-stopped.xml文件中mSettings.writeLPr();Runtime.getRuntime().gc();mRequiredVerifierPackage= getRequiredVerifierLPr();......//PKMS构造函数返回
}
3.4 PKMS构造函数总结
从流程角度看,PKMS构造函数的功能还算清晰,无非是扫描XML或APK文件,但是其中涉及的数据结构及它们之间的关系却较为复杂。这里有一些建议供读者参考:
· 理解PKMS构造函数工作的三个阶段及其各阶段的工作职责。
· 了解PKMS第二阶段工作中解析APK文件的几个关键步骤,可参考图7。
· 了解重点数据结构的名字和大体功能。
如果对PKMS的分析到此为止,则未免有些太小视它了。下面将分析几个重量级的知识点,期望能带领读者全方位认识PKMS。
4 APK Installation分析
本节将分析APK的安装及相关处理流程,从adb install开始。
4.1 adb install分析
adb install有多个参数,这里仅考虑最简单的,如adb install frameworktest.apk。adb是一个命令,install是它的参数。此处直接跳到处理install参数的代码:
[-->commandline.c]
int adb_commandline(int argc, char **argv) {......if(!strcmp(argv[0], "install")) {......//调用install_app函数处理return install_app(ttype, serial, argc, argv);}......
}
install_app函数也在commandline.c中定义,代码如下:
[-->commandline.c]
int install_app(transport_type transport, char*serial, int argc, char** argv)
{//要安装的APK现在还在Host机器上,要先把APK复制到手机中。//这里需要设置复制目标的目录,如果安装在内部存储中,则目标目录为/data/local/tmp;//如果安装在SD卡上,则目标目录为/sdcard/tmp。static const char *const DATA_DEST = "/data/local/tmp/%s";static const char *const SD_DEST = "/sdcard/tmp/%s";const char* where = DATA_DEST;char apk_dest[PATH_MAX];char verification_dest[PATH_MAX];char* apk_file;char* verification_file = NULL;int file_arg = -1;int err;int i;for (i =1; i < argc; i++) {if(*argv[i] != '-') {file_arg = i;break;}else if (!strcmp(argv[i], "-i")) {i++;}else if (!strcmp(argv[i], "-s")) {where = SD_DEST; //-s参数指明该APK安装到SD卡上}}......apk_file = argv[file_arg];......//获取目标文件的全路径,如果安装在内部存储中,则目标全路径为/data/local/tmp/安装包名,//调用do_sync_push将此APK传送到手机的目标路径err = do_sync_push(apk_file, apk_dest, 1 /* verify APK */);...... //1 4.0新增了一个安装包Verification功能,相关知识稍后分析//2 执行pm命令,这个函数很有意思pm_command(transport,serial, argc, argv);......cleanup_apk://3 在手机中执行shell rm 命令,删除刚才传送过去的目标APK文件。为什么要删除呢delete_file(transport, serial, apk_dest);return err;
}
以上代码中共有三个关键点,分别是:
· 4.0新增了APK安装过程中的Verification的功能。其实就是在安装时,把相关信息发送给指定的Verification程序(另外一个APK),由它对要安装的APK进行检查(Verify)。这部分内容在后面分析APK 安装时会介绍。
· 调用pm_command进行安装,这是一个比较有意思的函数,稍后对其进行分析。
· 安装完后,执行shell rm删除刚才传送给手机的APK文件。为什么会删除呢?因为PKMS在安装过程中会将该APK复制一份到/data/app目录下,所以/data/local/tmp下的对应文件就可以删除了。这部分代码在后面也能见到。
先来分析pm_command命令。为什么说它很有意思呢?
4.2 pm分析
pm_command代码如下:
[-->commandline.c]
static int pm_command(transport_type transport,char* serial,int argc, char** argv)
{char buf[4096];snprintf(buf,sizeof(buf), "shell:pm");......//准备参数//发送"shell:pm install 参数"给手机端的adbdsend_shellcommand(transport, serial, buf);return 0;
}
手机端的adbd在收到客户端发来的shellpm命令时会启动一个shell,然后在其中执行pm。pm是什么?为什么可以在shell下执行?
pm实际上是一个脚本,其内容如下:
[-->pm]
# Script to start "pm" on the device,which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/frameworks/pm.jar
exec app_process $base/bincom.android.commands.pm.Pm "$@"
在编译system.image时,Android.mk中会将该脚本复制到system/bin目录下。从pm脚本的内容来看,它就是通过app_process执行pm.jar包的main函数。在分析Zygote时,已经介绍了app_process是一个Native进程,它通过创建虚拟机启动了Zygote,从而转变为一个Java进程。实际上,app_process还可以通过类似的方法(即先创建Dalvik虚拟机,然后执行某个类的main函数)来转变成其他Java程序。
注意:Android系统中常用的monkeytest、pm、am等(这些都是脚本文件)都是以这种方式启动的,所以严格地说,app_process才是Android Java进程的老祖宗。
下面来分析pm.java,app_process执行的就是它定义的main函数,它相当于Java进程的入口函数,其代码如下:
[-->pm.java]
public static void main(String[] args) {new Pm().run(args);//创建一个Pm对象,并执行它的run函数
}
//直接分析run函数
public void run(String[] args) {boolean validCommand = false;......//获取PKMS的binder客户端mPm= IPackageManager.Stub.asInterface(ServiceManager.getService("package"));......mArgs = args;String op = args[0];mNextArg = 1;......//处理其他命令,这里仅考虑install的处理if("install".equals(op)) {runInstall();return;}......
}
接下来分析pm.java的runInstall函数,代码如下:
[-->pm.java]
private void runInstall() {int installFlags = 0;String installerPackageName = null;String opt;while ((opt=nextOption()) != null) {if (opt.equals("-l")) {installFlags |= PackageManager.INSTALL_FORWARD_LOCK;} else if (opt.equals("-r")) {installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;} else if (opt.equals("-i")) {installerPackageName = nextOptionData();...... //参数解析} ......}final Uri apkURI;final Uri verificationURI;final String apkFilePath = nextArg();System.err.println("/tpkg: " + apkFilePath);if(apkFilePath != null) {apkURI = Uri.fromFile(new File(apkFilePath));}......//获取Verification Package的文件位置final String verificationFilePath = nextArg();if(verificationFilePath != null) {verificationURI = Uri.fromFile(new File(verificationFilePath));}else {verificationURI = null;}//创建PackageInstallObserver,用于接收PKMS的安装结果PackageInstallObserver obs = new PackageInstallObserver();try{//1 调用PKMS的installPackageWithVerification完成安装mPm.installPackageWithVerification(apkURI, obs,installFlags,installerPackageName,verificationURI,null);synchronized(obs) {while(!obs.finished) {try {obs.wait();//等待安装结果} ......}if(obs.result == PackageManager.INSTALL_SUCCEEDED) {System.out.println("Success");//安装成功,打印Success} ......//安装失败,打印失败原因} ......}
}
Pm解析参数后,最终通过PKMS的Binder客户端调用installPackageWithVerification以完成后续的安装工作,所以,下面进入PKMS看看安装到底是怎么一回事。
4.3 installPackageWithVerification函数分析
installPackageWithVerification的代码如下:
[-->PackageManagerService.java::installPackageWithVerification函数]
public void installPackageWithVerification(UripackageURI,IPackageInstallObserverobserver,int flags, String installerPackageName, Uri verificationURI,ManifestDigest manifestDigest) {//检查客户端进程是否具有安装Package的权限。在本例中,该客户端进程是shellmContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,null);final int uid = Binder.getCallingUid();final int filteredFlags;if(uid == Process.SHELL_UID || uid == 0) {......//如果通过shell pm的方式安装,则增加INSTALL_FROM_ADB标志filteredFlags = flags | PackageManager.INSTALL_FROM_ADB;} else {filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB;}//创建一个Message,code为INIT_COPY,将该消息发送给之前在PKMS构造函数中//创建的mHandler对象,将在另外一个工作线程中处理此消息final Message msg = mHandler.obtainMessage(INIT_COPY);//创建一个InstallParams,其基类是HandlerParamsmsg.obj = new InstallParams(packageURI, observer,filteredFlags,installerPackageName,verificationURI,manifestDigest);mHandler.sendMessage(msg);
}
installPackageWithVerification函数倒是蛮清闲,简简单单创建几个对象,然后发送INIT_COPY消息给mHandler就退出了。根据之前在PKMS构造函数中介绍的知识可知,mHandler被绑定到另外一个工作线程(借助ThreadHandler对象的Looper)中,所以该INIT_COPY消息也将在那个工作线程中进行处理。
1. INIT_COPY处理
INIT_COPY只是安装流程的第一步。先来看相关代码:
[-->PackageManagerService.java::handleMesssage]
public void handleMessage(Message msg) {try {doHandleMessage(msg);//调用doHandleMessage函数} ......
}void doHandleMessage(Message msg) {switch(msg.what) {case INIT_COPY: {//1 这里记录的是params的基类类型HandlerParams,实际类型为InstallParamsHandlerParams params = (HandlerParams) msg.obj;//idx为当前等待处理的安装请求的个数int idx = mPendingInstalls.size();if(!mBound) {/*很多读者可能想不到,APK的安装居然需要使用另外一个APK提供的服务,该服务就是DefaultContainerService,由DefaultCotainerService.apk提供,下面的connectToService函数将调用bindService来启动该服务*/if(!connectToService()) {return;}else {//如果已经连上,则以idx为索引,将params保存到mPendingInstalls中mPendingInstalls.add(idx, params);}} else {mPendingInstalls.add(idx, params);if(idx == 0) {//如果安装请求队列之前的状态为空,则表明要启动安装mHandler.sendEmptyMessage(MCS_BOUND);}}break;}......//后续再分析
这里假设之前已经成功启动了DefaultContainerService(以后简称DCS),并且idx为零,所以这是PKMS首次处理安装请求下一个将要处理的是MCS_BOUND消息。
注意:connectToService在调用bindService时会传递一个DefaultContainerConnection类型的对象,以接收服务启动的结果。当该服务成功启动后,此对象的onServiceConnected被调用,其内部也将发送MCS_BOUND消息给mHandler。
2. MCS_BOUND处理
现在,安装请求的状态从INIT_COPY变成MCS_BOUND了,此时的处理流程时怎样的呢?依然在doHandleMessage函数中,直接从对应的case开始,代码如下:
[-->PackageManagerService.java]
......//接doHandleMesage中的switch/case
case MCS_BOUND: {if(msg.obj != null) {mContainerService= (IMediaContainerService) msg.obj;}if(mContainerService == null) {......//如果没法启动该service,则不能安装程序mPendingInstalls.clear();} else if(mPendingInstalls.size() > 0) {HandlerParams params = mPendingInstalls.get(0);if(params != null) {//调用params对象的startCopy函数,该函数由基类HandlerParams定义if(params.startCopy()) {......if(mPendingInstalls.size() > 0) {mPendingInstalls.remove(0);//删除队列头}if (mPendingInstalls.size() == 0) {if (mBound) {......//如果安装请求都处理完了,则需要和Service断绝联系,//通过发送MSC_UNB消息处理断交请求。读者可自行研究此情况的处理流程removeMessages(MCS_UNBIND);Message ubmsg = obtainMessage(MCS_UNBIND);sendMessageDelayed(ubmsg, 10000);}} else {//如果还有未处理的请求,则继续发送MCS_BOUND消息。//为什么不通过一个循环来处理所有请求呢mHandler.sendEmptyMessage(MCS_BOUND);}}} ......}break;
MCS_BOUND的处理就是调用HandlerParams的startCopy函数。在深入分析前,应先认识一下HandlerParams及相关的对象。
(1) HandlerParams和InstallArgs介绍
除了HandlerParams家族外,这里提前请出另外一个家族InstallArgs及其成员,如图8所示。
图8 HandlerParams及InstallArgs家族成员
由图8可知:
· HandlerParams和InstallArgs均为抽象类。
· HandlerParams有三个子类,分别是InstallParams、MoveParams和MeasureParams。其中,InstallParams用于处理APK的安装,MoveParams用于处理某个已安装APK的搬家请求(例如从内部存储移动到SD卡上),MeasureParams用于查询某个已安装的APK占据存储空间的大小(例如在设置程序中得到的某个APK使用的缓存文件的大小)。
· 对于InstallParams来说,它还有两个伴儿,即InstallArgs的派生类FileInstallArgs和SdInstallArgs。其中,FileInstallArgs针对的是安装在内部存储的APK,而SdInstallArgs针对的是那些安装在SD卡上的APK。
本节将讨论用于内部存储安装的FileInstallArgs。
在前面MCS_BOUND的处理中,首先调用InstallParams的startCopy函数,该函数由其基类HandlerParams实现,代码如下:
[-->PackageManagerService.java::HandlerParams.startCopy]
final boolean startCopy() {boolean res;try {//MAX_RETIRES目前为4,表示尝试4次安装,如果还不成功,则认为安装失败if(++mRetries > MAX_RETRIES) {mHandler.sendEmptyMessage(MCS_GIVE_UP);handleServiceError();return false;} else {handleStartCopy();//1 调用派生类的handleStartCopy函数res= true;}} ......handleReturnCode();//2 调用派生类的handleReturnCode,返回处理结果return res;
}
在上述代码中,基类的startCopy将调用子类实现的handleStartCopy和handleReturnCode函数。下面来看InstallParams是如何实现这两个函数的。
(2) InstallParams分析
先来看派生类InstallParams的handleStartCopy函数,代码如下:
[-->PackageManagerService::InstallParams.handleStartCopy]
public void handleStartCopy() throwsRemoteException {int ret= PackageManager.INSTALL_SUCCEEDED;//根据adb install的参数,判断安装位置finalboolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;finalboolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;PackageInfoLite pkgLite = null;if(onInt && onSd) {//APK不能同时安装在内部存储和SD卡上ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;} else {finallong lowThreshold;//获取DeviceStorageMonitorService的binder客户端finalDeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager.getService(DeviceStorageMonitorService.SERVICE);if(dsm == null) {lowThreshold = 0L;} else {//从DSMS查询内部空间最小余量,默认是总空间的10%lowThreshold = dsm.getMemoryLowThreshold();}try {//授权DefContainerService URI读权限mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE,packageURI,Intent.FLAG_GRANT_READ_URI_PERMISSION);//1 调用DCS的getMinimalPackageInfo函数,得到一个PackageLite对象pkgLite =mContainerService.getMinimalPackageInfo(packageURI,flags,lowThreshold);} finally ......//撤销URI授权//PacakgeLite的recommendedInstallLocation成员变量保存该APK推荐的安装路径int loc = pkgLite.recommendedInstallLocation;if (loc== PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {ret= PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;}//2 根据DCS返回的安装路径,还需要调用installLocationPolicy进行检查loc = installLocationPolicy(pkgLite, flags);if(!onSd && !onInt) {if(loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {flags |= PackageManager.INSTALL_EXTERNAL;flags &=~PackageManager.INSTALL_INTERNAL;} ......//处理安装位置为内部存储的情况}}//3 创建一个安装参数对象,对于安装位置为内部存储的情况,args的真实类型为FileInstallArgsfinalInstallArgs args = createInstallArgs(this);mArgs = args;if (ret == PackageManager.INSTALL_SUCCEEDED) {final int requiredUid = mRequiredVerifierPackage == null ? -1:getPackageUid(mRequiredVerifierPackage);if(requiredUid != -1 && isVerificationEnabled()) {......//4 待会再讨论verification的处理} else {//5 调用args的copyApk函数ret= args.copyApk(mContainerService, true);}}mRet =ret;//确定返回值
}
在以上代码中,一共列出了五个关键点,总结如下:
· 调用DCS的getMinimalPackageInfo函数,将得到一个PackageLite对象,该对象是一个轻量级的用于描述APK的结构(相比PackageParser.Package来说)。在这段代码逻辑中,主要想取得其recommendedInstallLocation的值。此值表示该APK推荐的安装路径。
· 调用installLocationPolicy检查推荐的安装路径。例如系统Package不允许安装在SD卡上。
· createInstallArgs将根据安装位置创建不同的InstallArgs。如果是内部存储,则返回FileInstallArgs,否则为SdInstallArgs。
· 在正式安装前,应先对该APK进行必要的检查。这部分代码后续再介绍。
· 调用InstallArgs的copyApk。对本例来说,将调用FileInstallArgs的copyApk函数。
下面围绕这五个基本关键点展开分析,其中installLocationPolicy和createInstallArgs比较简单,读者可自行研究。
3. handleStartCopy分析
(1) DefaultContainerService分析
首先分析DCS的getMinimalPackageInfo函数,其代码如下:
[-->DefaultContainerService.java::getMinimalPackageInfo]
public PackageInfoLite getMinimalPackageInfo(finalUri fileUri, int flags, long threshold) {//注意该函数的参数:fileUri指向该APK的文件路径(此时还在/data/local/tmp下)PackageInfoLite ret = new PackageInfoLite();......Stringscheme = fileUri.getScheme();......StringarchiveFilePath = fileUri.getPath();DisplayMetrics metrics = new DisplayMetrics();metrics.setToDefaults();//调用PackageParser的parsePackageLite解析该APK文件PackageParser.PackageLite pkg =PackageParser.parsePackageLite(archiveFilePath,0);if (pkg == null) {//解析失败......//设置错误值return ret;}ret.packageName = pkg.packageName;ret.installLocation = pkg.installLocation;ret.verifiers = pkg.verifiers;//调用recommendAppInstallLocation,取得一个合理的安装位置ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath, flags, threshold);return ret;
}
APK可在AndroidManifest.xml中声明一个安装位置,不过DCS除了解析该位置外,还需要做进一步检查,这个工作由recommendAppInstallLocation函数完成。
(2) InstallArgs的copyApk函数分析
至此,我们已经得到了一个合适的安装位置(先略过Verification这一步)。下一步工作就由copyApk来完成。根据函数名可知该函数将完成APK文件的复制工作,此中会有蹊跷吗?来看下面的代码。
[-->PackageManagerService.java::InstallArgs.copyApk]
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {if (temp){/*本例中temp参数为true,createCopyFile将在/data/app下创建一个临时文件。临时文件名为vmdl-随机数.tmp。为什么会用这样的文件名呢?因为PKMS通过Linux的inotify机制监控了/data/app,目录,如果新复制生成的文件名后缀为apk,将触发PKMS扫描。为了防止发生这种情况,这里复制生成的文件才有了如此奇怪的名字*/createCopyFile();}File codeFile = new File(codeFileName);......ParcelFileDescriptor out = null;try {out = ParcelFileDescriptor.open(codeFile,ParcelFileDescriptor.MODE_READ_WRITE);}......int ret= PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;try {mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE,packageURI,Intent.FLAG_GRANT_READ_URI_PERMISSION);//调用DCS的copyResource,该函数将执行复制操作,最终结果是/data/local/tmp//下的APK文件被复制到/data/app下,文件名也被换成vmdl-随机数.tmpret= imcs.copyResource(packageURI, out);} finally {......//关闭out,撤销URI授权}return ret;
}
关于临时文件,这里提供一个示例,如图9所示。
图9 createCopyFile生成的临时文件
由图9可知:/data/app下有两个文件,第一个是正常的APK文件,第二个是createCopyFile生成的临时文件。
4. handleReturnCode分析
在HandlerParams的startCopy函数中,handleStartCopy执行完之后,将调用handleReturnCode开展后续工作,代码如下:
[-->PackageManagerService.java::InstallParams.HandleParams]
void handleReturnCode() {if(mArgs != null) {//调用processPendingInstall函数,mArgs指向之前创建的FileInstallArgs对象processPendingInstall(mArgs, mRet);}
}private void processPendingInstall(finalInstallArgs args, final intcurrentStatus) {//向mHandler中抛一个Runnable对象mHandler.post(new Runnable() {public void run() {mHandler.removeCallbacks(this);//创建一个PackageInstalledInfo对象PackageInstalledInfo res = new PackageInstalledInfo();res.returnCode = currentStatus;res.uid = -1;res.pkg = null;res.removedInfo = new PackageRemovedInfo();if(res.returnCode == PackageManager.INSTALL_SUCCEEDED) {//1 调用FileInstallArgs的doPreInstallargs.doPreInstall(res.returnCode);synchronized (mInstallLock) {//2 调用installPackageLI进行安装installPackageLI(args, true, res);}//3 调用FileInstallArgs的doPostInstallargs.doPostInstall(res.returnCode);}final boolean update = res.removedInfo.removedPackage != null;boolean doRestore = (!update&& res.pkg != null && res.pkg.applicationInfo.backupAgentName!= null);int token;//计算一个ID号if(mNextInstallToken < 0) mNextInstallToken = 1;token = mNextInstallToken++;//创建一个PostInstallData对象PostInstallData data = new PostInstallData(args, res);//保存到mRunningInstalls结构中,以token为keymRunningInstalls.put(token, data);if (res.returnCode ==PackageManager.INSTALL_SUCCEEDED && doRestore) {......//备份恢复的情况暂时不考虑}if(!doRestore) {//4 抛一个POST_INSTALL消息给mHandler进行处理Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);mHandler.sendMessage(msg);}}});
}
由上面代码可知,handleReturnCode主要做了4件事情:
· 调用InstallArgs的doPreInstall函数,在本例中是FileInstallArgs的doPreInstall函数。
· 调用PKMS的installPackageLI函数进行APK安装,该函数内部将调用InstallArgs的doRename对临时文件进行改名。另外,还需要扫描此APK文件。至此,该APK中的私有财产就全部被登记到PKMS内部进行保存了。
· 调用InstallArgs的doPostInstall函数,在本例中是FileInstallArgs的doPostInstall函数。
· 此时,该APK已经安装完成(不论失败还是成功),继续向mHandler抛送一个POST_INSTALL消息,该消息携带一个token,通过它可从mRunningInstalls数组中取得一个PostInstallData对象。
5. POST_INSTALL处理
现在需要处理POST_INSTALL消息,因为adb install还等着安装结果呢。相关代码如下:
[-->PackageManagerService.java::doHandleMessage]
......//接前面的switch/case
case POST_INSTALL: {PostInstallData data = mRunningInstalls.get(msg.arg1);mRunningInstalls.delete(msg.arg1);boolean deleteOld = false;if (data!= null) {InstallArgs args = data.args;PackageInstalledInfo res = data.res;if(res.returnCode == PackageManager.INSTALL_SUCCEEDED) {res.removedInfo.sendBroadcast(false, true);Bundle extras = new Bundle(1);extras.putInt(Intent.EXTRA_UID, res.uid);final boolean update = res.removedInfo.removedPackage != null;if (update) {extras.putBoolean(Intent.EXTRA_REPLACING, true);}//发送PACKAGE_ADDED广播sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, res.pkg.applicationInfo.packageName,extras, null, null);if (update) {/*如果是APK升级,那么发送PACKAGE_REPLACE和MY_PACKAGE_REPLACED广播。二者不同之处在于PACKAGE_REPLACE将携带一个extra信息*/}Runtime.getRuntime().gc();if(deleteOld) {synchronized (mInstallLock) {//调用FileInstallArgs的doPostDeleteLI进行资源清理res.removedInfo.args.doPostDeleteLI(true);}}if(args.observer != null) {try {// 向pm通知安装的结果args.observer.packageInstalled(res.name, res.returnCode);} ......}} break;
4.4 APK 安装流程总结
没想到APK的安装流程竟然如此复杂,其目的无非是让APK中的私人财产公有化。
这里要总结APK安装过程中的几个重要步骤,如图10所示。
图10 APK安装流程
图10中列出以下内容:
· 安装APK到内部存储空间这一工作流程涉及的主要对象包括:PKMS、DCS、InstallParams和FileInstallArgs。
· 此工作流程中每个对象涉及到的关键函数。
· 对象之间的调用通过虚线表达,调用顺序通过①②③等标明。
todo 这个UML顺序图不标准
5 queryIntentActivities分析
PKMS除了负责Android系统中Package的安装、升级、卸载外,还有一项很重要的职责,就是对外提供统一的信息查询功能,其中包括查询系统中匹配某Intent的Activities、BroadCastReceivers或Services等。本节将以查询匹配某Intent的Activities为例,介绍PKMS在这方便提供的服务。
正式分析queryIntentActivities之前,先来认识一下Intent及IntentFilter。
5.1 Intent及IntentFilter介绍
1. Intent介绍
Intent中文是“意图”的意思,它是Android系统中一个很重要的概念,其基本思想来源于对日常生活及行为的高度抽象。
意图,是一个非常抽象的概念,在编码设计中,如何将它实例化呢?Android系统明确指定的一个Intent可由两方面属性来衡量。
· 主要属性:包括Action和Data。其中Action用于表示该Intent所表达的动作意图、Data用于表示该Action所操作的数据。
· 次要属性:包括Category、Type、Component和Extras。其中Category表示类别,Type表示数据的MIME类型,Component可用于指定特定的Intent响应者(例如指定广播接收者为某Package的某个BroadcastReceiver),Extras用于承载其他的信息。
进一步对Intent进行分类:
· Explicit Intents:这类Intent明确指明了要找哪些人。在代码中通过setComponent或setClass来锁定目标对象。处理这种Intent,工作就很轻松了。
· Implicit Intents:这一类Intents只标明了工作内容,而没有指定具体人名。
2. IntentFilter介绍
IntentFilter来表达自己的诉求。Andorid规定了3项内容:
· Action:求职方支持的Intent动作(和Intent中的Action对应)。
· Category:求职方支持的Intent种类(和Intent的Category对应)。
· Data:求职方支持的Intent 数据(和Intent的Data对应,包括URI和MIME类型)。
马上要做的工作就是匹配查询。在Android中,该工作被称为Intent Resolution。在做匹配工作时,将以Intent Filter列出的3项内容为参考标准,具体步骤如下:
· 首先匹配IntentFilter的Action,如果Intent设置的Action不满足IntentFilter的Action,则匹配失败。如果IntentFilter未设定Action,则匹配成功。
· 然后检查IntentFilter的Category,匹配方法同Action的匹配,唯一有些例外的是Category为CATEGORY_DEFAULT的情况。
· 最后检查Data。Data的匹配过程比较繁琐,因为它和IntentFilter设置的Data内容有关,见接下来的介绍。
IntentFilter中的Data可以包括两个内容。
· URI:完整格式为“scheme://host:port/path”,包含4个部分,scheme、host、port和path。其中host和port合起来标示URI authority,用于指明服务器的网络地址(IP加端口号)。由于URI最多可包含,4个部分,因此要根据情况相应部分做匹配检查。
· Date type:指定数据的MIME类型
要特别注意的是,URI中也可以携带数据的类型信息,所以在匹配过程中,还需要考虑URI中指定的数据类型。
提示:关于具体的匹配流程,请读者务必阅读SDK docs/guide/topics/intents/intents-filters.html中的说明。
5.2 Activity信息的管理
前面在介绍PKMS扫描APK时提到,PKMS将解析得到的Package私有的Activity信息加入到自己的数据结构mActivities中保存。先来回顾一下代码:
[-->PacakgeManagerService.java::scanPackageLI]
......//此时APK文件已经解析完成N = pkg.activities.size();//取出该APK中包含的Activities信息r = null;for (i=0; i<N; i++) {PackageParser.Activity a = pkg.activities.get(i);a.info.processName = fixProcessName(pkg.applicationInfo.processName,a.info.processName,pkg.applicationInfo.uid);mActivities.addActivity(a,"activity");//1 加到mActivities中保存
}
上面的代码中有两个比较重要的数据结构,如图11所示:
图11 相关数据结构示意图
· ActivityIntentResolver数据结构内部有一个mActivities变量,它以ComponetName为Key,保存PackageParser.Activity对象
· 从APK中解析得到的所有和Activity相关的信息(包括在XML中声明的IntentFilter标签)都由PacakgeParser.Activity来保存。
前面代码中调用addActivity函数完成了私有信息的公有化。addActivity函数的代码如下:
[-->PacakgeManagerService.java::ActivityIntentResolver.addActivity]
public final void addActivity(PackageParser.Activity a, String type) {final boolean systemApp = isSystemApp(a.info.applicationInfo);//将Component和Activity保存到mActivities中mActivities.put(a.getComponentName(), a);final int NI = a.intents.size();for(int j=0; j<NI; j++) {//ActivityIntentInfo存储的就是XML中声明的IntentFilter信息PackageParser.ActivityIntentInfo intent = a.intents.get(j);if(!systemApp && intent.getPriority() > 0 && "activity".equals(type)) {//非系统APK的priority必须为0。后续分析中将介绍priority的作用intent.setPriority(0);}addFilter(intent);//接下来将分析这个函数}
}
下面来分析addFilter函数,这里涉及较多的复杂数据结构,代码如下:
[-->IntentResolver.java::IntentResolver.addFilter]
public void addFilter(F f) {......mFilters.add(f);//mFilters保存所有IntentFilter信息//除此之外,为了加快匹配工作的速度,还需要分类保存IntentFilter信息//下边register_xxx函数的最后一个参数用于打印信息int numS = register_intent_filter(f, f.schemesIterator(), mSchemeToFilter, " Scheme: ");int numT = register_mime_types(f, " Type: ");if(numS == 0 && numT == 0) {register_intent_filter(f, f.actionsIterator(), mActionToFilter," Action: ");}if(numT != 0) {register_intent_filter(f, f.actionsIterator(), mTypedActionToFilter, " TypedAction: ");}
}
正如代码注释中所说,为了加快匹配工作的速度,这里使用了泛型编程并定义了较多的成员变量。下面总结一下这些变量的作用(注意,除mFilters为HashSet<F>类型外,其他成员变量的类型都是HashMap<String, ArrayList<F>>,其中F为模板参数)。
· mSchemeToFilter:用于保存URI中与schema相关的IntentFilter信息。
· mActionToFilter:用于保存仅设置Action条件的IntentFilter信息。
· mTypedActionToFilter:用于保存既设置了Action又设置了Data的MIME类型的IntentFilter信息。
· mFilters:用于保存所有IntentFilter信息
· mWildTypeToFilter:用于保存设置了Data类型类似“image/*”的IntentFilter,但是设置MIME类型类似“Image/jpeg”的不算在此类。
· mTypeToFilter:除了包含mWildTypeToFilter外,还包含那些指明了Data类型为确定参数的IntentFilter信息,例如“image/*”和”image/jpeg“等都包含在mTypeToFilter中。
· mBaseTypeToFilter:包含MIME中Base 类型的IntentFilter信息,但不包括Sub type为“*”的IntentFilter。
不妨举个例子来说明这些变量的用法。
假设,在XML中声明一个IntentFilter,代码如下:
<intent-filter android:label="test"><action android:name="android.intent.action.VIEW" />data android:mimeType="audio/*" android:scheme="http"
</intent-filter>
那么:
· 在mTypedActionToFilter中能够以“android.intent.action.VIEW”为key找到该IntentFilter。
· 在mWildTypeToFilter和mTypeToFilter中能够以“audio”为key找到该IntentFilter。
· 在mSchemeToFilter中能够以”http“为key找到该IntentFilter。
下面来分析Intent匹配查询工作。
5.3 Intent 匹配查询分析
1. 客户端查询
客户端通过ApplicationPackageManager输出的queryIntentActivities函数向PKMS发起一次查询请求,代码如下:
[-->ApplicationPackageManager.java::queryIntentActivities]
public List<ResolveInfo>queryIntentActivities(Intent intent, int flags) {try {return mPM.queryIntentActivities(intent, //下面这句话很重要intent.resolveTypeIfNeeded(mContext.getContentResolver()),flags);}......
}
如果Intent的Data包含一个URI,那么就需要查询该URI的提供者(即ContentProvider)以取得该数据的数据类型。读者可自行阅读resolveTypeIfNeeded函数的代码。
下面来看PKMS对匹配查询的处理。
2. queryIntentActivities分析
该函数代码如下:
[-->PacakgeManagerService.java::queryIntentActivities]
public List<ResolveInfo>queryIntentActivities(Intent intent,String resolvedType, int flags) {final ComponentName comp = intent.getComponent();if(comp != null) {//1 Explicit的Intents,直接根据component得到对应的ActivityInfofinal List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);final ActivityInfo ai = getActivityInfo(comp, flags);if (ai != null) {final ResolveInfo ri = new ResolveInfo();//ResovlerInfo的activityInfo指向查询得到的ActivityInfori.activityInfo = ai;list.add(ri);}return list;}synchronized (mPackages) {final String pkgName = intent.getPackage();if (pkgName == null) {//3 Implicit Intents,我们重点分析此中情况return mActivities.queryIntent(intent, resolvedType, flags);}//Intent指明了PackageName,比Explicit Intents情况差一点final PackageParser.Package pkg = mPackages.get(pkgName);if (pkg != null) {//2 其实是从该Package包含的Activities中进行匹配查询return mActivities.queryIntentForPackage(intent, resolvedType, flags, pkg.activities);}return new ArrayList<ResolveInfo>();}
}
上边代码分三种情况:
· 如果Intent指明了Component,则直接查询该Component对应的ActivityInfo。
· 如果Intent指明了Package名,则根据Package名找到该Package,然后再从该Package包含的Activities中进行匹配查询。
· 如果上面条件都不满足,则需要在全系统范围内进行匹配查询,这就是queryIntent的工作。
queryIntent函数的代码如下:
public List<ResolveInfo> queryIntent(Intentintent, String resolvedType, intflags) {mFlags =flags;//调用基类的queryIntent函数return super.queryIntent(intent, resolvedType, (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0);
}
基类queryIntent里设置了最多四轮匹配关卡,然后逐一执行匹配工作。具体的匹配代码由buildResolveList完成。
6 installd介绍
在前面对PKMS构造函数分析时介绍过一个Installer类型的对象mInstaller,它通过socket和后台服务installd交互,以完成一些重要操作。这里先回顾一下PKMS中mInstaller的调用方法:
//创建一个Installer对象
mInstaller = new Installer();
//对某个APK文件进行dexopt优化
mInstaller.dexopt(paths[i], Process.SYSTEM_UID,true);
//扫描完系统Package后,调用moveFiles函数
mInstaller.moveFiles();
//当存储空间不足时,调用该函数清理存储空间
mInstaller.freeCache(freeStorageSize);
Installer的种种行为都和其背后的installd有关。下面来分析installd。
6.1 installd概貌
installd是一个native进程,代码非常简单,其功能就是启动一个socket,然后处理来自Installer的命令,其代码如下:
[-->installd.c]
int main(const int argc, const char *argv[]) {char buf[BUFFER_MAX];struct sockaddr addr;socklen_t alen;int lsocket, s, count;if (初始化全局变量,如果失败则退出) {initialize_globals();initialize_directories();......}......lsocket = android_get_control_socket(SOCKET_PATH);listen(lsocket, 5);fcntl(lsocket, F_SETFD, FD_CLOEXEC);for (;;){alen = sizeof(addr);s = accept(lsocket, &addr, &alen);fcntl(s, F_SETFD, FD_CLOEXEC);for(;;) {unsigned short count;readx(s, &count, sizeof(count));//执行installer发出的命令,具体解释见下文execute(s, buf);}close(s);}return 0;
}
installd支持的命令及参数信息都保存在数据结构cmds中,代码如下:
[-->installd.c]
struct cmdinfo cmds[] = {//第二个变量是参数个数,第三个参数是命令响应函数{"ping", 0,do_ping },{"install", 3,do_install },{"dexopt", 3,do_dexopt },{"movedex", 2,do_move_dex },{"rmdex", 1,do_rm_dex },{"remove", 2,do_remove },{"rename", 2, do_rename },{"freecache", 1,do_free_cache },{"rmcache", 1,do_rm_cache },{"protect", 2,do_protect },{"getsize", 4,do_get_size },{"rmuserdata", 2,do_rm_user_data },{"movefiles", 0,do_movefiles },{"linklib", 2,do_linklib },{"unlinklib", 1,do_unlinklib },{"mkuserdata", 3,do_mk_user_data },{"rmuser", 1,do_rm_user },
};
下面来分析相关的几个命令。
6.2 dexOpt命令分析
PKMS在需要对一个APK或jar包做dex优化时,会发送dexopt命令给installd,相应的处理函数为do_dexopt,代码如下:
[-->installd.c]
static int do_dexopt(char **arg, charreply[REPLY_MAX])
{return dexopt(arg[0], atoi(arg[1]), atoi(arg[2]));
}
[-->commands.c]
int dexopt(const char *apk_path, uid_t uid, intis_public)
{struct utimbuf ut;struct stat apk_stat, dex_stat;char dex_path[PKG_PATH_MAX];char dexopt_flags[PROPERTY_VALUE_MAX];char *end;int res, zip_fd=-1, odex_fd=-1;......//取出系统级的dexopt_flags参数property_get("dalvik.vm.dexopt-flags", dexopt_flags,"");strcpy(dex_path, apk_path);end = strrchr(dex_path, '.');if (end!= NULL) {strcpy(end, ".odex");if(stat(dex_path, &dex_stat) == 0) {return 0;}}//得到一个字符串,用于描述dex文件名,位于/data/dalvik-cache/下if(create_cache_path(dex_path, apk_path)) {return -1;}memset(&apk_stat, 0, sizeof(apk_stat));stat(apk_path, &apk_stat);zip_fd = open(apk_path, O_RDONLY, 0);......unlink(dex_path);odex_fd = open(dex_path, O_RDWR | O_CREAT | O_EXCL, 0644);......pid_t pid;pid = fork();if (pid== 0) {......//uid设置//创建一个新进程,然后对exec dexopt进程进行dex优化run_dexopt(zip_fd, odex_fd, apk_path, dexopt_flags);exit(67); } else {//installd父进程将等待dexopt完成优化工作res = wait_dexopt(pid, apk_path);......}......//资源清理return -1;
}
让人大跌眼镜的是,dex优化工作竟然由installd委派给dexopt进程来实现。dex优化后会生成一个dex文件,一般位于/data/dalvik-cache/目录中。这里给出一个示例,如图12所示。
图12 dex文件示例
提示 dexopt进程由android源码/dalvik/dexopt/OptMain.cpp定义。感兴趣的读者可深入研究dex优化的工作原理。
6.3 movefiles命令分析
PKMS扫描完系统Package后,将发送该命令给installd,相应处理函数的代码如下:
[-->installd.c]
static int do_movefiles(char **arg, char reply[REPLY_MAX])
{return movefiles();
}
[-->commands.c]
int movefiles()
{DIR *d;int dfd,subfd;struct dirent *de;struct stat s;char buf[PKG_PATH_MAX+1];int bufp, bufe, bufi, readlen;char srcpkg[PKG_NAME_MAX];char dstpkg[PKG_NAME_MAX];char srcpath[PKG_PATH_MAX];char dstpath[PKG_PATH_MAX];int dstuid=-1, dstgid=-1;int hasspace;//打开/system/etc/updatecmds/目录d = opendir(UPDATE_COMMANDS_DIR_PREFIX);if (d ==NULL) {goto done;}dfd = dirfd(d);while((de = readdir(d))) {......//解析该目录下的文件,然后执行对应操作}closedir(d);
done:return 0;
}
movefiles的功能和系统升级有关。
6.4 doFreeCache
当系统空间不够时,DSMS会调用PKMS的freeStorageAndNotify函数进行空间清理。该工作真正的实施者是installd,相应的处理命令为do_free_cache,其代码如下:
[-->installd.c]
static int do_free_cache(char **arg, char reply[REPLY_MAX])
{return free_cache((int64_t)atoll(arg[0]));
}
[-->commands.c]
int free_cache(int64_t free_size)
{const char *name;int dfd,subfd;DIR *d;struct dirent *de;int64_t avail;avail = disk_free();//获取当前系统的剩余空间大小if(avail < 0) return -1;if(avail >= free_size) return 0;d = opendir(android_data_dir.path);//打开/data/目录dfd = dirfd(d);while((de = readdir(d))) {if (de->d_type != DT_DIR) continue;name = de->d_name;......//略过.和..文件subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY);//删除/data及各级子目录中的cache文件夹delete_dir_contents_fd(subfd, "cache");close(subfd);......//如果剩余空间恢复正常,则返回}closedir(d);return -1;//清理空间后,仍然不满足要求
}
7 本章学习指导
这里提出一些学习建议供读者参考。
· 从工作流程上看,PKMS包含几条重要的主线。一条是PKMS自身启动时构造函数的工作流程,另外几条和APK安装、卸载相关。每一条主线的难度都比较大,读者可结合日常工作的需求进行单独研究,例如研究如何加快构造函数的执行时间等。
· 从数据结构上看,PKMS涉及非常多的数据类型。如果对每个数据结构进行孤立分析,很容易陷入不可自拔的状态。笔者建议不妨跳出各种数据结构的具体形态,只从目的及功能角度去考虑。这里需要读者仔细查看前面的重要数据结构及说明示意图。