Android畫面顯示流程分析(4)

努比亞技術(shù)團(tuán)隊(duì)原創(chuàng)內(nèi)容,轉(zhuǎn)載請(qǐng)務(wù)必注明出處桑寨。

Android畫面顯示流程分析(1)
Android畫面顯示流程分析(2)
Android畫面顯示流程分析(3)
Android畫面顯示流程分析(4)
Android畫面顯示流程分析(5)

7. 畫面更新流程

在我們前面幾個(gè)章節(jié)的討論中,我們從最底層的顯示硬件吱肌,SOC和DDIC的接口牲蜀, linux和Userspace的圖形接口以及APP與SurfaceFlinger,HWC service三者的關(guān)系,了解了幀數(shù)據(jù)流動(dòng)所經(jīng)過的關(guān)鍵節(jié)點(diǎn)除秀,并重點(diǎn)討論了幀buffer是如何管理的糯累,以及在流動(dòng)過程中是如何做到同步的。接下來我們將從應(yīng)用側(cè)角度來從上到下看一下應(yīng)用所繪制的畫面是如何使用到我們上面所設(shè)計(jì)的流程中的册踩。

7.1. 畫布的申請(qǐng)

從前文5.4的討論可知泳姐,應(yīng)用側(cè)對(duì)圖層的操作是以Surface為接口的,其定義如下所示暂吉,它包含了一些更新畫面相關(guān)的核心api, 比如dequeueBuffer/queueBuffer/connect/disconnect等等胖秒。

Surface.h (frameworks\native\libs\gui\include\gui)

class Surface
    : public ANativeObjectBase<ANativeWindow, Surface, RefBase>
{
    ......
protected:    
    virtual int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd);
    virtual int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd);
    virtual int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd);
    virtual int perform(int operation, va_list args);
    ......
    virtual int connect(int api);
    ......
public:
    virtual int disconnect(int api,
            IGraphicBufferProducer::DisconnectMode mode =
                    IGraphicBufferProducer::DisconnectMode::Api);
}

那么應(yīng)用要想畫出它的畫面,第一個(gè)要解決的問題就是應(yīng)用側(cè)的Surface對(duì)象是如何創(chuàng)建慕的? 它又是如何與SurfaceFlinger建立聯(lián)系的阎肝?下面我們將從代碼邏輯中找尋到它的建立過程。

在Android系統(tǒng)中每個(gè)Activity都有一個(gè)獨(dú)立的畫布(在應(yīng)用側(cè)稱為Surface,在SurfaceFlinger側(cè)稱為Layer)肮街, 無論這個(gè)Activity安排了多么復(fù)雜的view結(jié)構(gòu)风题,它們最終都是被畫在了所屬Activity的這塊畫布上,當(dāng)然也有一個(gè)例外嫉父,SurfaceView是有自已獨(dú)立的畫布的沛硅,但此處我們先只討論Activity畫布的建立過程。

首先每個(gè)應(yīng)用都會(huì)創(chuàng)建有自已的Activity, 進(jìn)而Android會(huì)為Activity創(chuàng)建一個(gè)ViewRootImpl绕辖, 并調(diào)用到它的performTraversals這個(gè)函數(shù)(篇幅所限這部分流程請(qǐng)讀者自行閱讀源碼)摇肌。

該函數(shù)里會(huì)調(diào)用到relayoutWindow函數(shù):

frameworks/base/core/java/android/view/ViewRootImpl.java

private void performTraversals() {
    ......
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
    ......
}

relayoutWindow函數(shù)里會(huì)調(diào)用到WindowSession的relayout函數(shù),這個(gè)函數(shù)是一個(gè)跨進(jìn)程調(diào)用仪际,mWindowSession可以看作是WMS在應(yīng)用側(cè)的代表:

frameworks/base/core/java/android/view/ViewRootImpl.java

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
     ......
     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,
                mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame,
                mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                mTempControls, mSurfaceSize, mBlastSurfaceControl);
     ......
}

隨著代碼的執(zhí)行讓我們把視角切換到system_server進(jìn)程(WMS的relayoutWindow函數(shù)),這里會(huì)調(diào)用createSurfaceControl去創(chuàng)建一個(gè)SurfaceControl:

frameworks/base/services/core/java/com/android/server/wm/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 outContentInsets,
            Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame,
            DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Point outSurfaceSize,
            SurfaceControl outBLASTSurfaceControl) {
      ......
      try {
          result = createSurfaceControl(outSurfaceControl, outBLASTSurfaceControl,
          result, win, winAnimator);
      } catch (Exception e) {
      ......
}

SurfaceControl的創(chuàng)建過程朦蕴,注意這里創(chuàng)建工作是調(diào)用winAnimator來完成的,注意下面那句surfaceController.getSurfaceControl會(huì)把創(chuàng)建出來的SurfaceControl通過形參outSurfaceControl傳出去:

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

private int createSurfaceControl(SurfaceControl outSurfaceControl,
            SurfaceControl outBLASTSurfaceControl, int result,
            WindowState win, WindowStateAnimator winAnimator) {
    ......
    WindowSurfaceController surfaceController;
    try {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
        surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid);
    } finally {
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
    if (surfaceController != null) {
        surfaceController.getSurfaceControl(outSurfaceControl);
        ......
    }
    ......
}

我們先看下創(chuàng)建過程弟头,創(chuàng)建了一個(gè)WindowSurfaceController,進(jìn)而再創(chuàng)建SurfaceControll:

frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java

WindowSurfaceController createSurfaceLocked(int windowType, int ownerUid) {
    ......
    /*WindowSurfaceController mSurfaceController;*/
    mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), width,
                    height, format, flags, this, windowType, ownerUid);
    ......
}

