Surface Core Concept
A Surface represents a raw buffer managed by the screen compositor (SurfaceFlinger). It acts as the canvas where producers (like MediaPlayer, OpenGL, or Camera) draw content. Understanding the relationship between SurfaceFlinger and Surface is crucial for Android graphics architecture.
ViewRoot Establishment and IPC
When the UI framework prepares to display a window, it instantiates a ViewRoot. The constructor initializes a Binder session with the Window Manager Service (WMS).
public ViewRoot(Context uiContext) {
acquireWindowSession(uiContext.getMainLooper());
mUiThread = Thread.currentThread();
mBinderWindow = new BinderWindow(this);
mAttachmentInfo = new View.AttachInfo(sWindowSession, mBinderWindow, this, this);
}The acquireWindowSession method establishes the IPC connection.
public static IWindowSession acquireWindowSession(Looper looper) {
synchronized (mStaticLock) {
if (!mInitialized) {
InputMethodManager imManager = InputMethodManager.getInstance(looper);
sWindowSession = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"))
.openSession(imManager.getClient(), imManager.getInputContext());
mInitialized = true;
}
return sWindowSession;
}
}Binding the View and Scheduling Traversal
The bindRootView method stores the DecorView and initiates the drawing sequence.
public void bindRootView(View view, LayoutParams attrs, View parentPanel) {
synchronized (this) {
if (mRootView == null) {
mRootView = view;
mWindowAttributes.copyFrom(attrs);
requestLayout();
try {
int result = sWindowSession.add(mBinderWindow, mWindowAttributes,
getHostVisibility(), mAttachmentInfo.mContentInsets);
} catch (RemoteException e) {
// Handle IPC failure
}
view.assignParent(this);
}
}
}Calling requestLayout() schedules a traversal via scheduleTraversals(), which sends a DO_TRAVERSAL message. This eventually triggers performTraversals().
Window Attachment and SurfaceSession
On the WMS side, adding a window creates a WindowState and calls its attach() method.
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility, Rect outContentInsets) {
WindowState winState = new WindowState(session, client, token, attachedWindow, attrs, viewVisibility);
winState.attach();
// ...
}The attach() method initializes a SurfaceSession, linking the application to SurfaceFlinger.
void attach() {
mSession.windowAddedLocked();
}
void windowAddedLocked() {
if (mSurfaceSession == null) {
mSurfaceSession = new SurfaceSession();
mActiveSessions.add(this);
}
mWindowCount++;
}Relayout and Cross-Process Surface Mapping
During performTraversals(), relayoutWindow() is called. This IPC call passes the client's empty Surface object to WMS.
private int relayoutWindow(LayoutParams params, int viewVisibility, boolean insetsPending) {
int relayoutResult = sWindowSession.relayout(
mBinderWindow, params,
(int) (mRootView.mMeasuredWidth * appScale + 0.5f),
(int) (mRootView.mMeasuredHeight * appScale + 0.5f),
viewVisibility, insetsPending, mWinFrame,
mPendingContentInsets, mPendingVisibleInsets,
mPendingConfiguration, mClientSurface);
return relayoutResult;
}In WMS, relayoutWindow creates the real Surface and copies it to the client's Surface.
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, boolean insetsPending, Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
WindowState winState = windowForClientLocked(session, client, false);
// ...
Surface nativeSurface = winState.createSurfaceLocked();
if (nativeSurface != null) {
outSurface.copyFrom(nativeSurface);
} else {
outSurface.release();
}
// ...
}createSurfaceLocked() instantiates the Surface using the previously created SurfaceSession.
Surface createSurfaceLocked() {
if (mNativeSurface == null) {
mDrawPending = true;
int width = mFrame.width();
int height = mFrame.height();
if (width <= 0) width = 1;
if (height <= 0) height = 1;
try {
mNativeSurface = new Surface(
mSession.mSurfaceSession, mSession.mPid,
mAttrs.getTitle().toString(),
0, width, height, mAttrs.format, mSurfaceFlags);
} catch (Surface.OutOfResourcesException e) {
return null;
}
Surface.openTransaction();
try {
mNativeSurface.setPosition(mFrame.left + mXOffset, mFrame.top + mYOffset);
mNativeSurface.setLayer(mAnimLayer);
mNativeSurface.hide();
} finally {
Surface.closeTransaction();
}
}
return mNativeSurface;
}Canvas Locking and Rendering
Back in the application process, performTraversals() proceeds to draw. The draw() method locks the Canvas from the mClientSurface, draws the View hierarchy, and unlocks it.
private void draw(boolean fullRedrawNeeded) {
Surface targetSurface = mClientSurface;
Rect dirtyRect = mDirtyRect;
Canvas canvas;
try {
canvas = targetSurface.lockCanvas(dirtyRect);
if (!dirtyRect.isEmpty() || mIsAnimating) {
if (!canvas.isOpaque()) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
mRootView.draw(canvas);
}
} finally {
targetSurface.unlockCanvasAndPost(canvas);
}
}Native Layer Initialization
The client's empty Surface is instantiated via a no-argument constructor, which only creates a CompatibleCanvas.
public Surface() {
mCanvas = new CompatibleCanvas();
}SurfaceSession connects to SurfaceFlinger at the JNI level.
// frameworks/base/core/jni/android_view_Surface.cpp
static void SurfaceSession_init(JNIEnv* env, jobject clazz) {
sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient;
composerClient->incStrong(clazz);
env->SetIntField(clazz, sso.client, (int)composerClient.get());
}When WMS creates a Surface using the parameterized constructor, JNI uses the SurfaceComposerClient to generate a SurfaceControl.
static void Surface_init(JNIEnv* env, jobject clazz, jobject session, jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jint flags) {
SurfaceComposerClient* client = (SurfaceComposerClient*)env->GetIntField(session, sso.client);
sp<SurfaceControl> surfaceControl;
if (jname == NULL) {
surfaceControl = client->createSurface(pid, dpy, w, h, format, flags);
} else {
const jchar* str = env->GetStringCritical(jname, 0);
const String8 name(str, env->GetStringLength(jname));
env->ReleaseStringCritical(jname, str);
surfaceControl = client->createSurface(pid, name, dpy, w, h, format, flags);
}
setSurfaceControl(env, clazz, surfaceControl);
}The copyFrom method transfers the SurfaceControl from WMS's Surface object to the client's Surface object.
static void Surface_copyFrom(JNIEnv* env, jobject clazz, jobject other) {
const sp<SurfaceControl>& currentControl = getSurfaceControl(env, clazz);
const sp<SurfaceControl>& sourceControl = getSurfaceControl(env, other);
if (!SurfaceControl::isSameSurface(currentControl, sourceControl)) {
setSurfaceControl(env, clazz, sourceControl);
}
}During Binder IPC, writeToParcel flattens the SurfaceControl, and readFromParcel reconstructs a native Surface on the client side, mapping it back to the Java mClientSurface field.
static void Surface_writeToParcel(JNIEnv* env, jobject clazz, jobject argParcel, jint flags) {
Parcel* parcel = (Parcel*)env->GetIntField(argParcel, no.native_parcel);
const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
SurfaceControl::writeSurfaceToParcel(control, parcel);
}
static void Surface_readFromParcel(JNIEnv* env, jobject clazz, jobject argParcel) {
Parcel* parcel = (Parcel*)env->GetIntField(argParcel, no.native_parcel);
const sp<Surface>& currentSurface(getSurface(env, clazz));
sp<Surface> reconstructedSurface = new Surface(*parcel);
if (!Surface::isSameSurface(currentSurface, reconstructedSurface)) {
setSurface(env, clazz, reconstructedSurface);
}
}