Android 4.2 SetContentView 流程分析(三)

这一路分析下来, 开始进入 JNI layer 了.

[android_view_SurfaceSession.cpp]
static jint nativeCreate(JNIEnv* env, jclass clazz) {
    // new 一个 SurfaceComposerClient对象, 其功用后面会在分析.
    sp<SurfaceComposerClient> client = new SurfaceComposerClient;           
    client->incStrong(clazz);
    return reinterpret_cast<jint>(client);
}

到此在做个结论,handleResumeActivity 函数利用WindowManagerImpl依照WindowManager.LayoutParams的属性来为DecoreView增加一个 view的纪录.

, 在addView 过程会有以下的流程:

1. 产生一个新的ViewRootImpl, 在ViewRootImpl的对象初始化中建立一个 DecoreView 和 WindowManagerService 之间的 Session.

2. 将先前设置好的View 加入DecoreView. 在加入过程有两个关键动作如下:

        a.  执行 performTraversals开始布局并且绘画显示画面.

        b.  SurfaceComposerClient 组件:  new一个 SurfaceComposerClient对象, 其功用跟 surfaceControl 有关.

针对以上第二点的流程的两个关键动作,我们继续分析.

a. 执行 performTraversals

[ViewRootImpl.java]
private void performTraversals() {
    // mView是前面经由setContentView的一个View,也就是DecoreView.
    final View host = mView;
 
    //...
    //关键函数
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
    //....
 
    //...
    //开始绘图.
    performDraw();
    //...
}

performTraversals函数做蛮多杂事的, 我们只需要关心两个动作其中两个关键动作, relayoutWindow performDraw.

relayoutWindow 分析

[ViewRootImpl.java]
private int relayoutWindow(WindowManager.LayoutParams params,
                       int viewVisibility, 
                       boolean insetsPending) throws RemoteException {
 
         //....
         //由此可以看到藉由Session去呼叫WindowManagerService的
         // relayoutWindow function.
         int relayoutResult = sWindowSession.relayout(
                mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f),
                viewVisibility, insetsPending ?  
                WindowManagerImpl.RELAYOUT_INSETS_PENDING : 0,
                mWinFrame, mPendingContentInsets,
                mPendingVisibleInsets,
                mPendingConfiguration, mSurface);
          //...
         return relayoutResult;
}
 
[Session.java]
public int relayout(IWindow window, int seq, WindowManager.LayoutParams
attrs,
            int requestedWidth, int requestedHeight, int viewFlags,
            int flags, Rect outFrame, Rect outContentInsets,
            Rect outVisibleInsets, Configuration outConfig, Surface
outSurface) {
        int res = mService.relayoutWindow(this, window, seq, attrs,
                requestedWidth, requestedHeight, viewFlags, flags,
                outFrame, outContentInsets, outVisibleInsets,
                outConfig, outSurface);
        return res;
}
有此段程序代码,可以看到relayoutWindow函数会藉由Session去呼叫WindowManagerService的relayoutWindowfunction. 其中值得关注的是最后一个参数mSurface.这个mSurface是怎么来的.在定义 ViewRootImpl 类别一开始就产生了一个没有带参数的Surface物件

[ViewRootImpl.java]
private final Surface mSurface = new Surface();
 
[Surface.java]
public Surface() {
        checkHeadless();
        mCloseGuard.open("release");
}

接这就继续分析WindowManagerService的relayoutWindow function做了哪些事?

[WindowManagerService.java]
public int relayoutWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int requestedWidth,
            int requestedHeight, int viewVisibility, int flags,
            Rect outFrame, Rect outContentInsets,
            Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
 
            //断开Binder机制,清除呼叫者的UID, PID,重新设定service的//UID, PID,使其可以Service呼叫自己的功能.
            long origId = Binder.clearCallingIdentity();
 
            synchronized(mWindowMap) {
            //经由先前ViewRootImpl在setView时所建立的一个//WindowState Hashtable搭配指定的Client的IBinder去找出相//对应的WindowState.
               WindowState win = windowForClientLocked(session, client,
false);
               if (win == null) {
                return 0;
               }
               WindowStateAnimator winAnimator = win.mWinAnimator;
               // do something
               Surface surface = winAnimator.createSurfaceLocked();
               if (surface != null) {
                    //复制一份surface数据到outSurface.
                    outSurface.copyFrom(surface);
                       
               } else {
                    //归还之前为surface配置的内存.
                    outSurface.release();
               }
            }
           //恢复Binder机制,清除service的UID, PID,恢复呼叫者的UID,