WindowSurfaceController.java (frameworks\base\services\core\java\com\android\server\wm)

 WindowSurfaceController(String name, int w, int h, int format,
            int flags, WindowStateAnimator animator, int windowType, int ownerUid) {
     ......
     final SurfaceControl.Builder b = win.makeSurface()
                .setParent(win.getSurfaceControl())
                .setName(name)
                .setBufferSize(w, h)
                .setFormat(format)
                .setFlags(flags)
                .setMetadata(METADATA_WINDOW_TYPE, windowType)
                .setMetadata(METADATA_OWNER_UID, ownerUid)
                .setCallsite("WindowSurfaceController");
     ......
}

SurfaceControl.java (frameworks\base\core\java\android\view)

public static final @android.annotation.NonNull Creator<SurfaceControl> CREATOR
            = new Creator<SurfaceControl>() {
        public SurfaceControl createFromParcel(Parcel in) {
            return new SurfaceControl(in);
        }

        public SurfaceControl[] newArray(int size) {
            return new SurfaceControl[size];
        }
    };
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
            SurfaceControl parent, SparseIntArray metadata, WeakReference<View> localOwnerView,
            String callsite){
    ......
    mNativeObject = nativeCreate(session, name, w, h, format, flags,
                    parent != null ? parent.mNativeObject : 0, metaParcel);
    ......
}
            

到這里我們看到會(huì)通過JNI去創(chuàng)建C層的對(duì)象:

android_view_SurfaceControl.cpp (frameworks\base\core\jni)

static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
        jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
        jobject metadataParcel) {
    ScopedUtfChars name(env, nameStr);//Surface名字涉茧, 在SurfaceFlinger側(cè)就是Layer的名字
    ......
    sp<SurfaceComposerClient> client;
    ......
    status_t err = client->createSurfaceChecked(
            String8(name.c_str()), w, h, format, &surface, flags, parent, std::move(metadata));
    ......
}

C層的Surface在創(chuàng)建時(shí)去調(diào)用SurfaceComposerClient的createSurface去創(chuàng)建, 這個(gè)SurfaceComposerClient可以看作是SurfaceFlinger在Client端的代表

android_view_SurfaceControl.cpp (frameworks\base\core\jni)

status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h,
                                                     PixelFormat format,
                                                     sp<SurfaceControl>* outSurface, uint32_t flags,
                                                     SurfaceControl* parent, LayerMetadata metadata,
                                                     uint32_t* outTransformHint) {
      ......
      err = mClient->createSurface(name, w, h, format, flags, parentHandle, std::move(metadata),
                                     &handle, &gbp, &transformHint);
      ......
}

SurfaceComposerClient.cpp (frameworks\native\libs\gui)

sp<SurfaceControl> SurfaceComposerClient::createSurface(const String8& name, uint32_t w, uint32_t h,
                                                        PixelFormat format, uint32_t flags,
                                                        SurfaceControl* parent,
                                                        LayerMetadata metadata,
                                                        uint32_t* outTransformHint) {
    sp<SurfaceControl> s;
    createSurfaceChecked(name, w, h, format, &s, flags, parent, std::move(metadata),
                         outTransformHint);
    return s;
}
status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h,
                                                     PixelFormat format,
                                                     sp<SurfaceControl>* outSurface, uint32_t flags,
                                                     SurfaceControl* parent, LayerMetadata metadata,
                                                     uint32_t* outTransformHint) {
      .......
      err = mClient->createSurface(name, w, h, format, flags, parentHandle, std::move(metadata),
                                     &handle, &gbp, &transformHint);
      .......
}

跨進(jìn)程呼叫SurfaceFlinger:

ISurfaceComposerClient.cpp (frameworks\native\libs\gui)

status_t createSurface(const String8& name, uint32_t width, uint32_t height, PixelFormat format,
                           uint32_t flags, const sp<IBinder>& parent, LayerMetadata metadata,
                           sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp,
                           uint32_t* outTransformHint) override {
     return callRemote<decltype(&ISurfaceComposerClient::createSurface)>(Tag::CREATE_SURFACE,
                               name, width, height,
                               format, flags, parent,
                               std::move(metadata),
                               handle, gbp,
                               outTransformHint);
}

然后流程就來到了SurfaceFlinger進(jìn)程悦污,由于SurfaceFlinger支持很多不同類型的Layer, 這里我們只以BufferQueueLayer為例赘方, 當(dāng)SurfaceFlinger收到這個(gè)遠(yuǎn)程調(diào)用后會(huì)new 一個(gè)BufferQueueLayer出來。

SurfaceFlinger.cpp (frameworks\native\services\surfaceflinger)

