[Android] 解析 GraphicBuffer 之 Framework 層

[Android] GraphicBuffer之Framework層

一 前言

GraphicBuffer 是 SurfaceFlinger 中一塊重要的內(nèi)容, 它涉及到了我們應(yīng)用程序的數(shù)據(jù)是如何和SurfaceFlinger進(jìn)行傳遞的雌团。

在介紹 GraphicBuffer 之前壮吩,我們先提出這樣一個(gè)問(wèn)題:我們應(yīng)用程序的界面數(shù)據(jù)甩鳄,是如何傳遞給 SurfaceFlinger 進(jìn)行合成和顯示的代赁。是 Binder 嗎掩宜?顯然不是戚扳,Binder 傳遞不了這么大的數(shù)據(jù)。那么是共享內(nèi)存嗎魁巩,早期的界面數(shù)據(jù)的確是通過(guò)這種方式傳遞的急灭,但是那已經(jīng)是很早之前了。

前面我們介紹了SurfaceFlinger中的生產(chǎn)者和消費(fèi)者模型, 在生產(chǎn)者申請(qǐng) buffer 的時(shí)候, 如果拿到的 Slot 沒(méi)有和 GraphicBuffer 進(jìn)行綁定, 那么就會(huì)先創(chuàng)建一個(gè) GraphicBuffer , 然后進(jìn)行綁定

從這一篇開(kāi)始谷遂,我們就來(lái)探究 GraphicBuffer 的工作原理葬馋。

1.1 GraphicBuffer 的創(chuàng)建

在前面介紹 dequeueBuffer 申請(qǐng)緩沖區(qū)的時(shí)候, 我們還說(shuō)了一種邏輯, 那就是從 BufferQueue 中拿到的 slot 沒(méi)有關(guān)聯(lián)的 GraphicBuffer , 那么這種情況下還需要單獨(dú)創(chuàng)建 GraphicBuffer ,這里我們就來(lái)看看 GraphicBuffer 的創(chuàng)建邏輯

[frameworks/native/libs/gui/BufferQueueProducer.cpp]
if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
    // 如果拿到的緩沖區(qū)的 flag 為 BUFFER_NEEDS_REALLOCATION ,就創(chuàng)建一個(gè) GraphicBuffer
    sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
            width, height, format, BQ_LAYER_COUNT, usage,
            {mConsumerName.string(), mConsumerName.size()});
} 

在之前解讀 BufferQueue的時(shí)候介紹了 BufferQueue 中的生產(chǎn)者和消費(fèi)者,以及最重要的四個(gè)函數(shù)肾扰,其中的生產(chǎn)者在生成內(nèi)容的時(shí)候畴嘶,就會(huì)有這么一個(gè)過(guò)程;

  1. 生產(chǎn)者向 BufferQueue 中申請(qǐng) slot(緩沖槽)
  2. 生產(chǎn)者拿到 slot集晚,但是 slot 并沒(méi)有關(guān)聯(lián)對(duì)應(yīng)的 GraphicBuffer(緩沖區(qū))
  3. 生產(chǎn)者創(chuàng)建一個(gè)緩沖區(qū)窗悯,并將它與緩沖槽相關(guān)聯(lián)。

如上代碼偷拔,就是步驟2中的一個(gè)片段蒋院。接下來(lái),我們看 GraphicBuffer 創(chuàng)建時(shí)的具體流程莲绰。

1.2 GraphicBuffer的構(gòu)造函數(shù)

[frameworks/native/libs/ui/GraphicBuffer.cpp]
GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inUsage, std::string requestorName)
    : GraphicBuffer(inWidth, inHeight, inFormat, 1, static_cast<uint64_t>(inUsage), requestorName)
{
}

GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight, PixelFormat inFormat,
                             uint32_t inLayerCount, uint64_t inUsage, std::string requestorName)
      : GraphicBuffer() {
    mInitCheck = initWithSize(inWidth, inHeight, inFormat, inLayerCount, inUsage,
                              std::move(requestorName));
} 

GraphicBuffer 的構(gòu)造函數(shù)非常簡(jiǎn)單, 它只是調(diào)用了一個(gè)初始化函數(shù) initWithSize悦污。

1.3 GraphicBuffer::initWithSize