//PID.
           Binder.restoreCallingIdentity(origId);
 
        return (inTouchMode ? WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE : 0)
                | (toBeDisplayed ? WindowManagerImpl.RELAYOUT_RES_FIRST_TIME : 0)
                | (surfaceChanged ? WindowManagerImpl.RELAYOUT_RES_SURFACE_CHANGED : 0)
                | (animating ? WindowManagerImpl.RELAYOUT_RES_ANIMATING : 0);
}
由此段程序代码带出两个关键, Surface surface = winAnimator.createSurfaceLocked();  和outSurface.copyFrom(surface); 就依序来分析这两个动作.

Surface surface = winAnimator.createSurfaceLocked();

[WindowStateAnimator.java]
Surface createSurfaceLocked() {
    if (mSurface == null) {
        // …
        mSurface = new Surface(
                        mSession.mSurfaceSession,
                        attrs.getTitle().toString(),
                        w, h, format, flags);       
        // …
    }
    return mSurface;
}
 
[Surface.java]
/** create a surface with a name @hide */
public Surface(SurfaceSession session,
            String name, int w, int h, int format, int flags)
            throws OutOfResourcesException {
        if (session == null) {
            throw new IllegalArgumentException("session must not be null");
        }
        if (name == null) {
            throw new IllegalArgumentException("name must not be null");
        }
 
        if ((flags & HIDDEN) == 0) {
            // ...
        }
 
        checkHeadless();
        mName = name;
        nativeCreate(session, name, w, h, format, flags);
        mCloseGuard.open("release");
    }
private native void nativeCreate(SurfaceSession session, String name,
            int w, int h, int format, int flags)
            throws OutOfResourcesException;
 
[android_view_Surface.cpp]
static void nativeCreate(JNIEnv* env, jobject surfaceObj, jobject sessionObj,
        jstring nameStr, jint w, jint h, jint format, jint flags) {
    ScopedUtfChars name(env, nameStr);
    sp<SurfaceComposerClient>
             client(android_view_SurfaceSession_getClient(env, sessionObj));
 
    sp<SurfaceControl> surface = client->createSurface(
            String8(name.c_str()), w, h, format, flags);
    if (surface == NULL) {
        jniThrowException(env, OutOfResourcesException, NULL);
        return;
    }
 
    setSurfaceControl(env, surfaceObj, surface);
}

由此可知, winAnimator.createSurfaceLocked所做的事就是在native层产生一个SurfaceControl 对象,在Java层产生一个带有surfaceSession参数的Surface参考对象.

outSurface.copyFrom(surface);

[Surface.java]
public native void copyFrom(Surface o);
 
[android_view_Surface.cpp]
static void Surface_copyFrom(
        JNIEnv* env, jobject clazz, jobject other)
{
    if (clazz == other)
        return;
 
    if (other == NULL) {
        doThrowNPE(env);
        return;
    }
 
    /*
     * 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.
     */
 
    const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz);
    const sp<SurfaceControl>& rhs = getSurfaceControl(env, other);
    //将rhs对象指定给sutface指针对象.
    if (!SurfaceControl::isSameSurface(surface, rhs)) {
        // we reassign the surface only if it's a different one
        // otherwise we would loose our client-side state.
        setSurfaceControl(env, clazz, rhs);
    }
}

由以上的代码段,WindowManagerService的relayoutWindow可以做个以下总结:

1. 藉由mWindowMap里去得到目前Client (DecoreView)的WindowState对象.