status_t SurfaceFlinger::createLayer(const String8& name, const sp<Client>& client, uint32_t w,
                                     uint32_t h, PixelFormat format, uint32_t flags,
                                     LayerMetadata metadata, sp<IBinder>* handle,
                                     sp<IGraphicBufferProducer>* gbp,
                                     const sp<IBinder>& parentHandle, const sp<Layer>& parentLayer,
                                     uint32_t* outTransformHint) {
      ......
      case ISurfaceComposerClient::eFXSurfaceBufferQueue:
            result = createBufferQueueLayer(client, std::move(uniqueName), w, h, flags,
                                            std::move(metadata), format, handle, gbp, &layer);
      ......
}

至此完成了從一個(gè)應(yīng)用的Activity創(chuàng)建到SurfaceFlinger內(nèi)部為它創(chuàng)建了一個(gè)Layer來對(duì)應(yīng)猴娩。那么應(yīng)用側(cè)又是如何拿到這個(gè)Layer的操作接口呢?

續(xù)續(xù)看代碼:

注意到WMS的createSurfaceControl方法中是通過getSurfaceControl將SurfaceControll傳出來的:

frameworks/base/services/core/java/com/android/server/wm/WindowSurfaceController.java

void getSurfaceControl(SurfaceControl outSurfaceControl) {
    outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
}

這個(gè)對(duì)象會(huì)通過WindowSession跨進(jìn)程傳到應(yīng)用進(jìn)程里磕洪,進(jìn)而創(chuàng)建出Surface:

frameworks/base/core/java/android/view/ViewRootImpl.java

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
     ......
     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,
                mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame,
                mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, //這里的mSurfaceControl是從WMS傳來的
                mTempControls, mSurfaceSize, mBlastSurfaceControl);
     ......
     if (mSurfaceControl.isValid()) {
            if (!useBLAST()) {
                mSurface.copyFrom(mSurfaceControl);//通過Surface的copyFrom方法從SurfaceControl來創(chuàng)建這個(gè)Surface
            }
     ......
}

那么拿到Surface對(duì)象后怎么就能拿到幀緩沖區(qū)的操作接口呢?我們來看Surface的實(shí)現(xiàn):

Surface.java (frameworks\base\core\java\android\view)

public class Surface implements Parcelable {
    ......
    private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
            throws OutOfResourcesException;
    private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
    ......    
}

這里這個(gè)java層的Surface實(shí)現(xiàn)有兩個(gè)方法,一個(gè)是nativeLockCanvas一個(gè)是nativeUnlockCanvasAndPost额港, 它其實(shí)對(duì)應(yīng)了我們?cè)谡鹿?jié)5.4中所述的dequeueBuffer和queueBuffer, 我們繼續(xù)從代碼上看下它的實(shí)現(xiàn)過程:

android_view_Surface.cpp (frameworks\base\core\jni)

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    ......
     status_t err = surface->lock(&buffer, dirtyRectPtr);
    ......
}

Surface.cpp (frameworks\native\libs\gui)

status_t Surface::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds){
     ......
     status_t err = dequeueBuffer(&out, &fenceFd);
     ......
}
int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    ATRACE_CALL();
    ......
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight,
                                                            reqFormat, reqUsage, &mBufferAge,
                                                            enableFrameTimestamps ? &frameTimestamps
                                                                                  : nullptr);
    ......
}

到這里我們看到j(luò)ava層Surface的lock方法最終它有去調(diào)用到GraphicBufferProducer的dequeueBuffer函數(shù),在第5章中我們有詳細(xì)了解過dequeueBuffer在獲取一個(gè)Slot后歧焦,如果Slot沒有分配GraphicBuffer會(huì)在這時(shí)給它分配GraphicBuffer, 然后會(huì)返回一個(gè)帶有BUFFER_NEEDS_REALLOCATION標(biāo)記的flag, 應(yīng)用側(cè)看到這個(gè)flag后會(huì)通過requestBuffer和importBuffer接口把GraphicBuffer映射到自已的進(jìn)程空間移斩。

到此應(yīng)用拿到了它繪制界面所需的“畫布”。

由于上面的代碼過程看起來比較繁瑣绢馍,我們用一張圖來概述一下這個(gè)流程:

image-20210919115648328.png

首先在這個(gè)過程中涉及到三個(gè)進(jìn)程向瓷,APP,system_server, surfaceflinger, 先從應(yīng)用調(diào)用performTraversals中調(diào)用relayoutWindow這個(gè)函數(shù)開始舰涌,它跨進(jìn)程調(diào)用到了system_server進(jìn)程中的WMS模塊猖任,這個(gè)模塊的relayoutWindow又經(jīng)一系列過程創(chuàng)建一個(gè)SurfaceContorl, 在SurfaceControl創(chuàng)建過程中會(huì)跨進(jìn)程調(diào)用SurfaceFlinger讓它創(chuàng)建一個(gè)Layer出來。 之后SurfaceControll對(duì)象會(huì)跨進(jìn)程通過參數(shù)回傳給應(yīng)用瓷耙,應(yīng)用根據(jù)SurfaceControl創(chuàng)建出應(yīng)用側(cè)的Surface對(duì)象朱躺,而Surface對(duì)象通過一些api封裝向上層提供拿畫布(dequeueBuffer)和提交畫布(queueBuffer)的操作接口。這樣應(yīng)用完成了對(duì)畫布的申請(qǐng)操作搁痛。

7.2. 幀數(shù)據(jù)的繪制過程

上一節(jié)我們知道了應(yīng)用是如何拿到“畫布”的长搀,接下來我們來看下應(yīng)用是如何在繪畫完一幀后來提交數(shù)據(jù)的:

上節(jié)中應(yīng)用的主線程在performTraversals函數(shù)中獲取到了操作幀緩沖區(qū)的Surface對(duì)象,這個(gè)Surface對(duì)象會(huì)通過RenderProxy傳遞給RenderThread, 一些關(guān)健代碼如下:

performTraversals里初始化RenderThread時(shí)會(huì)把Surface對(duì)象傳過去:

private void performTraversals() {
    ......
    if (mAttachInfo.mThreadedRenderer != null) {
    try {
        hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface);
        ........
    } 
    ......
}

在ThreadedRenderer的初始化中調(diào)用了setSurface落追,這個(gè)setSurface函數(shù)會(huì)通過JNI調(diào)到native層:

ThreadedRenderer.java (frameworks\base\core\java\android\view)

boolean initialize(Surface surface) throws OutOfResourcesException {
    ......
    setSurface(surface);
    ......
}

android_graphics_HardwareRenderer.cpp (frameworks\base\libs\hwui\jni)

static void android_view_ThreadedRenderer_setSurface(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jobject jsurface, jboolean discardBuffer) {
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    ......
    proxy->setSurface(window, enableTimeout);
    ......
}

最終可以看到setSurface是通過RenderProxy這個(gè)對(duì)象向RenderThread的消息隊(duì)列中post了一個(gè)消息盈滴,在這個(gè)消息的處理中會(huì)調(diào)用Context的setSurface, 這里的mContext是CanvasContext.

RenderProxy.cpp (frameworks\base\libs\hwui\renderthread)

void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
    ANativeWindow_acquire(window);
    mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
        mContext->setSurface(win, enableTimeout);
        ANativeWindow_release(win);
    });
}

CanvasContext的setSurface會(huì)進(jìn)一步調(diào)用到Surface對(duì)象的connect方法(5.4節(jié))和SurfaceFlinger側(cè)協(xié)商同步一些參數(shù)。

上述過程如下圖所示

image-20210921100926294.png

這其中一些關(guān)鍵過程可以在systrace中看到轿钠,如下圖所示:

image-20210920180755077.png

接下來應(yīng)用主線程收到vsync信號(hào)后會(huì)開始繪圖流程巢钓,應(yīng)用主線程會(huì)遍歷ViewTree對(duì)所有的View完成measure, layout, draw的工作,我們知道Android的應(yīng)用界面是由很多View按樹狀結(jié)構(gòu)組織起來的疗垛,如下圖以微信主界面為例症汹,但無論界面多么復(fù)雜它都有一個(gè)根View叫DecorView, 而紛繁復(fù)雜的界面最終都是由一個(gè)個(gè)View通過樹形結(jié)構(gòu)組織起來的。

image-20210922173743472.png

限于篇幅我們這里不討論UI線程的measure和layout部分贷腕,這里只來看下draw部分:

首先App每次開始繪畫都是收到一個(gè)vsync信號(hào)才會(huì)開始繪圖(這里暫不討論SurfaceView自主上幀的情況)背镇,應(yīng)用是通過Choreographer來感知vsync信號(hào), 在ViewRootImpl里向Choreographer注冊(cè)一個(gè)callback, 每當(dāng)有vsync信號(hào)來時(shí)會(huì)執(zhí)行mTraversalRunnable:

ViewRootImpl.java (frameworks\base\core\java\android\view)

 void scheduleTraversals() {
     if (!mTraversalScheduled) {
         ......
         mChoreographer.postCallback(
         Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//注冊(cè)vsync的回調(diào)
         ......
     }
 }
 final class TraversalRunnable implements Runnable {
     @Override
     public void run() {
        doTraversal();//每次vsync到時(shí)調(diào)用該函數(shù)
     }
 }

而doTraversal()主要是調(diào)用performTraversals()這個(gè)函數(shù),performTraversals里會(huì)調(diào)用到draw()函數(shù):

 private boolean draw(boolean fullRedrawNeeded) {
     ......
      if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
          ......
          mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);//這里傳下去的mView就是DecorView
          ......
      }
     ......
 }

上面的draw()函數(shù)進(jìn)一步調(diào)用了ThreadedRenderer的draw:

ThreadedRenderer.java (frameworks\base\core\java\android\view)

void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    ......
    updateRootDisplayList(view, callbacks);
    ......
}
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");//這里有個(gè)trace泽裳,我們可以在systrace中觀察到它
    ......
    updateViewTreeDisplayList(view);
    ......
}
private void updateViewTreeDisplayList(View view) {
    ......
    view.updateDisplayListIfDirty();//這里開始調(diào)用DecorView的updateDisplayListIfDirty
    ......
}

接下來代碼調(diào)用到DecorView的基類View.java:

View.java (frameworks\base\core\java\android\view)

public RenderNode updateDisplayListIfDirty() {
    ......
     final RecordingCanvas canvas = renderNode.beginRecording(width, height);//這里開始displaylist的record
     ......
     draw(canvas);
     ......
     
}

上面的RecordingCanvas就是扮演一個(gè)繪圖指令的記錄員角色瞒斩,它會(huì)將這個(gè)View通過draw函數(shù)繪制的指令以displaylist形式記錄下來,那么上面的renderNode又個(gè)什么東西呢涮总?

