Android Bitmap的加載和Cache

Bitmap的高效加載

如何加載一個(gè)圖片?首先BitmapFactory類(lèi)提供了四種方法: decodeFile(), decodeResource(), decodeStream(), decodeByteArray(). 分別用于從文件系統(tǒng), 資源文件, 輸入流以及字節(jié)數(shù)組加載出一個(gè)Bitmap對(duì)象. 其中decodeFile和decodeResource又間接調(diào)用了decodeStream()方法, 這四類(lèi)方法最終是在Android的底層實(shí)現(xiàn)的, 對(duì)應(yīng)著B(niǎo)itmapFactory類(lèi)的幾個(gè)native方法.
高效加載的Bitmap的核心思想:采用BitmapFactory.Options來(lái)加載所需尺寸的圖片. 比如說(shuō)一個(gè)ImageView控件的大小為300300. 而圖片的大小為800800. 這個(gè)時(shí)候如果直接加載那么就比較浪費(fèi)資源, 需要更多的內(nèi)存空間來(lái)加載圖片, 這不是很必要的. 這里我們就可以先把圖片按一定的采樣率來(lái)縮小圖片在進(jìn)行加載. 不僅降低了內(nèi)存占用,還在一定程度上避免了OOM異常. 也提高了加載bitmap時(shí)的性能.
而通過(guò)Options參數(shù)來(lái)縮放圖片: 主要是用到了inSampleSize參數(shù), 即采樣率陷寝。

如果是inSampleSize=1那么和原圖大小一樣,
如果是inSampleSize=2那么寬高都為原圖1/2, 而像素為原圖的1/4, 占用的內(nèi)存大小也為原圖的1/4
如果是inSampleSize=3那么寬高都為原圖1/3, 而像素為原圖的1/9, 占用的內(nèi)存大小也為原圖的1/9
以此類(lèi)推…..

要知道Android中加載圖片具體在內(nèi)存中的占有的大小是根據(jù)圖片的像素決定的, 而與圖片的實(shí)際占用空間大小沒(méi)有關(guān)系.而且如果要加載mipmap下的圖片, 還會(huì)根據(jù)不同的分辨率下的文件夾進(jìn)行不同的放大縮小.
列舉現(xiàn)在有一張圖片像素為:10241024, 如果采用ARGB8888(四個(gè)顏色通道每個(gè)占有一個(gè)字節(jié),相當(dāng)于1點(diǎn)像素占用4個(gè)字節(jié)的空間)的格式來(lái)存儲(chǔ).(這里不考慮不同的資源文件下情況分析) 那么圖片的占有大小就是102410244那現(xiàn)在這張圖片在內(nèi)存中占用4MB.
如果針對(duì)剛才的圖片進(jìn)行inSampleSize=2, 那么最后占用內(nèi)存大小為512512*4, 也就是1MB
采樣率的數(shù)值必須是大于1的整數(shù)是才會(huì)有縮放效果, 并且采樣率同時(shí)作用于寬/高, 這將導(dǎo)致縮放后的圖片以這個(gè)采樣率的2次方遞減, 即內(nèi)存占用縮放大小為1/(inSampleSize的二次方). 如果小于1那么相當(dāng)于=1的時(shí)候. 在官方文檔中指出, inSampleSize的取值應(yīng)該總是為2的指數(shù), 比如1,2,4,8,16,32…如果外界傳遞inSampleSize不為2的指數(shù), 那么系統(tǒng)會(huì)向下取整并選擇一個(gè)最接近的2的指數(shù)來(lái)代替. 比如如果inSampleSize=3,那么系統(tǒng)會(huì)選擇2來(lái)代替. 但是這條規(guī)則并不作用于所有的android版本, 所以可以當(dāng)成一個(gè)開(kāi)發(fā)建議
整理一下開(kāi)發(fā)中代碼流程:

將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)置為true并加載圖片沪袭。
從BitmapFactory.Options取出圖片的原始寬高信息, 他們對(duì)應(yīng)于outWidth和outHeight參數(shù)枝缔。
根據(jù)采樣率的規(guī)則并結(jié)合目標(biāo)View的所需大小計(jì)算出采樣率inSampleSize。
將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)為false, 然后重新加載豺瘤。