2. 藉由此WindowState物件win来new一个带有SurfaceSession参数的Surface物件.用意是可以借着SurfaceComposerClient来新增一个SurfaceControl.

3. 利用Surface的copyfrom 函数把带有SurfaceSession参数的Surface对象复制一份到ViewRootImpl所产生的一个无参数建构出来的Surface对象.

结论又带出一个新的角色SurfaceComposerClient,其功用后面会分析.

 

performDraw分析

[ViewRootImpl.java]

private void performDraw() {
    //检查目前的Screen是否开启且是否要画下一张画面.只要其中一个条
    //件不满足,就不继续往下做处理.
 
    // fullRedrawNeeded用来控制画面的大小.
    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;
 
    mIsDrawing = true;
 
    try {
       draw(fullRedrawNeeded);
    } finally {
       mIsDrawing = false;
   }
}
 
private void draw(boolean fullRedrawNeeded) {
       // mSurface 是ViewRootImpl所产生的一个无参数建构出来的Surface
       //对象,经过relayoutWindow处理之后, 就变成一个带有
       //surfaceSession参数的Surface对象,在native layer具有surface
       //control物件.
       Surface surface = mSurface;
 
       //...
      
       final Rect dirty = mDirty;
 
       // ....
       //设定画面的大小
       if (fullRedrawNeeded) {
            attachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight *
                   appScale + 0.5f));
        }
 
       
 
        if (!dirty.isEmpty() || mIsAnimating) {
            //从android 4.1之后在绘制画面多了一个选择就是由HardWare
            //renderer来处理.
            if (attachInfo.mHardwareRenderer != null &&
              attachInfo.mHardwareRenderer.isEnabled()) {
                 //由Hardware renderer来画.
            } else if (!drawSoftware(surface, attachInfo, yoff,
                               scalingRequired, dirty)) {
                return;
            }
        }
 
        // ...
}
 
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
            boolean scalingRequired, Rect dirty) {
      //一旦有Hardware renderer的request而此时Hardware renderer的功
      //能又未打开,系统将停止Software renderer处理动作.
      if (attachInfo.mHardwareRenderer != null
        && !attachInfo.mHardwareRenderer.isEnabled() &&
        attachInfo.mHardwareRenderer.isRequested()) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
            return false;
        }
  
      //由Software renderer来画.
      Canvas canvas;
      // ...
      canvas = mSurface.lockCanvas(dirty);
     // ...
     //这里的mView就是在一开始 handleResumeActivity函数中的第六个
     //步骤, Window Manager (WindowManagerImpl)要处理addView所带
     //入的ViewGroup (DecoreView)物件.
     mView.draw(canvas);
     // ...
     surface.unlockCanvasAndPost(canvas);
     return true;
}
从以上的程序代码可以看到, Surface lock一块Canvas, 之前配置的View (DecoreView) 在利用这块Canvas来绘制UI组件.避免分析太分散,Surface的 lockCanvas, unlockCanvasAndPost和DecoreView的draw 函数分析放在最后分析.

b.SurfaceComposerClient 组件.

当ViewRootImpl去设置View(DecoreView)时,会先经由Session通知WindowMangerService去addWindow, 在addWindow中会去new SurfaceSession.SurfaceComposerClient就是由new SurfaceSession中产生的.由于SurfaceComposerClient衍生自RefBase类别, 所以都会用sp来取得对象指针.

[android_view_SurfaceSession.cpp]
sp<SurfaceComposerClient> client = new SurfaceComposerClient;

通常利用sp来建构对象时,会先呼叫onFirstRef函数,才会呼叫相对应建构子.因此若想要看一下SurfaceComposerClient的初始实作, 需要从其onFirstRef函数下手,分析如下: 