熟悉Web的同學(xué)一定會(huì)對(duì)DOM Tree和Render Tree不陌生胸囱,Android里的View和RenderNode是類似的概念,View代表的是實(shí)體在空間結(jié)構(gòu)上的存在瀑梗,而RenderNode代表它在界面呈現(xiàn)上的存在烹笔。這樣的設(shè)計(jì)可以讓存在和呈現(xiàn)進(jìn)行分離裳扯,便于實(shí)現(xiàn)同一存在不同狀態(tài)下呈現(xiàn)也不同。

在Android的設(shè)計(jì)里View會(huì)對(duì)應(yīng)一個(gè)RenderNode, RenderNode里的一個(gè)重要數(shù)據(jù)結(jié)構(gòu)是DisplayList, 每個(gè)DisplayList都會(huì)包含一系列DisplayListData. 這些DisplayList也會(huì)同樣以樹形結(jié)構(gòu)組織在一起谤职。

當(dāng)UI線程完成它的繪制工作后饰豺,它工作的產(chǎn)物是一堆DisplayListData, 我們可以將其理解為是一堆繪圖指令的集合,每一個(gè)DisplayListData都是在描繪這個(gè)View長什么樣子允蜈,所以一個(gè)View樹也可能理解為它的樣子由對(duì)應(yīng)的DisplayListData構(gòu)成的樹來描述:

image-20210921110021839.png

我們?cè)賮砜聪翫isplayListData是長什么樣子冤吨,它定義在下面這個(gè)文件中:

RecordingCanvas.h (frameworks\base\libs\hwui)

class DisplayListData final {
    ......
    void drawPath(const SkPath&, const SkPaint&);
    void drawRect(const SkRect&, const SkPaint&);
    void drawRegion(const SkRegion&, const SkPaint&);
    void drawOval(const SkRect&, const SkPaint&);
    void drawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&);
    ......
    template <typename T, typename... Args>
    void* push(size_t, Args&&...);
    ......
    SkAutoTMalloc<uint8_t> fBytes;
    ......
}

它的組成大體可以看成三個(gè)部分,第一部分是一堆以draw打頭的函數(shù)陷寝,它們是最基本的繪圖指令锅很,比如畫一條線, 畫一個(gè)矩形凤跑,畫一段圓弧等等爆安,上面我們摘取了其中幾個(gè),后面我們將以drawRect為例來看它是如何工作的; 第二部分是一個(gè)push模版函數(shù)仔引,后面我們會(huì)看到它的作用扔仓; 第三個(gè)是一塊存儲(chǔ)區(qū)fBytes,它會(huì)根據(jù)需要放大存儲(chǔ)區(qū)的大小咖耘。

我們來看下drawRect的實(shí)現(xiàn):

RecordingCanvas.cpp (frameworks\base\libs\hwui)

void DisplayListData::drawRect(const SkRect& rect, const SkPaint& paint) {
    this->push<DrawRect>(0, rect, paint);
}

我們發(fā)現(xiàn)它只是push了畫 一個(gè)Rect相關(guān)的參數(shù)翘簇,那么這個(gè)DrawRect又是什么呢?

struct Op {
    uint32_t type : 8;
    uint32_t skip : 24;
};
struct DrawRect final : Op {
    static const auto kType = Type::DrawRect;
    DrawRect(const SkRect& rect, const SkPaint& paint) : rect(rect), paint(paint) {}
    SkRect rect;
    SkPaint paint;
    void draw(SkCanvas* c, const SkMatrix&) const { c->drawRect(rect, paint); }
};

通過上面代碼我們不難發(fā)現(xiàn)儿倒,DrawRect代表的是一段內(nèi)存布局版保,這段內(nèi)存第一個(gè)字節(jié)存儲(chǔ)了它是哪種類型,后面的部分存儲(chǔ)有畫這個(gè)Rect所需要的參數(shù)信息夫否,再來看push方法的實(shí)現(xiàn):

template <typename T, typename... Args>
void* DisplayListData::push(size_t pod, Args&&... args) {
    ......
    auto op = (T*)(fBytes.get() + fUsed);
    fUsed += skip;
    new (op) T{std::forward<Args>(args)...};
    op->type = (uint32_t)T::kType;//注意這里將DrawRect的類型編碼Type::DrawRect存進(jìn)了第一個(gè)字節(jié)
    op->skip = skip;
    return op + 1;
}

這里push方法就是在fBytes后面放入這個(gè)DrawRect的內(nèi)存布局彻犁,也就是執(zhí)行DisplayListData::drawRect方法時(shí)就是把畫這個(gè)Rect的方法和參數(shù)存入了fBytes這塊內(nèi)存中, 那么最后fBytes這段內(nèi)存空間就放置了一條條的繪制指令凰慈。

通過上面的了解汞幢,我們知道了UI線程并沒有將應(yīng)用設(shè)計(jì)的View轉(zhuǎn)換成像素點(diǎn)數(shù)據(jù),而是將每個(gè)View的繪圖指令存入了內(nèi)存中微谓,我們通常稱這些繪圖指令為DisplayList, 下面讓我們跳出這些細(xì)節(jié)森篷,再次回到宏觀一些的角度。

當(dāng)所有的View的displaylist建立完成后豺型,代碼會(huì)來到:

RenderProxy.cpp (frameworks\base\libs\hwui\renderthread)

