Surface的创建涉及三个部分:
- App 进程 App需要将自己的内容显示在屏幕上,所以App负责发起Surface创建请求,创建好Surface后, 就可以直接可以在canvas上画图等,最终都会保存到Surface里的buffer里,最后由SurfaceFlinger合成并显示。
- System_Server进程 主要是其中的WindowManagerService, 负责接收APP请求,向SurfaceFlinger发起具体的请求创建Surface, 且WMS需要创建Surface的辅助管理类,如SurfaceControl。
- SurfaceFlinger 为App进程创建具体的Surface, 在SurfaceFlinger里对应成Layer, 然后负责管理、合成显示。
Surface相关的基础知识介绍
显示层(Layer)和屏幕组成
图8-10 屏幕组成示意图
从图8-10中可以看出:
· 屏幕位于一个三维坐标系中,其中Z轴从屏幕内指向屏幕外。
· 编号为①②③的矩形块叫显示层(Layer)。每一层有自己的属性,例如颜色、透明度、所处屏幕的位置、宽、高等。除了属性之外,每一层还有自己对应的显示内容,也就是需要显示的图像。
在Android中,Surface系统工作时,会由SurfaceFlinger对这些按照Z轴排好序的显示层进行图像混合,混合后的图像就是在屏幕上看到的美妙画面了。这种按Z轴排序的方式符合我们在日常生活中的体验,例如前面的物体会遮挡住后面的物体。
注意,Surface系统中定义了一个名为Layer类型的类,为了区分广义概念上的Layer和代码中的Layer,这里称广义层的Layer为显示层,以免混淆。
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)。这个虚拟设备将不同硬件厂商实现的真实设备统一在一个框架下,这样应用层就可以通过标准的接口进行图形/图像的输入和输出了。图8-12展示了FBD示意图:
图8-12 Linux系统中的FBD示意图
从上图中可以看出,应用层通过标准的ioctl或mmap等系统调用,就可以操作显示设备,用起来非常方便。这里,把mmap的调用列出来,相信大部分读者都知道它的作用了。
FrameBuffer中的Buffer,就是通过mmap把设备中的显存映射到用户空间的,在这块缓冲上写数据,就相当于在屏幕上绘画。
(2)PageFlipping
图形/图像数据和音频数据不太一样,我们一般把音频数据叫音频流,它是没有边界的, 而图形/图像数据是一帧一帧的,是有边界的。这一点非常类似UDP和TCP之间的区别。所以在图形/图像数据的生产/消费过程中,人们使用了一种叫PageFlipping的技术。
PageFlipping的中文名叫画面交换,其操作过程如下所示:
· 分配一个能容纳两帧数据的缓冲,前面一个缓冲叫FrontBuffer,后面一个缓冲叫BackBuffer。
· 消费者使用FrontBuffer中的旧数据,而生产者用新数据填充BackBuffer,二者互不干扰。
· 当需要更新显示时,BackBuffer变成FrontBuffer,FrontBuffer变成BackBuffer。如此循环,这样就总能显示最新的内容了。这个过程很像我们平常的翻书动作,所以它被形象地称为PageFlipping。
说白了,PageFlipping其实就是使用了一个只有两个成员的帧缓冲队列,以后在分析数据传输的时候还会见到诸如dequeue和queue的操作。
图像混合
我们知道,在AudioFlinger中有混音线程,它能将来自多个数据源的数据混合后输出,那么,SurfaceFlinger是不是也具有同样的功能呢? 答案是肯定的,否则它就不会叫Flinger了。Surface系统支持软硬两个层面的图像混合:
· 软件层面的混合:例如使用copyBlt进行源数据和目标数据的混合。
· 硬件层面的混合:使用Overlay系统提供的接口。
无论是硬件还是软件层面,都需将源数据和目标数据进行混合,混合需考虑很多内容,例如源的颜色和目标的颜色叠加后所产生的颜色。关于这方面的知识,读者可以学习计算机图形/图像学。这里只简单介绍一下copyBlt和Overlay。
· copyBlt,从名字上看,是数据拷贝,它也可以由硬件实现,例如现在很多的2D图形加速就是将copyBlt改由硬件来实现,以提高速度的。但不必关心这些,我们只需关心如何调用copyBlt相关的函数进行数据混合即可。
· Overlay方法必须有硬件支持才可以,它主要用于视频的输出,例如视频播放、摄像机摄像等,因为视频的内容往往变化很快,所以如改用硬件进行混合效率会更高。
Surface源码分析
public class Surface implements Parcelable {
private static final String TAG = "Surface";
// Guarded state.
final Object mLock = new Object(); // protects the native state
private String mName;
long mNativeObject; // package scope only for SurfaceControl access
private long mLockedObject;
private int mGenerationId; // incremented each time mNativeObject changes
private final Canvas mCanvas = new CompatibleCanvas();
}
复制代码
我们可以看到,surface是parcelable的,这意味着它是可以跨进程传递的。 既然是parcelable,那么就离不开两个函数:
- writeToParcel
- readFromParcel
下面具体看看这两个函数的实现。
1.1 writeToParcel()
@Override
public void writeToParcel(Parcel dest, int flags) {
if (dest == null) {
throw new IllegalArgumentException("dest must not be null");
}
synchronized (mLock) {
// NOTE: This must be kept synchronized with the native parceling code
// in frameworks/native/libs/Surface.cpp
dest.writeString(mName);
dest.writeInt(mIsSingleBuffered ? 1 : 0);
nativeWriteToParcel(mNativeObject, dest);
}
if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
release();
}
}
复制代码
这里的代码很简单,就几行:
- 写入name
- 写入是否是singleBuffer
- 写入一个native层的指针
关键在于nativeWriteToParcel这个函数:
---》/frameworks/base/core/jni/android_view_Surface.cpp
static void nativeWriteToParcel(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel == NULL) {
doThrowNPE(env);
return;
}
//通过Java层的指针,还原出Native层的surface对象
sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
parcel->writeStrongBinder( self != 0 ? IInterface::asBinder(self->getIGraphicBufferProducer()) : NULL);
}
复制代码
看源码网址:xref
在这里我们也可以看到,其实surface对象是一对的,应用层有一个surface,native层也有一个。如果看过Handler机制源码应该知道,MesageQueue和Looper也是一样的,java层有一个,native层还有一个。不过这里就不过多展开了,我们继续往下看。
1.2 self->getIGraphicBufferProducer()
----> /frameworks/av/media/libstagefright/filters/GraphicBufferListener.h
sp<IGraphicBufferProducer> getIGraphicBufferProducer() const {
return mProducer;
}
复制代码
我们这里看到得到了个IGraphicBufferProducer对象,这个对象是什么呢? 我们先在这里打住,先去看看readFromParcel这个方法,结合这个方法我们来看IGraphicBufferProducer这个类的作用、
1.3 readFromParcel()
public void readFromParcel(Parcel source) {
if (source == null) {
throw new IllegalArgumentException("source must not be null");
}
synchronized (mLock) {
mName = source.readString();
mIsSingleBuffered = source.readInt() != 0;
setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
}
复制代码
它做的事情也很简单:
- 读取到Name
- 读取是否是singleBuffer,并设置给mIsSingleBuffered
- 读取native层的一个指针,并赋值给mNativeObject这个变量
1.4 nativeReadFromParcel()
static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject parcelObj) {
....
Parcel* parcel = parcelForJavaObject(env, parcelObj);
sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
sp<Surface> sur;
if (surfaceShim.graphicBufferProducer != nullptr) {
// we have a new IGraphicBufferProducer, create a new Surface for it
sur = new Surface(surfaceShim.graphicBufferProducer, true);
...
}
复制代码
这一段的逻辑就是:
- 根据java层的parcel对象,拿到native层的parcel对象
- 根据指针拿到native层的surface对象
- 从parcel中读出一个Binder对象,这个Binder对象就是一个IGraphicBufferProducer对象
- 之后,根据这个IGraphicBufferProducer又重新构造了一个Surface对象
- 最后,返回这个新创建的surface对象
到这里,不知道有没有看晕,我们来简单总结一下:
- 对于java层的surface来说,它的核心在于Native层的surface对象
- 对于Native层的surface对象来说,它的核心又在于IGraphicBufferProducer
由于这一次不打算把Surface的绘制也讲了,所以关于IGraphicBufferProducer这个Binder对象的分析就留到下次(如果我记得填坑的话~~~)
Activity的Surface怎么传输?
我们知道,应用想进行绘制,是需要向SurfaceFlinger去申请一块内存的,大致流程如下:
这个申请的流程会涉及到surface的传递,那么surface是怎么传递到应用端的呢?
我们现在就撸起袖子研究一下。
2.1 performTraversals()
-----> 类:ViewRootImpl
//1
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
....
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
}
//2
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
//注意看最后一个参数mSurface
int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
mPendingMergedConfiguration, mSurface);
}
复制代码
这里,一个新的对象出现了————WindowSession,它是干嘛的呢?
2.2 IWindowSession
/frameworks/base/core/java/android/view/IWindowSession.aidl
interface IWindowSession {
int add(IWindow window, int seq, in WindowManager.LayoutParams attrs,
in int viewVisibility, out Rect outContentInsets, out Rect outStableInsets,
out InputChannel outInputChannel);
int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out Rect outContentInsets,
out Rect outStableInsets, out Rect outOutsets, out InputChannel outInputChannel);
int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
in int viewVisibility, out Rect outContentInsets, out Rect outStableInsets);
int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out Rect outContentInsets,
out Rect outStableInsets);
void remove(IWindow window);
}
复制代码
WindowSessioin原来是个AIDL文件,它是跟WMS进行通信的对象,相当于打开了一条通信通道。
我们接下来看一下IWindowSession是如何初始化的:
可以看到它的实现类是Session,那接下来就可以继续跟踪了。
2.3 relayout(…)
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets,
Rect outStableInsets, Rect outsets, Rect outBackdropFrame,
DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
Surface outSurface) {
....
int res = mService.relayoutWindow(this, window, seq, attrs,
requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
outStableInsets, outsets, outBackdropFrame, cutout,
mergedConfiguration, outSurface);
...
}
}
复制代码
可以看到,它最终是调用到了WMS的relayoutWindow方法,大家重点关注一下最后传递的参数:outSurface。 这个outSurface此时其实是空的,这里只是传递了一个壳过去给WMS。
接下来,我们就要去看看传递给空壳给WMS,那WMS是怎么处理这个空壳的?怎么给它填充数据的?
2.4 service.relayoutWindow(…)
----> 类:WindowManagerService.java
public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
Surface outSurface) {
...
result = createSurfaceControl(outSurface, result, win, winAnimator);
if (surfaceController != null) {
surfaceController.getSurface(outSurface);
if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " OUT SURFACE " + outSurface + ": copied");
}
...
}
-----》 WindowSurfaceController.java
void getSurface(Surface outSurface) {
outSurface.copyFrom(mSurfaceControl);
}
复制代码
上面这一段代码的逻辑是:
- 创建一个SurfaceControl
- 从SurfaceControl中拷贝数据到之前的空壳的Surface中
那它是如何实现这个复制的过程的呢?
我们继续看copyFrom()函数:
2.5 surface.copyFrom()
---->Surface.java
public void copyFrom(SurfaceControl other) {
...
long surfaceControlPtr = other.mNativeObject;
...
long newNativeObject = nativeGetFromSurfaceControl(surfaceControlPtr);
...
setNativeObjectLocked(newNativeObject);
}
}
复制代码
分析这几行代码实现的功能:
- 从SurfaceControl对象中获取到一个native对象的指针
- 通过这个native层对象创建一个native层的surface对象
- 然后把这个native层surface对象跟java层的surface对象绑定在一起
这里放上native层代码,大家就会更清楚了:
-----> /frameworks/base/core/jni/android_view_Surface.cpp
static jlong nativeGetFromSurfaceControl(JNIEnv* env, jclass clazz,
jlong surfaceControlNativeObj) {
/*
* This is used by the WindowManagerService just after constructing
* a Surface and is necessary for returning the Surface reference to
* the caller. At this point, we should only have a SurfaceControl.
大家看这个注释,很清楚地讲解了这个方法是被WMS调用,用于返回native层Surface的指针给到Java层
*/
sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
sp<Surface> surface(ctrl->getSurface());
if (surface != NULL) {
surface->incStrong(&sRefBaseOwner);
}
return reinterpret_cast<jlong>(surface.get());
}
复制代码
-----> /frameworks/native/libs/gui/SurfaceControl.cpp
sp<Surface> SurfaceControl::getSurface() const
{
Mutex::Autolock _l(mLock);
if (mSurfaceData == 0) {
return generateSurfaceLocked();
}
return mSurfaceData;
}
sp<Surface> SurfaceControl::generateSurfaceLocked() const
{
// This surface is always consumed by SurfaceFlinger, so the
// producerControlledByApp value doesn't matter; using false.
//使用GraphicBufferProducer创建了一个Surface
mSurfaceData = new Surface(mGraphicBufferProducer, false);
return mSurfaceData;
}
复制代码
好了,到这一步,Surface跨进程通信的整个流程就结束了。我们最后总结回顾一下。以上就是Android开发中的Surface分析
总结
- 应用通过ViewRootImpl创建一个空的Surface
- 通过IWindowSession将这个空的Surface传递给WMS
- WMS通过SurfaceControl创建一个native层Surface,并通过指针将Surface跟Java层的Surface进行绑定,从而完成Surface的跨进程传输