[SurfaceComposerClient.cpp]
void SurfaceComposerClient::onFirstRef() {
    // ISurfaceComposer 是 surfaceFlinger的 binder界面,所以
    //getComposerService会回传SurfaceFlinger的Binder Proxy:
    //BpSurfaceComposer.
    sp<ISurfaceComposer> sm(ComposerService::getComposerService());
    if (sm != 0) {
        sp<ISurfaceComposerClient> conn = sm->createConnection();
        if (conn != 0) {
            mClient = conn;
            mStatus = NO_ERROR;
        }
    }
}
既然getComposerService回传的是SurfaceFlinger的Binder Proxy,表示由得到的对象,其方法可以在SurfaceFlnger类别去找.所以sm->createConnection(), 其createConnection函数如下所示:

[SurfaceFlinger.cpp]
sp<ISurfaceComposerClient> SurfaceFlinger::createConnection()
{
    sp<ISurfaceComposerClient> bclient;
    sp<Client> client(new Client(this));
    status_t err = client->initCheck();
    if (err == NO_ERROR) {
        bclient = client;
    }
    return bclient;
}
createConnection 函数回传一个ISurfaceComposerClient指针对象, 其实作可以发现这个指针对象是一个Client的指针对象.接下来就来分析Client建构子和其方法initCheck.

[Client.cpp]
Client::Client(const sp<SurfaceFlinger>& flinger)
    : mFlinger(flinger), mNameGenerator(1)
{
}
status_t Client::initCheck() const {
    //没做任何事,推测未来会有实作.
    return NO_ERROR;
} 

c. lockCanvas, unlockCanvasAndPost

现在就来分析,Surface的 lockCanvas, unlockCanvasAndPost.

[Surface.java]
/** draw into a surface */
public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException, IllegalArgumentException {
   return nativeLockCanvas (dirty);
}
private native Canvas nativeLockCanvas (Rect dirty);
 
[android_view_Surface.cpp]
static jobject nativeLockCanvas (JNIEnv* env, jobject clazz, jobject dirtyRect)
{
    sp<Surface> surface(getSurface(env, surfaceObj));
    if (!Surface::isValid(surface)) {
        doThrowIAE(env);
        return NULL;
    }
    //1. 取得Dirty区域的大小.
    Region dirtyRegion;
    if (dirtyRectObj) {
        Rect dirty;
        dirty.left = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
        dirty.top = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
        dirty.right = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
        dirty.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
        if (!dirty.isEmpty()) {
            dirtyRegion.set(dirty);
        }
    } else {
        dirtyRegion.set(Rect(0x3FFF, 0x3FFF));
    }
 
 
     //2. 利用获得的Dirty区域利用Surface对象的lock函数来设置
     //SurfaceInfo.
     Surface::SurfaceInfo info; 
     status_t err = surface->lock(&info, &dirtyRegion);
     //3. 从Java layer取得SkCanvas object.
     // mCanvas在Surface建构之前就已经存在了,请参考程序代码 Surface.java
     jobject canvas = env->GetObjectField(clazz, so.canvas);
     //...
     SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas,
                           no.native_canvas);
    //4. 利用得到的SkCanvas来开始设定绘制区域.从Android 4.1开始绘制的
    //类型有分SkBitmap内存管理跟 SkRegion内存管理.
    //利用SurfaceInfo配置一块 2d skia的SkBitmap内存管理
    SkBitmap bitmap;  
    ssize_t bpr = info.s * bytesPerPixel(info.format);
    bitmap.setConfig(convertPixelFormat(info.format), info.w, info.h, bpr);
    if (info.format == PIXEL_FORMAT_RGBX_8888) {
        bitmap.setIsOpaque(true);
    }
    if (info.w > 0 && info.h > 0) {
        //设定需要管理surface的buffer
        bitmap.setPixels(info.bits);
    } else {
        // be safe with an empty bitmap.
        bitmap.setPixels(NULL);
    }
    //SkCanvas设定此bitmap内存管理, 以便可以用这个内存管理在
    //surface的buffer上作画.
    nativeCanvas->setBitmapDevice(bitmap); 
   
    //配置一块 SkRegion内存管理
    if (dirtyRegion.isRect()) { // very common case
        const Rect b(dirtyRegion.getBounds());
        clipReg.setRect(b.left, b.top, b.right, b.bottom);
    } else {
        size_t count;
        Rect const* r = dirtyRegion.getArray(&count);
        while (count) {
            clipReg.op(r->left, r->top, r->right, r->bottom,
                     SkRegion::kUnion_Op);
            r++, count--;
        }
    }
 
    nativeCanvas->clipRegion(clipReg);
    //5. 储存SkCanvas在内存管理上的配置.
    int saveCount = nativeCanvas->save();
   
    //6. 利用JNI的函数将一些需要的值来设定Java对象的属性.
   
     return canvas;
}
 