int RenderProxy::syncAndDrawFrame() {
    return mDrawFrameTask.drawFrame();
}

DrawFrameTask.cpp (frameworks\base\libs\hwui\renderthread)

void DrawFrameTask::postAndWait() {
    AutoMutex _lock(mLock);
    mRenderThread->queue().post([this]() { run(); });//丟任務(wù)到RenderThread線程
    mSignal.wait(mLock);
}

這邊可以看到UI線程的工作到此結(jié)束仲智,它丟了一個(gè)叫DrawFrameTask的任務(wù)到RenderThread線程中去,之后畫面繪制的工作轉(zhuǎn)移到RenderThread中來:

DrawFrameTask.cpp (frameworks\base\libs\hwui\renderthread)

void DrawFrameTask::run() {
    .....
    context->draw();
    .....
}

CanvasContext.cpp (frameworks\base\libs\hwui\renderthread)

void CanvasContext::draw() {
    ......
    Frame frame = mRenderPipeline->getFrame();//這句會(huì)調(diào)用到Surface的dequeueBuffer
    ......
     bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                                      mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
                                      &(profiler()));
    ......
    waitOnFences();
    ......
    bool didSwap =
            mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);//這句會(huì)調(diào)用到Surface的queueBuffer
    ......
}

在這個(gè)函數(shù)中完成了三個(gè)重要的動(dòng)作姻氨,一個(gè)是通過getFrame調(diào)到了Surface的dequeueBuffer向SurfaceFlinger申請(qǐng)了畫布钓辆, 第二是通過mRenderPipeline->draw將畫面畫到申請(qǐng)到的畫布上, 第三是通過調(diào)mRenderPipeline->swapBuffers把畫布提交到SurfaceFlinger去顯示。

那么在mRenderPipeline->draw里是如何將displaylist翻譯成畫布上的像素點(diǎn)顏色的呢岩馍?

SkiaOpenGLPipeline.cpp (frameworks\base\libs\hwui\pipeline\skia)

bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
                              const LightGeometry& lightGeometry,
                              LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
                              bool opaque, const LightInfo& lightInfo,
                              const std::vector<sp<RenderNode>>& renderNodes,
                              FrameInfoVisualizer* profiler) {
      ......
      renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, SkMatrix::I());
      ......
 }

SkiaPipeline.cpp (frameworks\base\libs\hwui\pipeline\skia)

void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
                               const std::vector<sp<RenderNode>>& nodes, bool opaque,
                               const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
                               const SkMatrix& preTransform) {
    ......
    SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers);
    ......
    renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform);
    endCapture(surface.get());
    ......
    ATRACE_NAME("flush commands");
    surface->getCanvas()->flush();
    ......
}

在上面的renderFrameImpl中會(huì)把在UI線程中記錄的displaylist重新“繪制”到skSurface中,然后通過SkCanvas將其轉(zhuǎn)化為gl指令抖韩, surface->getCanvas()->flush();這句是將指令發(fā)送給GPU執(zhí)行蛀恩,這其中是如何“翻譯”的細(xì)節(jié)筆者暫時(shí)尚未研究,這里先不做討論茂浮。

總結(jié)一下應(yīng)用通過android的View系統(tǒng)畫出第一幀的總的流程双谆,如下圖所示:

image-20210920180157716.png

首先是UI線程進(jìn)行measure, layout然后開始draw, 在draw的過程中會(huì)建立displaylist樹,將每個(gè)view應(yīng)該怎么畫記錄下來席揽,然后通過RenderProxy把后續(xù)任務(wù)下達(dá)給RenderThread, RenderThread主要完成三個(gè)動(dòng)作顽馋,先通過Surface接口向Surfaceflinger申請(qǐng)buffer, 然后通過SkiaOpenGLPipline的draw方法把displaylist翻譯成GPU指令, 指揮GPU把指令變成像素點(diǎn)數(shù)據(jù)幌羞, 最后通過swapBuffer把數(shù)據(jù)提交給SurfaceFlinger, 完成一幀數(shù)據(jù)的繪制和提交寸谜。

這個(gè)過程我們可以在systrace上觀察到,如下圖所示:

image-20210920182936703.png
image-20210920181748084.png

7.3. 幀數(shù)據(jù)的提交過程

那么應(yīng)用提交buffer以后SurfaceFlinger會(huì)如何處理呢属桦?又是如何提交到HWC Service去合成的呢熊痴?

    首先響應(yīng)應(yīng)用queueBuffer的是一條binder線程, 處理邏輯會(huì)走進(jìn):

BufferQueueProducer.cpp (frameworks\native\libs\gui)

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {
    ATRACE_CALL();
    ATRACE_BUFFER_INDEX(slot);
    ......
     if (frameAvailableListener != nullptr) {
        frameAvailableListener->onFrameAvailable(item);
     }
    ......
}

上面的frameAvailableListener是BufferQueueLayer:

BufferQueueLayer.cpp (frameworks\native\services\surfaceflinger)

void BufferQueueLayer::onFrameAvailable(const BufferItem& item) {
    ......
     mFlinger->signalLayerUpdate();//這里申請(qǐng)一下個(gè)vsync-sf信號(hào)
    ......
}

由上面代碼可知聂宾,只要有l(wèi)ayer上幀果善,那么就會(huì)申請(qǐng)下一次的vsync-sf信號(hào), 當(dāng)vsync-sf信號(hào)來時(shí)會(huì)調(diào)用onMessageReceived函數(shù)來處理幀數(shù)據(jù):