inJustDecodeBounds這個(gè)參數(shù)的作用就是在加載圖片的時(shí)候是否只是加載圖片寬高信息而不把圖片全部加載到內(nèi)存. 所以這個(gè)操作是個(gè)輕量級(jí)的.
通過(guò)這些步驟就可以整理出以下的工具加載圖片類(lèi)調(diào)用decodeFixedSizeForResource()即可.

public class MyBitmapLoadUtil {
    /**
     * 對(duì)一個(gè)Resources的資源文件進(jìn)行指定長(zhǎng)寬來(lái)加載進(jìn)內(nèi)存, 并把這個(gè)bitmap對(duì)象返回
     *
     * @param res   資源文件對(duì)象
     * @param resId 要操作的圖片id
     * @param reqWidth 最終想要得到bitmap的寬度
     * @param reqHeight 最終想要得到bitmap的高度
     * @return 返回采樣之后的bitmap對(duì)象
     */
    public static Bitmap decodeFixedSizeForResource(Resources res, int resId, int reqWidth, int reqHeight){
        // 首先先指定加載的模式 為只是獲取資源文件的大小
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        //Calculate Size  計(jì)算要設(shè)置的采樣率 并把值設(shè)置到option上
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        // 關(guān)閉只加載屬性模式, 并重新加載的時(shí)候傳入自定義的options對(duì)象
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }
    /**
     *  一個(gè)計(jì)算工具類(lèi)的方法, 傳入圖片的屬性對(duì)象和 想要實(shí)現(xiàn)的目標(biāo)大小. 通過(guò)計(jì)算得到采樣值
     */
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        //Raw height and width of image
        //原始圖片的寬高屬性
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        // 如果想要實(shí)現(xiàn)的寬高比原始圖片的寬高小那么就可以計(jì)算出采樣率, 否則不需要改變采樣率
        if (reqWidth < height || reqHeight < width){
            int halfWidth = width/2;
            int halfHeight = height/2;
            // 判斷原始長(zhǎng)寬的一半是否比目標(biāo)大小小, 如果小那么增大采樣率2倍, 直到出現(xiàn)修改后原始值會(huì)比目標(biāo)值大的時(shí)候
            while((halfHeight/inSampleSize) >= reqHeight && (halfWidth/inSampleSize) >= reqWidth){
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

android中的緩存策略

1.lrucache

lrucache是api level 12提供的一個(gè)泛型類(lèi),它內(nèi)部采用一個(gè)linkedhashmap以強(qiáng)引用的方式存儲(chǔ)外界的緩存對(duì)象听诸,提供了get和put方法來(lái)完成緩存的獲取和添加操作坐求,當(dāng)緩存滿(mǎn)了,lrucache會(huì)remove掉較早使用的緩存對(duì)象晌梨,然后再添加新的對(duì)象桥嗤。

過(guò)去實(shí)現(xiàn)內(nèi)存緩存的常用做法是使用softreference或者使用weakreference须妻,但是并不推薦這種做法,從api level 9以后泛领,gc強(qiáng)制回收掉soft荒吏、weak引用,從而導(dǎo)致這些緩存并沒(méi)有任何效率的提升渊鞋。

lrucache的實(shí)現(xiàn)原理:
根據(jù)lru的算法思想绰更,我們需要一種數(shù)據(jù)結(jié)構(gòu)來(lái)快速定位哪個(gè)對(duì)象是最近訪(fǎng)問(wèn)的,哪個(gè)對(duì)象是最長(zhǎng)時(shí)間未訪(fǎng)問(wèn)的锡宋,lrucache選擇的是linkedhashmap這個(gè)數(shù)據(jù)結(jié)構(gòu)儡湾,它是一個(gè)雙向循環(huán)鏈表。來(lái)瞅一眼linkedhashmap的構(gòu)造函數(shù):

/** 初始化linkedhashmap

     * 第一個(gè)參數(shù):initialcapacity执俩,初始大小
     * 第二個(gè)參數(shù):loadfactor徐钠,負(fù)載因子=0.75f

     * 第三個(gè)參數(shù):accessorder=true,基于訪(fǎng)問(wèn)順序役首;accessorder=false丹皱,基于插入順序<br/>

   public linkedhashmap(int initialcapacity, float loadfactor, boolean accessorder) {

       super(initialcapacity, loadfactor);

       init();

       this.accessorder = accessorder;

    }

所以在lrucache中應(yīng)該選擇accessorder = true,當(dāng)我們調(diào)用put宋税、get方法時(shí)摊崭,linkedhashmap內(nèi)部會(huì)將這個(gè)item移動(dòng)到鏈表的尾部,即在鏈表尾部是最近剛剛使用的item杰赛,鏈表頭部就是最近最少使用的item呢簸。當(dāng)緩存空間不足時(shí),可以remove頭部結(jié)點(diǎn)釋放緩存空間乏屯。

下面舉例lrucache的典型使用姿勢(shì):

int maxmemory = (int) (runtime.getruntime().maxmemory() / 1024);

int cachesize = maxmemory / 8;

mmemorycache = new lrucache<string bitmap="">(cachesize) {

    @override

    protected int sizeof(string key, bitmap bitmap) {

        return bitmap.getrowbytes() * bitmap.getheight() / 1024;

    }

};

 <br/>// 向 lrucache 中添加一個(gè)緩存對(duì)象

private void addbitmaptomemorycache(string key, bitmap bitmap) {

    if (getbitmapfrommemcache(key) == null) {

        mmemorycache.put(key, bitmap);

    }

}

//獲取一個(gè)緩存對(duì)象

private bitmap getbitmapfrommemcache(string key) {

    return mmemorycache.get(key);

}</string>

上述示例代碼中根时,總?cè)萘康拇笮∈钱?dāng)前進(jìn)程的可用內(nèi)存的八分之一(官方推薦是八分之一哈,你們可以自己視情況定)辰晕,sizeof()方法計(jì)算了bitmap的大小蛤迎,sizeof方法默認(rèn)返回的是你緩存item數(shù)目,源碼中直接return 1(這里的源碼比較簡(jiǎn)單含友,可以自己看看~)替裆。

如果你需要cache中某個(gè)值釋放,可以重寫(xiě)entryremoved()方法窘问,這個(gè)方法會(huì)在元素被put或者remove的時(shí)候調(diào)用辆童,源碼默認(rèn)是空實(shí)現(xiàn)。重寫(xiě)entryremoved()方法還可以實(shí)現(xiàn)二級(jí)內(nèi)存緩存惠赫,進(jìn)一步提高性能把鉴。思路如下:重寫(xiě)entryremoved(),把刪除掉的item儿咱,再次存入另一個(gè)linkedhashmap中庭砍。這個(gè)數(shù)據(jù)結(jié)構(gòu)當(dāng)做二級(jí)緩存场晶,每次獲得圖片的時(shí)候,按照一級(jí)緩存 怠缸、二級(jí)緩存峰搪、sdcard、網(wǎng)絡(luò)的順序查找凯旭,找到就停止概耻。

2.disklrucache

當(dāng)我們需要存大量圖片的時(shí)候,我們指定的緩存空間可能很快就用完了罐呼,lrucache會(huì)頻繁地進(jìn)行trimtosize操作將最近最少使用的數(shù)據(jù)remove掉鞠柄,但是hold不住過(guò)會(huì)又要用這個(gè)數(shù)據(jù),又從網(wǎng)絡(luò)download一遍嫉柴,為此有了disklrucache厌杜,它可以保存這些已經(jīng)下載過(guò)的圖片。當(dāng)然计螺,從磁盤(pán)讀取圖片的時(shí)候要比內(nèi)存慢得多夯尽,并且應(yīng)該在非ui線(xiàn)程中載入磁盤(pán)圖片。disklrucache顧名思義登馒,實(shí)現(xiàn)存儲(chǔ)設(shè)備緩存匙握,即磁盤(pán)緩存,它通過(guò)將緩存對(duì)象寫(xiě)入文件系統(tǒng)從而實(shí)現(xiàn)緩存效果陈轿。

ps: 如果緩存的圖片經(jīng)常被使用圈纺,可以考慮使用contentprovider。

disklrucache的實(shí)現(xiàn)原理:
lrucache采用的是linkedhashmap這種數(shù)據(jù)結(jié)構(gòu)來(lái)保存緩存中的對(duì)象麦射,那么對(duì)于disklrucache呢蛾娶?由于數(shù)據(jù)是緩存在本地文件中,相當(dāng)于是持久保存的一個(gè)文件潜秋,即使app kill掉蛔琅,這些文件還在滴。so ,,,,, 到底是啥峻呛?disklrucache也是采用linekedhashmap這種數(shù)據(jù)結(jié)構(gòu)罗售,但是不夠,需要加持buff

日志文件杀饵。日志文件可以看做是一塊“內(nèi)存”莽囤,map中的value只保存文件的簡(jiǎn)要信息谬擦,對(duì)緩存文件的所有操作都會(huì)記錄在日志文件中切距。

disklrucache的初始化:

下面是disklrucache的創(chuàng)建過(guò)程:

private static final long disk_cache_size = 1024 * 1024 * 50; //50mb

file diskcachedir = getdiskcachedir(mcontext, "bitmap");

if (!diskcachedir.exists()) {

    diskcachedir.mkdirs();

}

if (getusablespace(diskcachedir) > disk_cache_size) {

    try {

        mdisklrucache = disklrucache.open(diskcachedir, 1, 1,

                disk_cache_size);

    } catch (ioexception e) {

        e.printstacktrace();

    }

}

瞅了一眼,可以知道重點(diǎn)在open()函數(shù)惨远,其中第一個(gè)參數(shù)表示文件的存儲(chǔ)路徑谜悟,緩存路徑可以是sd卡上的緩存目錄话肖,具體是指/sdcard/android/data/package_name/cache,package_name表示當(dāng)前應(yīng)用的包名葡幸,當(dāng)應(yīng)用被卸載后最筒, 此目錄會(huì)一并刪除掉。如果你希望應(yīng)用卸載后蔚叨,這些緩存文件不被刪除床蜘,可以指定sd卡上其他目錄。第二個(gè)參數(shù)表示應(yīng)用的版本號(hào)蔑水,一般設(shè)為1即可邢锯。第三個(gè)參數(shù)表示單個(gè)結(jié)點(diǎn)所對(duì)應(yīng)數(shù)據(jù)的個(gè)數(shù),一般設(shè)為1搀别。第四個(gè)參數(shù)表示緩存的總大小丹擎,比如50mb,當(dāng)緩存大小超過(guò)這個(gè)設(shè)定值后歇父,disklrucache會(huì)清除一些緩存保證總大小不會(huì)超過(guò)設(shè)定值

disklrucache的數(shù)據(jù)緩存與獲取緩存:
數(shù)據(jù)緩存操作是借助disklrucache.editor類(lèi)完成的蒂培,editor表示一個(gè)緩存對(duì)象的編輯對(duì)象。

new thread(new runnable() {  

    @override  

    public void run() {  

        try {  

            string imageurl = "http://d.url.cn/myapp/qq_desk/friendprofile_def_cover_001.png";  

            string key = hashkeyfordisk(imageurl);  //md5對(duì)url進(jìn)行加密榜苫,這個(gè)主要是為了獲得統(tǒng)一的16位字符

            disklrucache.editor editor = mdisklrucache.edit(key);  //拿到editor护戳,往journal日志中寫(xiě)入dirty記錄

            if (editor != null) {  

                outputstream outputstream = editor.newoutputstream(0);  

                if (downloadurltostream(imageurl, outputstream)) {  //downloadurltostream方法為下載圖片的方法,并且將輸出流放到outputstream

                    editor.commit();  //完成后記得commit()垂睬,成功后灸异,再往journal日志中寫(xiě)入clean記錄

                } else {  

                    editor.abort();  //失敗后,要remove緩存文件羔飞,往journal文件中寫(xiě)入remove記錄

                }  

            }  

            mdisklrucache.flush();  //將緩存操作同步到j(luò)ournal日志文件肺樟,不一定要在這里就調(diào)用

        } catch (ioexception e) {  

            e.printstacktrace();  

        }  

    }  

}).start();

上述示例代碼中,每次調(diào)用edit()方法時(shí)逻淌,會(huì)返回一個(gè)新的editor對(duì)象么伯,通過(guò)它可以得到一個(gè)文件輸出流;調(diào)用commit()方法將圖片寫(xiě)入到文件系統(tǒng)中卡儒,如果失敗田柔,通過(guò)abort()方法進(jìn)行回退。

而獲取緩存和緩存的添加過(guò)程類(lèi)似骨望,將url轉(zhuǎn)換為key硬爆,然后通過(guò)disklrucache的get方法得到一個(gè)snapshot對(duì)象,接著通過(guò)snapshot對(duì)象得到緩存的文件輸入流擎鸠。有了文件輸入流缀磕,bitmap就get到了。

 bitmap bitmap = null;

    string key = hashkeyformurl(url);

    disklrucache.snapshot snapshot = mdisklrucache.get(key);

    if (snapshot != null) {

        fileinputstream fileinputstream = (fileinputstream)snapshot.getinputstream(disk_cache_index);

        filedescriptor filedescriptor = fileinputstream.getfd();

        bitmap = mimageresizer.decodesampledbitmapfromfiledescriptor(filedescriptor,

                reqwidth, reqheight);

        ......

    }
disklrucache優(yōu)化思考:
disklrucache是基于日志文件的,每次對(duì)緩存文件操作都需要進(jìn)行日志記錄袜蚕,我們可以不用日志文件糟把,在第一次構(gòu)造disklrucache時(shí),直接從程序訪(fǎng)問(wèn)緩存目錄下的文件牲剃,并將每個(gè)緩存文件的訪(fǎng)問(wèn)時(shí)間作為初始值記錄在map中的value值遣疯,每次訪(fǎng)問(wèn)或保存緩存都更新相應(yīng)key對(duì)應(yīng)的緩存文件的訪(fǎng)問(wèn)時(shí)間,避免了頻繁地io操作凿傅。

#####3. 緩存策略對(duì)比與總結(jié)
lrucache是android中已經(jīng)封裝好的類(lèi)缠犀,disklrucache需要導(dǎo)入相應(yīng)的包才可以使用。
可以在ui線(xiàn)程中直接使用lrucache聪舒;使用disklrucache時(shí)夭坪,由于緩存或者獲取都需要對(duì)本地文件進(jìn)行操作,因此要在子線(xiàn)程中實(shí)現(xiàn)过椎。
lrucache主要用于內(nèi)存緩存室梅,當(dāng)app kill掉的時(shí)候,緩存也跟著沒(méi)了疚宇;而disklrucache主要用于存儲(chǔ)設(shè)備緩存亡鼠,app kill掉的時(shí)候,緩存還在
lrucache的內(nèi)部實(shí)現(xiàn)是linkedhashmap敷待,對(duì)于元素的添加或獲取用put间涵、get方法即可。而disklrucache是通過(guò)文件流的形式進(jìn)行緩存榜揖,所以對(duì)于元素的添加或獲取通過(guò)輸入輸出流來(lái)實(shí)現(xiàn)勾哩。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市举哟,隨后出現(xiàn)的幾起案子思劳,更是在濱河造成了極大的恐慌,老刑警劉巖妨猩,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潜叛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡壶硅,警方通過(guò)查閱死者的電腦和手機(jī)威兜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)庐椒,“玉大人椒舵,你說(shuō)我怎么就攤上這事≡继福” “怎么了笔宿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵犁钟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我措伐,道長(zhǎng)特纤,這世上最難降的妖魔是什么军俊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任侥加,我火速辦了婚禮,結(jié)果婚禮上粪躬,老公的妹妹穿的比我還像新娘担败。我一直安慰自己,他們只是感情好镰官,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布提前。 她就那樣靜靜地躺著,像睡著了一般泳唠。 火紅的嫁衣襯著肌膚如雪狈网。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天笨腥,我揣著相機(jī)與錄音拓哺,去河邊找鬼。 笑死脖母,一個(gè)胖子當(dāng)著我的面吹牛士鸥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谆级,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼烤礁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了肥照?” 一聲冷哼從身側(cè)響起脚仔,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舆绎,沒(méi)想到半個(gè)月后玻侥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亿蒸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年南用,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞬浓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖玄货,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酱畅,我是刑警寧澤弄喘,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布则拷,位于F島的核電站,受9級(jí)特大地震影響曹鸠,放射性物質(zhì)發(fā)生泄漏煌茬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一彻桃、第九天 我趴在偏房一處隱蔽的房頂上張望坛善。 院中可真熱鬧,春花似錦邻眷、人聲如沸眠屎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)改衩。三九已至,卻和暖如春驯镊,著一層夾襖步出監(jiān)牢的瞬間葫督,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工板惑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留橄镜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓洒放,卻偏偏與公主長(zhǎng)得像蛉鹿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子往湿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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