[Surface.cpp]
status_t Surface::lock(SurfaceInfo* other, Region* inOutDirtyRegion) {
     //...
     //因为Surface类别衍生自 SurfaceTextureClient类别, 要呼叫父类别的
     //函数需要父类别名子加上范围运算符.
     status_t err = SurfaceTextureClient::lock(&outBuffer, inOutDirtyBounds);
     //...
     return err;
}
 
[SurfaceTextureClient.cpp]
status_t SurfaceTextureClient::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
   //0. already locked, retrun
   if (!mConnectedToCpu) {
        //
        int err = SurfaceTextureClient::connect(NATIVE_WINDOW_API_CPU);
        if (err) {
            return err;
        }
        // we're intending to do software rendering from this point
        setUsage(GRALLOC_USAGE_SW_READ_OFTEN |
                    GRALLOC_USAGE_SW_WRITE_OFTEN);
    }
   
    ANativeWindowBuffer* out;
    //检查目前在queue中的buffer使用状态.并取出未使用的buffer index.
    int fenceFd = -1;
    status_t err = dequeueBuffer(&out, &fenceFd);
    // 1. 宣告一个GraphicBuffer型态指针变量指向(ANativeWindowBuffer)out
    //内存.
    sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
   
    //2. 依据backBuffer来设定需要更新的区域边界.
    const Rect bounds(backBuffer->width, backBuffer->height);
 
    Region newDirtyRegion;
      if (inOutDirtyBounds) {
            newDirtyRegion.set(static_cast<Rect const&>(*inOutDirtyBounds));
            newDirtyRegion.andSelf(bounds);
      } else {
            newDirtyRegion.set(bounds);
      }
 
      //3. 检查是否有frontBuffer可以copy,以便显示到画面.
      // mPostedBuffer跟之后要分析的 unlockCanvasAndPost处理有关.
      const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
      const bool canCopyBack = (frontBuffer != 0 &&
                backBuffer->width  == frontBuffer->width &&
                backBuffer->height == frontBuffer->height &&
                backBuffer->format == frontBuffer->format);
 
      if (canCopyBack) {
            // copy the area that is invalid and not repainted this round
            const Region copyback(mDirtyRegion.subtract(newDirtyRegion));
            if (!copyback.isEmpty())
               //4. 避免整块memory做更新,因此只要藉由这方法来更新
               //该更新的部分.
                copyBlt(backBuffer, frontBuffer, copyback);
        } else {
            // if we can't copy-back anything, modify the user's dirty
            // region to make sure they redraw the whole buffer
            newDirtyRegion.set(bounds);
            mDirtyRegion.clear();
            Mutex::Autolock lock(mMutex);
            for (size_t i=0 ; i<NUM_BUFFER_SLOTS ; i++) {
                mSlots[i].dirtyRegion.clear();
            }
        }
     // ...
     void* vaddr;
     //5. 依照newDirtyRegion的边界在backBuffer上lock一块内存并传回
     //起始地址vaddr给jni中所宣告出来的SkBitmap使用.
     status_t res = backBuffer->lock(
                    GRALLOC_USAGE_SW_READ_OFTEN |
                    GRALLOC_USAGE_SW_WRITE_OFTEN,
                    newDirtyRegion.bounds(), &vaddr);
    //...
    mLockedBuffer = backBuffer;
 
    if (res != 0) {
            err = INVALID_OPERATION;
     } else {
            mLockedBuffer = backBuffer;
            outBuffer->width  = backBuffer->width;
            outBuffer->height = backBuffer->height;
            outBuffer->stride = backBuffer->stride;
            outBuffer->format = backBuffer->format;
            outBuffer->bits   = vaddr;
     }
 
     return err;
}
在分析lockCanvas中有个 dequeueBuffer的动作没有在这里分析,有兴趣再另起一个topic来分析.接下来继续分析unlockCanvasAndPost.