SurfaceFlinger.cpp (frameworks\native\services\surfaceflinger)

void SurfaceFlinger::onMessageInvalidate(nsecs_t expectedVSyncTime) {
    ATRACE_CALL();
    ......
        refreshNeeded |= handleMessageInvalidate();
    ......
    signalRefresh();//再次向消息隊(duì)列發(fā)送一個(gè)消息系谐,消息到達(dá)時(shí)會(huì)調(diào)用onMessageRefresh
    ......
}
bool SurfaceFlinger::handleMessageInvalidate() {
    ATRACE_CALL();
    bool refreshNeeded = handlePageFlip();
    ......
}

在handleMessageInvalidate里一個(gè)比較重要的函數(shù)是handlePageFlip():

bool SurfaceFlinger::handlePageFlip()
{
    ATRACE_CALL();
    ......
    mDrawingState.traverse([&](Layer* layer) {
        if (layer->hasReadyFrame()) {
            frameQueued = true;
            if (layer->shouldPresentNow(expectedPresentTime)) {
                mLayersWithQueuedFrames.push_back(layer);
            } 
            .......
        } 
        ......
    });
    ......
        for (auto& layer : mLayersWithQueuedFrames) {
            if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) {
                mLayersPendingRefresh.push_back(layer);
            }
            .......
        }
        ......
}

這里可以看出來巾陕,handlePageFlip里一個(gè)重要的工作是檢查所有的Layer是否有新buffer提交,如果有則調(diào)用其latchBuffer來處理:

BufferLayer.cpp (frameworks\native\services\surfaceflinger)

bool BufferLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
                              nsecs_t expectedPresentTime) {
    ATRACE_CALL();
    ......
    status_t err = updateTexImage(recomputeVisibleRegions, latchTime, expectedPresentTime);
    ......
}

BufferQueueLayer.cpp (frameworks\native\services\surfaceflinger)

status_t BufferQueueLayer::updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime,
                                          nsecs_t expectedPresentTime) {
     ......
      status_t updateResult = mConsumer->updateTexImage(&r, expectedPresentTime, &mAutoRefresh,
                                                      &queuedBuffer, maxFrameNumberToAcquire);
     ......
}

BufferLayerConsumer.cpp (frameworks\native\services\surfaceflinger)

status_t BufferLayerConsumer::updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime,
                                             bool* autoRefresh, bool* queuedBuffer,
                                             uint64_t maxFrameNumber) {
    ATRACE_CALL();
    ......
    status_t err = acquireBufferLocked(&item, expectedPresentTime, maxFrameNumber);
    ......
}

這里調(diào)用到了BufferLayerConsumer的基類ConsumerBase里:

status_t ConsumerBase::acquireBufferLocked(BufferItem *item,
        nsecs_t presentWhen, uint64_t maxFrameNumber) {
    ......
    status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber);
    ......
}

到這里onMessageReceived中的主要工作結(jié)束纪他,在這個(gè)函數(shù)的處理中鄙煤,SurfaceFlinger主要是檢查每個(gè)Layer是否有新提交的buffer, 如果有則調(diào)用latchBuffer將每個(gè)BufferQueue中的Slot 通過acquireBuffer拿走止喷。 之后拿走的buffer(Slot對(duì)應(yīng)的狀態(tài)是ACQUIRED狀態(tài))會(huì)被交由HWC Service處理馆类,這部分是在onMessageRefresh里處理的:

void SurfaceFlinger::onMessageRefresh() {
    ATRACE_CALL();
    ......
    mCompositionEngine->present(refreshArgs);
    ......
}

CompositionEngine.cpp (frameworks\native\services\surfaceflinger\compositionengine\src)

void CompositionEngine::present(CompositionRefreshArgs& args) {
    ATRACE_CALL();
    ......
    for (const auto& output : args.outputs) {
        output->present(args);
    }
    ......
}

Output.cpp (frameworks\native\services\surfaceflinger\compositionengine\src)

void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
    ATRACE_CALL();
    ......
    updateAndWriteCompositionState(refreshArgs);//告知HWC service有哪些layer要參與合成
    ......
    beginFrame();
    prepareFrame();
    ......
    finishFrame(refreshArgs);
    postFramebuffer();//這里會(huì)調(diào)用到HWC service的接口去present display合成畫面
}
void Output::postFramebuffer() {
    ......
    auto frame = presentAndGetFrameFences();
    ......
}
HWComposer.cpp (frameworks\native\services\surfaceflinger\displayhardware)
status_t HWComposer::presentAndGetReleaseFences(DisplayId displayId) {
    ATRACE_CALL();
    ......
    auto error = hwcDisplay->present(&displayData.lastPresentFence);//送去HWC service合成
    ......
    std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
    error = hwcDisplay->getReleaseFences(&releaseFences);
    RETURN_IF_HWC_ERROR_FOR("getReleaseFences", error, displayId, UNKNOWN_ERROR);

    displayData.releaseFences = std::move(releaseFences);//獲取releaseFence, 以便通知到各個(gè)Slot, buffer被release后會(huì)通過dequeueBuffer給到應(yīng)用,應(yīng)用在繪圖前會(huì)等待releaseFence
    ......
}