status_t GraphicBuffer::initWithSize(uint32_t inWidth, uint32_t inHeight,
        PixelFormat inFormat, uint32_t inLayerCount, uint64_t inUsage,
        std::string requestorName) {
    // 獲取一個(gè) GraphicBufferAllocator 對(duì)象, 這個(gè)對(duì)象是一個(gè)單例
    // GraphicBufferAllocator 主要負(fù)責(zé) GraphicBuffer 的內(nèi)存分配
    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
    uint32_t outStride = 0;
    // 分配一塊制定寬高的 GraphicBuffer
    status_t err = allocator.allocate(inWidth, inHeight, inFormat, inLayerCount,
            inUsage, &handle, &outStride, mId,
            std::move(requestorName));
    if (err == NO_ERROR) {
        // 通過(guò) GraphicBufferMapper 將這塊 GraphicBuffer 的參數(shù)記錄下來(lái)
        // GraphicBufferMapper 負(fù)責(zé)的是 GraphicBuffer 的內(nèi)存映射
        mBufferMapper.getTransportSize(handle, &mTransportNumFds, &mTransportNumInts);
    // 初始化參數(shù)
        width = static_cast<int>(inWidth);
        height = static_cast<int>(inHeight);
        format = inFormat;
        layerCount = inLayerCount;
        usage = inUsage;
        usage_deprecated = int(usage);
        stride = static_cast<int>(outStride);
    }
    return err;
} 

這里的初始化函數(shù)中,創(chuàng)建了一個(gè) GraphicBufferAllocator 對(duì)象钉蒲,這個(gè)我們申請(qǐng)的 GraphicBuffer 內(nèi)存,其實(shí)是通過(guò) GraphicBufferAllocator 這個(gè)對(duì)象進(jìn)行分配的彻坛。

從這里開(kāi)始顷啼,就需要注意了,因?yàn)樵趧?chuàng)建 GraphicBuffer 并分配內(nèi)存的時(shí)候昌屉,會(huì)通過(guò)一個(gè)個(gè)對(duì)象不斷的調(diào)用钙蒙,從 Framework 層一直到最后的硬件層。

首先我們來(lái)看 GraphicBufferAllocator 這個(gè)類(lèi)间驮。

二 GraphicBufferAllocator

GraphicBufferAllocator 它是一個(gè)單例躬厌,外部使用時(shí)可以通過(guò)它來(lái)為 GraphicBuffer 來(lái)分配內(nèi)存,Android 系統(tǒng)是為了屏蔽不同硬件平臺(tái)的差異性竞帽,所以使用它來(lái)為外部提供一個(gè)統(tǒng)一的接口扛施。

2.1 GraphicBufferAllocator::allocate

GraphicBufferAllocator::allocate 函數(shù)就是分配內(nèi)存的具體函數(shù), 它又通過(guò)調(diào)用一個(gè) mAllocator 對(duì)象的 allocate 來(lái)分配內(nèi)存, 這個(gè) mAllocator 是一個(gè) GrallocAllocator 的指針對(duì)象。GrallocAllocator 定義在frameworks/native/libs/ui/include/ui/Gralloc.h屹篓,它有幾個(gè)實(shí)現(xiàn)分別是 定義在 Gralloc3.h 中的 Gralloc3Allocator , 和定義在 Gralloc2.h 中的 Gralloc2Allocator

[frameworks/native/libs/ui/GraphicBufferAllocator.cpp]
status_t GraphicBufferAllocator::allocate(uint32_t width, uint32_t height,
        PixelFormat format, uint32_t layerCount, uint64_t usage,
        buffer_handle_t* handle, uint32_t* stride,
        uint64_t /*graphicBufferId*/, std::string requestorName) {
    // 如果寬或者高為0, 則將寬高設(shè)置為1
    if (!width || !height)
        width = height = 1;
    // 如果圖層的數(shù)量少于1, 則將圖層的數(shù)量設(shè)置為1
    if (layerCount < 1)
        layerCount = 1;
    // 移除調(diào)用者中的無(wú)效位
    usage &= ~static_cast<uint64_t>((1 << 10) | (1 << 13));
    // 分配內(nèi)存疙渣,使用的是 GrallocAllocator 指針,根據(jù)不同的版本有哦不同的實(shí)現(xiàn)堆巧,
    // 這里我們假設(shè)它的實(shí)現(xiàn)是 Gralloc3Allocator
    status_t error =
            mAllocator->allocate(width, height, format, layerCount, usage, 1, stride, handle);
    if (error == NO_ERROR) {
        // 初始化參數(shù)
        Mutex::Autolock _l(sLock);
        KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
        uint32_t bpp = bytesPerPixel(format);
        alloc_rec_t rec;
        rec.width = width;
        rec.height = height;
        rec.stride = *stride;
        rec.format = format;
        rec.layerCount = layerCount;
        rec.usage = usage;
        rec.size = static_cast<size_t>(height * (*stride) * bpp);
        rec.requestorName = std::move(requestorName);
        list.add(*handle, rec);
        return NO_ERROR;
    } else {
        return NO_MEMORY;
    }
} 