[Surface.java]
public void unlockCanvasAndPost(Canvas canvas) {
        nativeUnlockCanvasAndPost(canvas);
}
private native void nativeUnlockCanvasAndPost(Canvas canvas);
 
[android_view_Surface.cpp]
static void nativeUnlockCanvasAndPost(JNIEnv* env, jobject surfaceObj, jobject canvasObj) {  
 
   jobject ownCanvasObj = env->GetObjectField(surfaceObj,
                                        gSurfaceClassInfo.mCanvas);
    if (!env->IsSameObject(ownCanvasObj, canvasObj)) {
        doThrowIAE(env);
        return;
    }  
 
  
   const sp<Surface>& surface(getSurface(env, clazz));
   if (!Surface::isValid(surface)) {
        return;
   }
 
    SkCanvas* nativeCanvas = reinterpret_cast<SkCanvas*>(
            env->GetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas));
    int saveCount = env->GetIntField(surfaceObj,
                               gSurfaceClassInfo.mCanvasSaveCount);
    nativeCanvas->restoreToCount(saveCount);
    nativeCanvas->setBitmapDevice(SkBitmap());
    env->SetIntField(surfaceObj, gSurfaceClassInfo.mCanvasSaveCount, 0);
 
    status_t err = surface->unlockAndPost();
    if (err < 0) {
        doThrowIAE(env);
    }
}
 
[Surface.cpp]
status_t Surface::unlockAndPost() {
    //因为Surface类别衍生自 SurfaceTextureClient类别, 要呼叫父类别的
    //函数需要父类别名子加上范围运算符.
    return SurfaceTextureClient::unlockAndPost();
}
 
[SurfaceTextureClient.cpp]
status_t SurfaceTextureClient::unlockAndPost()
{
  //...
  //0. 将使用完的Buffer做unlock的动作.
  status_t err = mLockedBuffer->unlock();
  //...
  //1. 将unlock的buffer index送进queue中.
  err = queueBuffer(mLockedBuffer.get());
  //...
  //2. 将mLockedBuffer指针对象传给mPostedBuffer指针对象做为下一次
  // lockCanvas的依据.
  mPostedBuffer = mLockedBuffer;
  mLockedBuffer = 0;
 
}
关于View的Draw流程就不在这一次的分析中, 因为Surface中的Canvas和Bitmap对View的Draw流程来说只是个工具. 总结如下:

1. 在App开始要布局layout view都会由setContentView来设置整体画面.

   这时的角色有PhoneWindow, WindowManagerImpl, DecoreView

   其相互动作如下:

   a. getWindow 得到一个 mWindow, 这个 mWindow 就是一个  

     PhoneWindow 物件.

   b. 利用 PhoneWindow 对象设为 WindowManagerImpl的角色.

   c. PhoneWindow 物件设置一个 DecoreView 的子 view.

2. 当App从Create状态转到Resume状态时, 这时就会开始绘制画面.

   角色: DecoreView, WindowManagerImpl, ViewRootImpl,WindowManagerService,     

   SurfaceSession, WindowState, Surface, Canvas

   i. ViewRootImpl利用Binder机制将WindowManagerService 和 DecoreView建立一个session.

   ii. ViewRootImpl经由session请求WindowManagerService来增加一个WindowState,并作记录.

   iii. WindowManagerService在作windowstate时, 会建构一个SurfaceComposerClient物件.

   iv. 利用SurfaceComposerClient对象产生SurfaceControl对象.

    v. 利用Canvas设置两个Buffer, 分别为Front和Back.

    vi. 利用Surface对象的lockCanvas, unlockCanvasAndPost函数来处理绘制画面的流程.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值