Hwc2::Composer是HWC service提供給Surfaceflinger側(cè)的一些接口弹谁,挑些有代表的如下

class Composer final : public Hwc2::Composer {
    ......
    Error createLayer(Display display, Layer* outLayer) override;//通知HWC要新加一個(gè)layer
    Error destroyLayer(Display display, Layer layer) override;//通知hwc 有個(gè)layer被destory掉了
    ......
    Error presentDisplay(Display display, int* outPresentFence) override;//合成畫面
    ......
    Error setActiveConfig(Display display, Config config) override;//設(shè)置一些參數(shù)乾巧,如屏幕刷新率
    ......
    CommandWriter mWriter;
    CommandReader mReader;//mWriter用于SurfaceFlinger向HWC service發(fā)送指令, mReader用于從HWC service側(cè)獲取信息
}
Error Composer::presentDisplay(Display display, int* outPresentFence)
{
    ......
    mWriter.presentDisplay();
    ......
}

到這里 SurfaceFlinger側(cè)的處理主流程走完了预愤,我們先來總結(jié)一下當(dāng)應(yīng)用的buffer提交到SurfaceFlinger后SurfaceFlinger所經(jīng)歷的主要流程沟于, 如下圖所示,首先binder線程會(huì)通過BufferQueue機(jī)制把應(yīng)用上幀的Slot狀態(tài)改為QUEUED, 然后把這個(gè)Slot放入mQueue隊(duì)列(請(qǐng)回憶5.3節(jié)知識(shí))植康, 然后通過onFrameAvailable回調(diào)通知到BufferQueueLayer, 在處理函數(shù)里會(huì)請(qǐng)求下一次的vsync-sf信號(hào)旷太,在vsync-sf信號(hào)到來后,SurfaceFlinger主線程要執(zhí)行兩次onMessageReceived, 第一次要檢查所有的layer看是否有上幀, 如果有Layer上幀就調(diào)用它的latchBuffer把它的buffer acquireBuffer走供璧。并發(fā)送一個(gè)消息到主消息隊(duì)列存崖,讓UI線程再次走進(jìn)onMessageReceived, 第二次走進(jìn)來時(shí),主要執(zhí)行present方法睡毒,在這些方法里會(huì)和HWC service溝通来惧,調(diào)用它的跨進(jìn)程接口通知它去做圖層的合成。

image-20210920180035351.png

之后就進(jìn)入HWC Service的處理流程演顾,這部分的處理流程和芯片廠商HAL層實(shí)現(xiàn)緊密相關(guān)供搀,限于某些因素不便于介紹。本章應(yīng)用更新畫面的代碼流程就介紹到這里钠至。

7.4. 本章小結(jié)

本章我們沿著代碼邏輯學(xué)習(xí)了應(yīng)用是如何申請(qǐng)到畫布葛虐、使用android的View系統(tǒng)如何繪圖、繪圖完成后如何提交buffer以及buffer提交以及Surfaceflinger如何處理棉钧。但本章所述的邏輯均是指通過android的View系統(tǒng)繪圖的過程屿脐,也可以稱其為hwui繪圖流程,從上面代碼流程可以知道掰盘,hwui的繪圖流程是被vsync信號(hào)觸發(fā)的摄悯,開始于vsync信號(hào)到達(dá)UI線程調(diào)用performTraversals函數(shù), hwui的畫面更新是被vsync信號(hào)驅(qū)動(dòng)的愧捕。

在android系統(tǒng)中也有提供不依賴vsync信號(hào)的自主上幀接口奢驯,比如app可以使用SurfaceView這個(gè)特殊的View來申請(qǐng)一個(gè)獨(dú)立于Activity之外的畫布。接下來我們通過一些helloworld示例來看下這些接口如何使用次绘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘪阁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子邮偎,更是在濱河造成了極大的恐慌管跺,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禾进,死亡現(xiàn)場離奇詭異豁跑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)泻云,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門艇拍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宠纯,你說我怎么就攤上這事卸夕。” “怎么了婆瓜?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵快集,是天一觀的道長贡羔。 經(jīng)常有香客問我,道長个初,這世上最難降的妖魔是什么乖寒? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮院溺,結(jié)果婚禮上宵统,老公的妹妹穿的比我還像新娘。我一直安慰自己覆获,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布瓢省。 她就那樣靜靜地躺著弄息,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勤婚。 梳的紋絲不亂的頭發(fā)上摹量,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音馒胆,去河邊找鬼缨称。 笑死,一個(gè)胖子當(dāng)著我的面吹牛祝迂,可吹牛的內(nèi)容都是我干的睦尽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼型雳,長吁一口氣:“原來是場噩夢啊……” “哼当凡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纠俭,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤沿量,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后冤荆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朴则,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年钓简,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乌妒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涌庭,死狀恐怖芥被,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坐榆,我是刑警寧澤拴魄,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響匹中,放射性物質(zhì)發(fā)生泄漏夏漱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一顶捷、第九天 我趴在偏房一處隱蔽的房頂上張望挂绰。 院中可真熱鬧,春花似錦服赎、人聲如沸葵蒂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽践付。三九已至,卻和暖如春缺厉,著一層夾襖步出監(jiān)牢的瞬間永高,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工提针, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留命爬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓辐脖,卻偏偏與公主長得像饲宛,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嗜价,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容