GraphicBufferAllocator 有兩個(gè)實(shí)現(xiàn)類(lèi), 我們來(lái)看它其中的一個(gè)實(shí)現(xiàn)類(lèi) Gralloc3Allocator

2.2 Gralloc3Allocator

GrallocAllocator 有多個(gè)實(shí)現(xiàn)版本妄荔,Gralloc3Allocator 就是實(shí)現(xiàn) Gralloc3 的版本

2.2.1 Gralloc3Allocator的定義

在 Gralloc3Allocator 定義的構(gòu)造函數(shù)中泼菌,只有一個(gè)參數(shù),就是 Gralloc3Mapper啦租。這個(gè)參數(shù)在后面做內(nèi)存分配的時(shí)候會(huì)用上哗伯,接著來(lái)看它構(gòu)造函數(shù)具體的實(shí)現(xiàn)

[frameworks/native/libs/ui/include/ui/Gralloc3.h]

class Gralloc3Allocator : public GrallocAllocator {
public:
    // Gralloc3Allocator 的構(gòu)造函數(shù)需要傳遞一個(gè) Gralloc3Mapper,因?yàn)榉峙鋬?nèi)存依賴映射器 mapper
    Gralloc3Allocator(const Gralloc3Mapper& mapper);

    bool isLoaded() const override;

    std::string dumpDebugInfo() const override;

    // 它只有一個(gè)函數(shù)篷角,就是分配內(nèi)存
    status_t allocate(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
                      uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
                      buffer_handle_t* outBufferHandles) const override;

private:
    const Gralloc3Mapper& mMapper;
    // 真正實(shí)現(xiàn)分配內(nèi)存的則是 mAllocator焊刹,這是通過(guò) HAL 調(diào)用。
    // HAL 層調(diào)用使用的通信方式就是 HIDL内地,其實(shí)和我們使用的 AIDL 是一樣的原理伴澄。
    sp<hardware::graphics::allocator::V3_0::IAllocator> mAllocator;
}; 

2.2.2 Gralloc3Allocator的構(gòu)造函數(shù)

[frameworks/native/libs/ui/Gralloc3.cpp]
Gralloc3Allocator::Gralloc3Allocator(const Gralloc3Mapper& mapper) : mMapper(mapper) {
    mAllocator = IAllocator::getService();
    if (mAllocator == nullptr) {
        return;
    }
} 

Gralloc3Allocator 的構(gòu)造函數(shù)會(huì)傳遞一個(gè) Gralloc3Mapper 作為參數(shù), 并將它賦值給 mMapper , 當(dāng)然, 其中最關(guān)鍵的還是通過(guò)一個(gè) IAllocator::getService() 獲取到了一個(gè) mAllocator 對(duì)象, 這個(gè) mAllocator 對(duì)象就是 hardware::graphics::allocator::V3_0::IAllocator 的指針。

看到這里阱缓,我們大概就能猜到非凌,這和我們常見(jiàn)的進(jìn)程間獲取服務(wù)的方式很相似,在我們?cè)趹?yīng)用中使用系統(tǒng)服務(wù)時(shí)荆针,也是先通過(guò) getService 拿到注冊(cè)好的服務(wù)敞嗡,然后再通過(guò)這個(gè) Bp 對(duì)象,調(diào)用對(duì)應(yīng)的服務(wù)函數(shù)航背。

2.2.3 Gralloc3Allocator的allocate

然后我們看 Gralloc3Allocator 分配內(nèi)存的函數(shù)

status_t Gralloc3Allocator::allocate(uint32_t width, uint32_t height, android::PixelFormat format,
                                     uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
                                     uint32_t* outStride, buffer_handle_t* outBufferHandles) const {

    // 定義一個(gè)緩沖區(qū)的描述信息 descriptorInfo , 并將緩沖區(qū)的相關(guān)參數(shù)都封裝到這個(gè)描述信息 descriptorInfo 中
    IMapper::BufferDescriptorInfo descriptorInfo;
    sBufferDescriptorInfo(width, height, format, layerCount, usage, &descriptorInfo);

    BufferDescriptor descriptor;
    // 通過(guò)之前構(gòu)造函數(shù)中的 mMapper 對(duì)象來(lái)創(chuàng)建一個(gè)描述信息
    status_t error = mMapper.createDescriptor(static_cast<void*>(&descriptorInfo),
                                              static_cast<void*>(&descriptor));
    if (error != NO_ERROR) {
        return error;
    }

    // 通過(guò) mAllocator 這個(gè) Bp 對(duì)象來(lái)調(diào)用對(duì)應(yīng)的系統(tǒng)服務(wù)
    // 調(diào)用到 hardware::graphics::allocator::V3_0::IAllocator 的 allocate 函數(shù)
    auto ret = mAllocator->allocate(descriptor, bufferCount,
                                    [&](const auto& tmpError, const auto& tmpStride,
                                        const auto& tmpBuffers) {
                                        error = static_cast<status_t>(tmpError);
                                        if (tmpError != Error::NONE) {
                                            return;
                                        }

                                        // import buffers
                                        for (uint32_t i = 0; i < bufferCount; i++) {
                                            error = mMapper.importBuffer(tmpBuffers[i],
                                                                         &outBufferHandles[i]);
                                            if (error != NO_ERROR) {
                                                for (uint32_t j = 0; j < i; j++) {
                                                    mMapper.freeBuffer(outBufferHandles[j]);
                                                    outBufferHandles[j] = nullptr;
                                                }
                                                return;
                                            }
                                        }
                                        *outStride = tmpStride;
                                    });

    // 確保內(nèi)核驅(qū)動(dòng)程序看到 BC_FREE_BUFFER 并立即關(guān)閉fds
    hardware::IPCThreadState::self()->flushCommands();
    return (ret.isOk()) ? error : static_cast<status_t>(kTransactionError);
} 

Gralloc3Allocator 的內(nèi)存分配主要用到了兩個(gè)對(duì)象喉悴,分別是繼承自 Gralloc::GraphicBufferMapper 的 mMapper,和繼承自 GraphicBufferAllocator 的 mAllocator玖媚。

mAllocator 是通過(guò) HAL 創(chuàng)建的對(duì)象, 這個(gè) IAllocator 是一個(gè) HIDL 接口, HIDL 其實(shí)就是 hardware 層的 AIDL , 它和我們應(yīng)用層的 AIDL 其實(shí)是一樣的, 在我們應(yīng)用層, 如果想使用 AIDL 進(jìn)行跨進(jìn)程通信, 需要定義一個(gè) aidl 后綴名的文件, 而在系統(tǒng)的 HAL 層, 如果想使用 HIDL 進(jìn)行跨進(jìn)行通信也是類(lèi)似, 需要定義一個(gè) hal 后綴名的文件.箕肃。

例如 IAllocator 這個(gè)接口,其實(shí)就定義中 hardware/interfaces/graphics/allocator/3.0/ 路徑下的 IAllocator.hal 中

[hardware/interfaces/graphics/allocator/3.0/IAllocator.hal]
package android.hardware.graphics.allocator@3.0;
import android.hardware.graphics.mapper@3.0;

// 這是一個(gè)接口
interface IAllocator {
    // 調(diào)試相關(guān)的方法今魔,不關(guān)心
    dumpDebugInfo() generates (string debugInfo);

    // 按照描述符的屬性分配緩沖區(qū)
    allocate(BufferDescriptor descriptor, uint32_t count)
        generates (Error error,
                   uint32_t stride,
                   vec<handle> buffers);
}; 

IAllocator.hal 定義的方法很簡(jiǎn)單, 它只有一個(gè) allocate 函數(shù),這個(gè)就是我們用來(lái)分配內(nèi)存的函數(shù)勺像。我們會(huì)拿到 Bp 對(duì)象來(lái)進(jìn)行調(diào)用,具體的實(shí)現(xiàn)則是在 Bn 中错森。

它實(shí)際上就是調(diào)用到了 HAL 部分的 allocate吟宦,這其實(shí)就有些像我們調(diào)用系統(tǒng)函數(shù)時(shí)的過(guò)程,也是通過(guò) AIDL 完成的涩维,只不過(guò)這里就是通過(guò) HIDL 完成的殃姓。接下來(lái)我們需要知道這個(gè) HAL 是如何完成工作的。

注意, 我們之前通過(guò) IAllocator 調(diào)用了兩個(gè)函數(shù), 其中還有一個(gè)函數(shù)是 getService ,而這里的 hal 文件中我們并沒(méi)有看到這個(gè)函數(shù), 那么這個(gè)函數(shù)是哪里來(lái)的呢 其實(shí)在編譯的時(shí)候, 還會(huì)生成一個(gè)文件 AllocatorAll.cpp , 這個(gè)文件就包含了我們之前調(diào)用的 getService

2.2.4 getService

[out/soong/.intermediates/hardware/interfaces/graphics/allocator/3.0/android.hardware.graphics.allocator@3.0_genc++/gen/android/hardware/graphics/allocator/3.0/AllocatorAll.cpp]
::android::sp<IAllocator> IAllocator::getService(const std::string &serviceName, const bool getStub) {
    return ::android::hardware::details::getServiceInternal<BpHwAllocator>(serviceName, true, getStub);
} 

到此瓦阐,F(xiàn)ramework 部分的內(nèi)容就全部介紹完了蜗侈,還是比較簡(jiǎn)單的,因?yàn)檎嬲墓ぷ鞫际前l(fā)生在 HAL 層和硬件層睡蟋。

最后宛篇,如果大伙有什么好的學(xué)習(xí)方法或建議歡迎大家在評(píng)論中積極留言哈,希望大家能夠共同學(xué)習(xí)薄湿、共同努力叫倍、共同進(jìn)步偷卧。

小編在這里祝小伙伴們?cè)谖磥?lái)的日子里都可以 升職加薪,當(dāng)上總經(jīng)理吆倦,出任CEO听诸,迎娶白富美,走上人生巔峰2显蟆晌梨!

不論遇到什么困難,都不應(yīng)該成為我們放棄的理由须妻!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仔蝌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子荒吏,更是在濱河造成了極大的恐慌敛惊,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绰更,死亡現(xiàn)場(chǎng)離奇詭異瞧挤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)儡湾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)特恬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人徐钠,你說(shuō)我怎么就攤上這事癌刽。” “怎么了尝丐?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵养铸,是天一觀的道長(zhǎng)株旷。 經(jīng)常有香客問(wèn)我星爪,道長(zhǎng)男翰,這世上最難降的妖魔是什么风秤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任稼稿,我火速辦了婚禮苔咪,結(jié)果婚禮上漓概,老公的妹妹穿的比我還像新娘乏屯。我一直安慰自己根时,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布辰晕。 她就那樣靜靜地躺著蛤迎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪含友。 梳的紋絲不亂的頭發(fā)上替裆,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天校辩,我揣著相機(jī)與錄音,去河邊找鬼辆童。 笑死宜咒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的把鉴。 我是一名探鬼主播故黑,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼庭砍!你這毒婦竟也來(lái)了场晶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤怠缸,失蹤者是張志新(化名)和其女友劉穎诗轻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凯旭,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡概耻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罐呼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞠柄。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嫉柴,靈堂內(nèi)的尸體忽然破棺而出厌杜,到底是詐尸還是另有隱情,我是刑警寧澤计螺,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布夯尽,位于F島的核電站,受9級(jí)特大地震影響登馒,放射性物質(zhì)發(fā)生泄漏匙握。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一陈轿、第九天 我趴在偏房一處隱蔽的房頂上張望圈纺。 院中可真熱鬧,春花似錦麦射、人聲如沸蛾娶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛔琅。三九已至,卻和暖如春峻呛,著一層夾襖步出監(jiān)牢的瞬間罗售,已是汗流浹背辜窑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留莽囤,地道東北人谬擦。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像朽缎,于是被迫代替她去往敵國(guó)和親惨远。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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