Glide筆記

Glide筆記

一、簡(jiǎn)介

在泰國(guó)舉行的谷歌開(kāi)發(fā)者論壇上,谷歌為我們介紹了一個(gè)名叫Glide的圖片加載庫(kù)痢毒,作者是bumptech。這個(gè)庫(kù)被廣泛的運(yùn)用在Google的開(kāi)源項(xiàng)目中蚕甥,包括2014年Google I/O大會(huì)上發(fā)布的官方App哪替。

Glide是一款由Bump Technologies開(kāi)發(fā)的圖片加載框架,使得我們可以在Android平臺(tái)上以極度簡(jiǎn)單的方式加載和展示圖片菇怀。Glide默認(rèn)使用HttpUrlConnection進(jìn)行網(wǎng)絡(luò)請(qǐng)求凭舶,為了讓App保持一致的網(wǎng)絡(luò)請(qǐng)求形式,可以讓Glide使用我們指定的網(wǎng)絡(luò)請(qǐng)求形式請(qǐng)求網(wǎng)絡(luò)資源爱沟。

二帅霜、依賴

1.jar包

Github地址:https://github.com/bumptech/glide/releases

2.Gradle

dependencies {  
    compile 'com.github.bumptech.glide:glide:3.7.0'  
    compile 'com.android.support:support-v4:23.3.0'  
}

三、權(quán)限

<uses-permission android:name="android.permission.INTERNET" />

四呼伸、混淆

-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
    **[] $VALUES;
    public *;
}

五身冀、使用

  • Glide.with(context).load(imageUrl).into(imageView); //從URL中加載
  • Glide.with(context).load(R.mipmap.ic_launcher).into(imageView); //從Res資源中加載
  • File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Running.jpg");
    Glide.with(context).load(file).into(imageView); //從文件加載
  • Uri uri = resourceIdToUri(context, R.mipmap.ic_launcher);
    Glide.with(context).load(uri).into(imageView);//從Uri加載

Glide.with()方法用于創(chuàng)建一個(gè)加載圖片的實(shí)例。with()方法可以接收Context括享、Activity搂根、Fragment或者FragmentActivity類型的參數(shù)。特別需要注意的是with()方法中傳入的實(shí)例會(huì)決定Glide加載圖片的生命周期铃辖,如果傳入的是Activity剩愧、Fragment或者FragmentActivity的實(shí)例,那么當(dāng)其被銷(xiāo)毀時(shí)圖片加載也會(huì)停止娇斩,如果傳入的是ApplicationContext時(shí)只有當(dāng)應(yīng)用程序被殺掉的時(shí)候圖片加載才會(huì)停止仁卷。

使用Glide加載圖片不用擔(dān)心內(nèi)存浪費(fèi)穴翩,甚至是內(nèi)存溢出的問(wèn)題。因?yàn)镚lide不會(huì)直接將圖片的完整尺寸全部加載到內(nèi)存中锦积,而是用多少加載多少藏否。Glide會(huì)自動(dòng)判斷ImageView的大小,然后只將這么大的圖片像素加載到內(nèi)存當(dāng)中充包,幫助我們節(jié)省內(nèi)存開(kāi)支副签。

下面一個(gè)小的工具函數(shù)可以將資源id轉(zhuǎn)換為一個(gè)Uri:

public static final String ANDROID_RESOURCE = "android.resource://";
public static final String FOREWARD_SLASH = "/";
private static Uri resourceIdToUri(Context context, int resourceId) {
    return Uri.parse(ANDROID_RESOURCE + context.getPackageName() + FOREWARD_SLASH + resourceId);
}

六、方法

1基矮、指定圖片格式

如果調(diào)用了.asBitmap()方法淆储,則.load()中的參數(shù)指向的可以是一個(gè)靜態(tài)圖片也可以是GIF圖片,如果是一張GIF圖片家浇,則加載之后只會(huì)展示GIF圖片的第一幀本砰。
如果調(diào)用的.asGif()方法,則.load()方法中的參數(shù)指向的必須是一個(gè)GIF圖片钢悲,如果是一張靜態(tài)圖片点额,則圖片加載完成之后展示的只是出錯(cuò)占位符(如果沒(méi)有設(shè)置出錯(cuò)占位符,則什么也不展示)莺琳。

//顯示靜態(tài)圖片(若加載的是gif圖那么就會(huì)顯示第一幀的圖片)
.asBitmap()
//顯示動(dòng)態(tài)圖片(若加載的是靜態(tài)圖會(huì)加載失敾估狻)
.asGif()

2、指定占位圖顯示

//加載時(shí)顯示的圖片
.placeholder(R.drawable.image_load)
//加載失敗時(shí)顯示的圖片
.error(R.drawable.image_error)

3惭等、設(shè)置緩存

Android應(yīng)用中一個(gè)較好的圖片處理框架珍手,會(huì)最小化網(wǎng)絡(luò)請(qǐng)求的消耗。Glide也是一樣辞做,默認(rèn)使用內(nèi)存和磁盤(pán)緩存來(lái)避免不必要的網(wǎng)絡(luò)請(qǐng)求琳要。然而,如果你的圖片變化的非吵用快稚补,你需要禁止一些緩存。
比如你請(qǐng)求一個(gè)1000x1000像素的圖片框喳,你的ImageView是500x500像素课幕,Glide會(huì)保存兩個(gè)版本的圖片到緩存里。

//禁止內(nèi)存緩存帖努,但仍然會(huì)緩存到磁盤(pán)
.skipMemoryCache(true)
//禁止磁盤(pán)緩存(Glide默認(rèn)緩存策略是:DiskCacheStrategy.RESULT)
.diskCacheStrategy(DiskCacheStrategy.NONE)
//緩存參數(shù)
//ALL:緩存源資源和轉(zhuǎn)換后的資源(即緩存所有版本圖像撰豺,默認(rèn)行為)
//NONE:不作任何磁盤(pán)緩存,然而默認(rèn)的它將仍然使用內(nèi)存緩存
//SOURCE:僅緩存源資源(原來(lái)的全分辨率的圖像)拼余,上面例子里的1000x1000像素的圖片
//RESULT:緩存轉(zhuǎn)換后的資源(最終的圖像,即降低分辨率后的或者是轉(zhuǎn)換后的)

如果你有一個(gè)圖片你需要經(jīng)常處理它亩歹,會(huì)生成各種不同的版本的圖片匙监,緩存它的原始的分辨率圖片才有意義凡橱。我們
使用DiskCacheStrategy.SOURCE去告訴Glide只緩存原始版本:

Glide.with(context).load("url").diskCacheStrategy(DiskCacheStrategy.SOURCE).into(imageView);

4、設(shè)置加載尺寸

Glide在緩存和內(nèi)存里自動(dòng)限制圖片的大小去適配ImageView的尺寸亭姥。用Glide時(shí)稼钩,如果圖片不需要自動(dòng)適配ImageView,調(diào)用override(horizontalSize, verticalSize)达罗,
它會(huì)在將圖片顯示在ImageView之前調(diào)整圖片的大小坝撑。

//加載圖片為100*100像素的尺寸
.override(100, 100)

5、設(shè)置圖片縮放

如果調(diào)用了.centerCrop()方法粮揉,則顯示圖片的時(shí)候短的一邊填充容器巡李,長(zhǎng)的一邊跟隨縮放;
如果調(diào)用了.fitCenter()方法扶认,則顯示圖片的時(shí)候長(zhǎng)的一邊填充容器侨拦,短的一邊跟隨縮放;
這兩個(gè)方法可以都調(diào)用狱从,如果都調(diào)用叠纹,則最終顯示的效果是后調(diào)用的方法展示的效果。

//它是一個(gè)裁剪技術(shù)训貌,即縮放圖像讓它填充到ImageView界限內(nèi)并且裁剪額外的部分冒窍,ImageView可能會(huì)完全填充递沪,但圖像可能不會(huì)完整顯示
.centerCrop()
//它是一個(gè)裁剪技術(shù),即縮放圖像讓圖像都測(cè)量出來(lái)等于或小于ImageView的邊界范圍综液,該圖像將會(huì)完全顯示款慨,但可能不會(huì)填滿整個(gè)ImageView
.fitCenter()
Glide
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .override(600, 200) // resizes the image to these dimensions (in pixel)
    .centerCrop() // this cropping technique scales the image so that it fills the requested bounds and then crops the extra.
    .into(imageViewResizeCenterCrop);

6、設(shè)置資源加載優(yōu)先級(jí)

假設(shè)你正在創(chuàng)建一個(gè)信息展示界面谬莹,包含頂部的一個(gè)主要照片檩奠,還有底部的2個(gè)并不重要的小圖。對(duì)于用戶體驗(yàn)附帽,我們最好先加載主角照片埠戳,然后再加載底部不緊急的圖片。Glide里的.priority()方法和Priority的枚舉變量支持你的想法蕉扮。

Priority枚舉變量整胃,以遞增方式列出:

  • Priority.LOW
  • Priority.NORMAL
  • Priority.HIGH
  • Priority.IMMEDIATE

你應(yīng)當(dāng)明白優(yōu)先級(jí)并不是非常嚴(yán)格的。Glide會(huì)將它們作為一個(gè)指導(dǎo)來(lái)最優(yōu)化處理請(qǐng)求喳钟。但并不意味著所有的圖片都能夠按請(qǐng)求的順序加載屁使。

.priority(Priority.HIGH)

7在岂、設(shè)置圓角或圓形圖片

//圓角圖片
.transform(new GlideRoundTransform(this))
//圓形圖片
.transform(new GlideCircleTransform(this))

8、設(shè)置縮略圖

縮略圖的優(yōu)點(diǎn)

縮略圖不同于前面提到的占位圖蔽午。占位圖應(yīng)當(dāng)是跟app綁定在一起的資源及老〗径瘢縮略圖是一個(gè)動(dòng)態(tài)的占位圖尉咕,可以從網(wǎng)絡(luò)加載年缎〉ノ撸縮略圖也會(huì)被先加載,直到實(shí)際圖片請(qǐng)求加載完畢堂淡。如果因?yàn)槟承┰蚓畹恚s略圖獲得的時(shí)間晚于原始圖片皆的,它并不會(huì)替代原始圖片费薄,而是簡(jiǎn)單地被忽略掉楞抡。

提示:另外一個(gè)非常棒的平滑圖片顯示的方法召廷,通過(guò)加載圖片主色調(diào)的占位圖。

Glide提供了2個(gè)不同的方法產(chǎn)生縮略圖数初。

第一種:簡(jiǎn)單的縮略圖

通過(guò)在加載的時(shí)候指定一個(gè)小的分辨率梗顺,產(chǎn)生一個(gè)縮略圖寺谤。這個(gè)方法在ListView和詳細(xì)視圖的組合中非常有用变屁。如果你已經(jīng)在ListView中用到了250x250像素的圖片粟关,那么在在詳細(xì)視圖中會(huì)需要一個(gè)更大分辨率的圖片闷板。然而從用戶的角度遮晚,我們已經(jīng)看見(jiàn)了一個(gè)小版本的圖片县遣,為什么需要好幾秒萧求,同樣的圖片(高分辨率的)才能被再次加載出來(lái)呢顶瞒?
在這種情況下搁拙,從顯示250x250像素版本的圖片平滑過(guò)渡到詳細(xì)視圖里查看大圖更有意義箕速。Glide里的.thumbnail()方法讓這個(gè)變?yōu)榭赡苎尉ァ_@里,.thumbnal()的參數(shù)是一個(gè)(0,1)之間浮點(diǎn)數(shù):

Glide.with(context).load("url")
    .thumbnail(0.1f)//系數(shù)需在(0,1)之間狡赐,這樣會(huì)先加載縮略圖然后加載全圖
    .into(imageView);

這里傳遞一個(gè)0.1f作為參數(shù)枕屉,Glide會(huì)加載原始圖片大小的10%的圖片搀擂。如果原始圖片有1000x1000像素卷玉,縮略圖的分辨率為100x100像素相种。由于圖片將會(huì)比ImageView小寝并,你需要確笔尘ィ縮放類型是否正確别渔。
注意到你所有的請(qǐng)求設(shè)置都會(huì)影響到你的縮略圖。例如喇伯,如果你使用了一個(gè)變換讓你的圖片變?yōu)榛叶葓D稻据,縮略圖也同樣將會(huì)是灰度圖捻悯。

第二種:高級(jí)縮略圖請(qǐng)求(原圖與縮略圖完全不同 )

.thumbnail()傳入一個(gè)浮點(diǎn)類型的參數(shù)今缚,非常簡(jiǎn)單有效姓言,但并不是總是有意義。如果縮略圖需要從網(wǎng)絡(luò)加載同樣全分辨率圖片囱淋,可能根本都不快妥衣。這樣称鳞,Glide提供了另一個(gè)方法去加載和顯示縮略圖:傳遞一個(gè)新的Glide請(qǐng)求作為參數(shù)稠鼻。

private void loadImageThumbnailRequest() {  
    // setup Glide request without the into() method
    DrawableRequestBuilder<String> thumbnailRequest = Glide
        .with( context )
        .load( eatFoodyImages[2] );

    // pass the request as a a parameter to the thumbnail request
    Glide
        .with( context )
        .load( UsageExampleGifAndVideos.gifUrl )
        .thumbnail( thumbnailRequest )
        .into( imageView3 );
}

區(qū)別在于第一個(gè)縮略圖請(qǐng)求是完全獨(dú)立于第二個(gè)原始請(qǐng)求的候齿』哦ⅲ縮略圖可以來(lái)自不同資源或者圖片URL亚皂,你可以在它上面應(yīng)用不同的變換灭必。

9禁漓、設(shè)置動(dòng)畫(huà)

加載圖片時(shí)所展示的動(dòng)畫(huà)播歼,可以是Animator類型的屬性動(dòng)畫(huà)秘狞,也可以是int類型的動(dòng)畫(huà)資源烁试。這個(gè)動(dòng)畫(huà)只在第一次加載的時(shí)候會(huì)展示廓潜,以后都會(huì)從緩存中獲取圖片辩蛋,因此也就不會(huì)展示動(dòng)畫(huà)了(圖片的改變時(shí)才會(huì)有用)。

//設(shè)置加載動(dòng)畫(huà)
.animate(R.anim.alpha_in)
//實(shí)現(xiàn)ViewPropertyAnimation.Animator接口
.animate(ViewPropertyAnimation.Animator animator)
//淡入淡出動(dòng)畫(huà)伤为,也是默認(rèn)動(dòng)畫(huà)绞愚,動(dòng)畫(huà)默認(rèn)的持續(xù)時(shí)間是300毫秒位衩。類似:.crossFade(int duration)
.crossFade() 
//移除所有動(dòng)畫(huà)
.dontAnimate()
ViewPropertyAnimation.Animator animationObject = new ViewPropertyAnimation.Animator() {  
    @Override
    public void animate(View view) {
        // if it's a custom view class, cast it here
        // then find subviews and do the animations
        // here, we just use the entire view for the fade animation
        view.setAlpha( 0f );

        ObjectAnimator fadeAnim = ObjectAnimator.ofFloat( view, "alpha", 0f, 1f );
        fadeAnim.setDuration( 2500 );
        fadeAnim.start();
    }
};

10糖驴、加載本地視頻(相當(dāng)于一張縮略圖)

//只能加載本地視頻(顯示的只是視頻的第一幀圖像贮缕,相當(dāng)于一張縮略圖感昼,不能播
//放視頻)定嗓,網(wǎng)絡(luò)視頻無(wú)法加載蜕乡。如果你想要從網(wǎng)絡(luò)URL播放視頻层玲,參考VideoView
String files = Environment.getExternalStorageDirectory().getAbsolutePath() + "/glide.avi";
Glide.with(this).load(files).into(view);

11反症、定制view中使用SimpleTarget和ViewTarget

Glide中的回調(diào):Target

假設(shè)我們并沒(méi)有ImageView作為圖片加載的目標(biāo)铅碍。我們只需要Bitmap本身。Glide提供了一個(gè)用Target獲取Bitmap資源的方法尘盼。Target只是用來(lái)回調(diào)卿捎,它會(huì)在所有的加載和處理完畢時(shí)返回想要的結(jié)果午阵。

Glide提供了多種多樣有各自明確目的Target。先從SimpleTarget介紹植袍。

SimpleTarget
private SimpleTarget target = new SimpleTarget<Bitmap>() {  
    @Override
    public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
        // do something with the bitmap
        // for demonstration purposes, let's just set it to an ImageView
        imageView1.setImageBitmap( bitmap );
    }
};

    Glide
        .with(context) // could be an issue!
        .load(eatFoodyImages[0])
        .asBitmap()
        .into(target);

使用Target注意事項(xiàng):

  • 第一個(gè)是SimpleTarget對(duì)象的定義于个。java/Android可以允許你在.into()內(nèi)匿名定義览濒,但這會(huì)顯著增加在Glide處理完
    圖片請(qǐng)求前Android垃圾回收清理匿名target對(duì)象的可能性。最終宙项,會(huì)導(dǎo)致圖片被加載了尤筐,但是回調(diào)永遠(yuǎn)不會(huì)被調(diào)用盆繁。
    所以油昂,請(qǐng)確保將你的回調(diào)定義為一個(gè)字段對(duì)象冕碟,防止被萬(wàn)惡的Android垃圾回收給清理掉安寺。
  • 第二個(gè)關(guān)鍵部分是Glide的.with( context )挑庶。這個(gè)問(wèn)題實(shí)際上是Glide一個(gè)特性問(wèn)題:當(dāng)你傳遞了一個(gè)context,例如
    當(dāng)前app的activity井仰,當(dāng)activity停止后俱恶,Glide會(huì)自動(dòng)停止當(dāng)前的請(qǐng)求合是。這種整合到app生命周期內(nèi)是非常有用的聪全,但也
    是很難處理的难礼。如果你的target是獨(dú)立于app的生命周期蛾茉。這里的解決方案是使用application的context:.with(context.getApplicationContext())撩鹿。當(dāng)app自己停止運(yùn)行的時(shí)候节沦,Glide會(huì)只取消掉圖片的請(qǐng)求甫贯。
特定大小的Target

另外一個(gè)潛在問(wèn)題是Target沒(méi)有一個(gè)明確的大小叫搁。如果你傳遞一個(gè)ImageView作為.into()的參數(shù)常熙,Glide會(huì)使用ImageView的
大小來(lái)限制圖片的大小裸卫。例如如果要加載的圖片是1000x1000像素墓贿,但是ImageView的尺寸只有250x250像素,Glide會(huì)降低圖片到小尺寸队伟,以節(jié)省處理時(shí)間和內(nèi)存嗜侮。顯然锈颗,由于target沒(méi)有具體大小击吱,這對(duì)target并不起效覆醇。但是永脓,如果你有個(gè)期望的具體大小,你可以增強(qiáng)回調(diào)革屠。如果你知道圖片應(yīng)當(dāng)為多大,那么在你的回調(diào)定義里應(yīng)當(dāng)指明板甘,以節(jié)省內(nèi)存:

private SimpleTarget target2 = new SimpleTarget<Bitmap>(250, 250) {  
    @Override
    public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
        imageView2.setImageBitmap(bitmap);
    }
};
ViewTarget

有很多原因?qū)е挛覀儾荒苤苯邮褂肐mageView盐类。前面已經(jīng)介紹了如何獲取Bitmap≡谔現(xiàn)在猫妙,我們將更深入學(xué)習(xí)割坠。假設(shè)你有個(gè)自定義的View。由于沒(méi)有已知的方法在哪里設(shè)置圖片对妄,Glide并不支持加載圖片到定制的View內(nèi)剪菱。然而用ViewTarget會(huì)讓這個(gè)更簡(jiǎn)單孝常。

讓我們看一個(gè)簡(jiǎn)單的定制View茫因,它繼承于FrameLayout冻押,內(nèi)部使用了一個(gè)ImageView:

public class FutureStudioView extends FrameLayout {  
    ...
    public void setImage(Drawable drawable) {
        iv = (ImageView) findViewById(R.id.custom_view_image);
        iv.setImageDrawable(drawable);
    }
}

由于我們定制的View并不是繼承自ImageView洛巢,這里不能使用常規(guī)的.into()方法稿茉。因此漓库,我們只能創(chuàng)建一個(gè)ViewTarget渺蒿,用來(lái)傳遞給.into()方法:

void loadImageViewTarget() {  
    FutureStudioView customView = (FutureStudioView) findViewById(R.id.custom_view);

    viewTarget = new ViewTarget<FutureStudioView, GlideDrawable>(customView) {
        @Override
        public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
            this.view.setImage(resource.getCurrent());
        }
    };

    Glide.with(context.getApplicationContext()) // safer!
        .load(eatFoodyImages[2])
        .into(viewTarget);
}

在target的回調(diào)方法中茂装,我們?cè)诙ㄖ苬iew上使用我們創(chuàng)建的setImage(Drawable drawable)方法設(shè)置圖片少态。同時(shí)彼妻,確保你注意到我們已經(jīng)在ViewTarget的構(gòu)造方法里傳遞了我們的定制view:new ViewTarget<FutureStudioView, GlideDrawable>(customView)澳骤。

12、設(shè)置監(jiān)聽(tīng)請(qǐng)求接口

首先摊册,創(chuàng)建一個(gè)listener作為一個(gè)字段對(duì)象茅特,避免被垃圾回收:

private RequestListener<String, GlideDrawable> requestListener 
    = new RequestListener<String, GlideDrawable>() {  
    @Override
    public boolean onException(Exception e, String model, 
    Target<GlideDrawable> target, boolean isFirstResource) {
        // todo log exception
        // important to return false so the error placeholder can be placed
        //加載異常
        return false;
    }

    @Override
    public boolean onResourceReady(GlideDrawable resource, String model, 
    Target<GlideDrawable> target, boolean isFromMemoryCache, 
    boolean isFirstResource) {
            //加載成功
            //view.setImageDrawable(resource);
        return false;
    }
};

在onException方法中白修,你可以抓取問(wèn)題兵睛,并決定你需要做什么祖很,比如記錄日志假颇。如果Glide應(yīng)當(dāng)處理這個(gè)后果笨鸡,比如顯示一個(gè)出錯(cuò)占位圖形耗,在onException方法中返回false是很重要的辙浑。

Glide  
    .with( context )
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .listener( requestListener )
    .error( R.drawable.cupcake )
    .into( imageViewPlaceholder );

只有在listener的onException方法里返回false,R.drawable.cupcake才會(huì)顯示出來(lái)已卸。

13累澡、設(shè)置取消或恢復(fù)請(qǐng)求

以下兩個(gè)方法是為了保證用戶界面的滑動(dòng)流暢而設(shè)計(jì)的愧哟。當(dāng)在ListView中加載圖片的時(shí)候,如果用戶滑動(dòng)ListView的時(shí)候繼續(xù)加載圖片霞赫,就很有可能造成滑動(dòng)不流暢端衰、卡頓的現(xiàn)象旅东,這是由于Activity需要同時(shí)處理滑動(dòng)事件以及Glide加載圖片抵代。Glide為我們提供了這兩個(gè)方法荤牍,讓我們可以在ListView等滑動(dòng)控件滑動(dòng)的過(guò)程中控制Glide停止加載或繼續(xù)加載参淫,可以有效的保證界面操作的流暢涎才。

//當(dāng)列表在滑動(dòng)的時(shí)候可以調(diào)用pauseRequests()取消請(qǐng)求
Glide.with(context).pauseRequests();
//當(dāng)列表滑動(dòng)停止時(shí)可以調(diào)用resumeRequests()恢復(fù)請(qǐng)求
Glide.with(context).resumeRequests();

// ListView滑動(dòng)時(shí)觸發(fā)的事件
lv.setOnScrollListener(new AbsListView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        switch (scrollState) {
            case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
            case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                // 當(dāng)ListView處于滑動(dòng)狀態(tài)時(shí)耍铜,停止加載圖片棕兼,保證操作界面流暢
                Glide.with(MainActivity.this).pauseRequests();
                break;
            case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                // 當(dāng)ListView處于靜止?fàn)顟B(tài)時(shí)伴挚,繼續(xù)加載圖片
                Glide.with(MainActivity.this).resumeRequests();
                break;
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    }
});

14颅眶、獲取緩存大小

new GetDiskCacheSizeTask(textView).execute(new File(getCacheDir(), DiskCache.Factory.DEFAULT_DISK_CACHE_DIR));

private class GetDiskCacheSizeTask extends AsyncTask<File, Long, Long> {

    private final TextView resultView;

    public GetDiskCacheSizeTask(TextView resultView) {
        this.resultView = resultView;
    }

    @Override
    protected void onPreExecute() {
        resultView.setText("Calculating...");
    }

    @Override
    protected void onProgressUpdate(Long... values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected Long doInBackground(File... dirs) {
        try {
            long totalSize = 0;
            for (File dir : dirs) {
                publishProgress(totalSize);
                totalSize += calculateSize(dir);
            }
            return totalSize;
        } catch (RuntimeException ex) {
            final String message = String.format("Cannot get size of %s: %s", Arrays.toString(dirs), ex);
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    resultView.setText("error");
                    Toast.makeText(resultView.getContext(), message, Toast.LENGTH_LONG).show();
                }
            });
        }
        return 0L;
    }

    @Override
    protected void onPostExecute(Long size) {
        String sizeText = android.text.format.Formatter.formatFileSize(resultView.getContext(), size);
        resultView.setText(sizeText);
    }

    private long calculateSize(File dir) {
        if (dir == null) return 0;
        if (!dir.isDirectory()) return dir.length();
        long result = 0;
        File[] children = dir.listFiles();
        if (children != null)
            for (File child : children)
                result += calculateSize(child);
        return result;
    }
}

15涛酗、清除內(nèi)存緩存

//可以在UI主線程中進(jìn)行
Glide.get(this).clearMemory();

16肃续、清除磁盤(pán)緩存

//需要在子線程中執(zhí)行
Glide.get(this).clearDiskCache();

17饿凛、圖片裁剪卵洗、模糊忌怎、濾鏡等處理

變換

在圖片顯示出之前可以對(duì)圖片進(jìn)行變換處理榴啸。例如鸥印,如果你的app需要顯示一張灰度圖库说,但只能獲取到一個(gè)原始全色彩的版本潜的,你可以使用一個(gè)變換去將圖片從有明艷色彩的版本轉(zhuǎn)換成慘淡的黑白版啰挪。不要誤會(huì)我們亡呵,變換不僅限于顏色锰什。你可以改變圖片的很多屬性:大小汁胆、邊框沦泌、色彩、像素點(diǎn),等等回挽!在之前介紹用Glide調(diào)整圖片大小時(shí)千劈,已經(jīng)介紹了自帶的兩個(gè)
變換fitCenter和 centerCrop墙牌。這兩個(gè)方案都有一個(gè)顯著的特征喜滨,他們有他們自己的Glide轉(zhuǎn)換方法虽风,所以辜膝,這篇文章不再介紹了厂抖。

實(shí)現(xiàn)自己的變換

為了實(shí)現(xiàn)你自己自定義的變換忱辅,你需要?jiǎng)?chuàng)建一個(gè)新的類去實(shí)現(xiàn)變換接口耕蝉。這個(gè)方法需要實(shí)現(xiàn)的內(nèi)容還是相當(dāng)復(fù)雜的垒在,你需要深入探索Glide的內(nèi)部結(jié)構(gòu)才能讓其工作好场躯。如果你只是想要常規(guī)的圖片(不包括Gif和視頻)變換踢关,我們建議只要處理抽象的BitmapTransformation類签舞。它簡(jiǎn)化了相當(dāng)多的實(shí)現(xiàn),能覆蓋95%的使用范圍吠架。

所以傍药,讓我們先看一下BitmapTransformation實(shí)現(xiàn)的一個(gè)例子拐辽。用Renderscript去模糊圖片俱诸。我們可以用之前用過(guò)的代碼去實(shí)現(xiàn)一個(gè)Glide變換乙埃。我們的框架必須繼承BitmapTransformation類:

public class BlurTransformation extends BitmapTransformation {

    public BlurTransformation(Context context) {
        super( context );
    }

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        return null; // todo
    }

    @Override
    public String getId() {
        return null; // todo
    }
}

現(xiàn)在介袜,我們用前面文章的代碼出吹,借助Renderscript來(lái)實(shí)現(xiàn)圖片的模糊處理捶牢。

public class BlurTransformation extends BitmapTransformation {

    private RenderScript rs;

    public BlurTransformation(Context context) {
        super( context );

        rs = RenderScript.create( context );
    }

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        Bitmap blurredBitmap = toTransform.copy( Bitmap.Config.ARGB_8888, true );

        // Allocate memory for Renderscript to work with
        Allocation input = Allocation.createFromBitmap(
            rs, 
            blurredBitmap, 
            Allocation.MipmapControl.MIPMAP_FULL, 
            Allocation.USAGE_SHARED
        );
        Allocation output = Allocation.createTyped(rs, input.getType());

        // Load up an instance of the specific script that we want to use.
        ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        script.setInput(input);

        // Set the blur radius
        script.setRadius(10);

        // Start the ScriptIntrinisicBlur
        script.forEach(output);

        // Copy the output to the blurred bitmap
        output.copyTo(blurredBitmap);

        toTransform.recycle();

        return blurredBitmap;
    }

    @Override
    public String getId() {
        return "blur";
    }
}

getId()方法為這個(gè)變換描述了一個(gè)獨(dú)有的識(shí)別渐排。Glide使用那個(gè)關(guān)鍵字作為緩存系統(tǒng)的一部分驯耻。防止出現(xiàn)異常問(wèn)題可缚,確保其唯一帘靡。

應(yīng)用一個(gè)簡(jiǎn)單的變換

Glide有兩個(gè)不同的方式進(jìn)行變換描姚。第一個(gè)是傳遞一個(gè)你的類的實(shí)例作為.transform()的參數(shù)。不管是圖片還是gif谒主,都可以進(jìn)行變換。另一個(gè)則是使用.bitmapTransform()榛斯,
它只接受bitmap的變換驮俗。由于我們的實(shí)現(xiàn)都是基于bitmap王凑,我們可以使用第一個(gè):

Glide.with(context)
    .load(eatFoodyImages[0])
    .transform(new BlurTransformation(context))
    //.bitmapTransform(new BlurTransformation(context)) // this would work too!
    .into(imageView1);

這足夠讓Glide從網(wǎng)絡(luò)下載的圖片自動(dòng)實(shí)現(xiàn)模糊算法索烹。非常有用百姓!

實(shí)現(xiàn)多重變換

通常垒拢,Glide的流接口(fluent interface)允許方法被連接在一起火惊,然而變換并不是這樣的屹耐。確保你只調(diào)用.transform()或者.bitmapTransform()一次张症,不然俗他,之前的設(shè)置將會(huì)被覆蓋兆衅!然而,你可以通過(guò)傳遞多個(gè)轉(zhuǎn)換對(duì)象當(dāng)作參數(shù)到.transform()(或者.bitmapTransform())中來(lái)進(jìn)行多重變換:

Glide.with(context)
    .load(eatFoodyImages[1])
    .transform(new GreyscaleTransformation(context), new BlurTransformation(context))
    .into(imageView2);

在這段代碼中危融,我們先對(duì)圖片進(jìn)行了灰度變換吉殃,然后模糊處理蛋勺。Glide會(huì)為你自動(dòng)進(jìn)行兩個(gè)轉(zhuǎn)換抱完。牛逼吧巧娱!

提示:當(dāng)你使用變換的時(shí)候禁添,你不能使用.centerCrop()或者.fitCenter()庙楚。

Glide的變換集合

如果你已經(jīng)對(duì)你的app里要用什么變換有了想法馒闷,在花點(diǎn)時(shí)間看看下面的庫(kù)吧:Glide-transformations(https://github.com/wasabeef/glide-transformations)纳账。它提供了許多變換的集合疏虫。值得去看一下你的idea是否已經(jīng)被實(shí)現(xiàn)了卧秘。

這個(gè)庫(kù)有2個(gè)不同版本翅敌。擴(kuò)展庫(kù)包括更多的變換蚯涮,并且是用手機(jī)的GPU進(jìn)行計(jì)算。需要一個(gè)額外的依賴泪蔫,所以這兩個(gè)版本的設(shè)置還有點(diǎn)不一樣撩荣。你應(yīng)當(dāng)看看支持的變換的列表婿滓,再?zèng)Q定你需要用哪個(gè)版本。

Glide變換的設(shè)置

設(shè)置是很簡(jiǎn)單的橘券!對(duì)于基本版旁舰,你可以在你的build.gradle里加一行:

dependencies {  
    compile 'jp.wasabeef:glide-transformations:2.0.0'
}

如果你想要使用GPU變換:

repositories {  
    jcenter()
    mavenCentral()
}

dependencies {  
    compile 'jp.wasabeef:glide-transformations:2.0.0'
    compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0'
}

Glide變換的使用

在你同步了Android Studio的builde.gradle文件后毯焕,你已經(jīng)可以進(jìn)行使用變換集合了磺樱。使用方式與使用自定義變換一樣。假如我們要用glide變換集合去模糊圖片:

Glide  
    .with( context )
    .load( eatFoodyImages[2] )
    .bitmapTransform( new jp.wasabeef.glide.transformations.BlurTransformation( context, 25 ) )
    .into( imageView3 );

你也可以像上面一樣應(yīng)用一組變換芜辕。一個(gè)單獨(dú)的變換或者一組變換侵续,.bitmapTransform()都可以接受状蜗!

示例:圓角處理

 Glide.with(mContext)
    .load(R.drawable.image_example)
    .bitmapTransform(new RoundedCornersTransformation(mContext, 30, 0, RoundedCornersTransformation.CornerType.BOTTOM))
    .into(imageView);

可實(shí)現(xiàn)Transformation接口轧坎,進(jìn)行更靈活的圖片處理眶根,如進(jìn)行簡(jiǎn)單地圓角處理属百。

public class RoundedCornersTransformation implements Transformation<Bitmap> {

    private BitmapPool mBitmapPool;
    private int mRadius;

    public RoundedCornersTransformation(Context context, int mRadius) {
        this(Glide.get(context).getBitmapPool(), mRadius);
    }

    public RoundedCornersTransformation(BitmapPool mBitmapPool, int mRadius) {
        this.mBitmapPool = mBitmapPool;
        this.mRadius = mRadius;
    }

    @Override
    public Resource<Bitmap> transform(Resource<Bitmap> resource, int outWidth, int outHeight) {
        //從其包裝類中拿出Bitmap
        Bitmap source = resource.get();
        int width = source.getWidth();
        int height = source.getHeight();
        Bitmap result = mBitmapPool.get(width, height, Bitmap.Config.ARGB_8888);
        if (result == null) {
            result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        }
        Canvas canvas = new Canvas(result);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
        canvas.drawRoundRect(new RectF(0, 0, width, height), mRadius, mRadius, paint);
        //返回包裝成Resource的最終Bitmap
        return BitmapResource.obtain(result, mBitmapPool);
    }

    @Override
    public String getId() {
        return "com.wiggins.glide.widget.GlideCircleTransform(radius=" + mRadius + ")";
    }
}

自定義圖片處理時(shí)為了避免創(chuàng)建大量Bitmap以及減少GC厌丑,可以考慮重用Bitmap怒竿,這就需要使用BitmapPool耕驰,例如從Bitmap池中取一個(gè)Bitmap朦肘,用這個(gè)Bitmap生成一個(gè)Canvas,然后在這個(gè)Canvas上畫(huà)初始的Bitmap并使用Matrix媒抠、Paint或者Shader處理這張圖片趴生。為了有效并正確重用Bitmap需要遵循以下三條準(zhǔn)則:

  • 永遠(yuǎn)不要把transform()傳給你的原始resource或原始Bitmap給recycle()了苍匆,更不要放回BitmapPool锉桑,因?yàn)檫@些都自動(dòng)完成了民轴。值得注意的是后裸,任何從BitmapPool取出的用于自定義圖片變換的輔助Bitmap微驶,如果不經(jīng)過(guò)transform()方法返回因苹,就必須主動(dòng)放回BitmapPool或者調(diào)用recycle()回收扶檐。
  • 如果你從BitmapPool拿出多個(gè)Bitmap或不使用你從BitmapPool拿出的一個(gè)Bitmap款筑,一定要返回extras給BitmapPool奈梳。
  • 如果你的圖片處理沒(méi)有替換原始resource(例如由于一張圖片已經(jīng)匹配了你想要的尺寸攘须,你需要提前返回)于宙,transform()方法就返回原始resource或原始Bitmap限煞。例如:
private static class MyTransformation extends BitmapTransformation {
    public MyTransformation(Context context) {
        super(context);
    }

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        Bitmap result = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
        // 如果BitmapPool中找不到符合該條件的Bitmap署驻,get()方法會(huì)返回null旺上,就需要我們自己創(chuàng)建Bitmap了
        if (result == null) {
            // 如果想讓Bitmap支持透明度宣吱,就需要使用ARGB_8888
            result = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888);
        }
        //創(chuàng)建最終Bitmap的Canvas.
        Canvas canvas = new Canvas(result);
        Paint paint = new Paint();
        paint.setAlpha(128);
        // 將原始Bitmap處理后畫(huà)到最終Bitmap中
        canvas.drawBitmap(toTransform, 0, 0, paint);
        // 由于我們的圖片處理替換了原始Bitmap征候,就return我們新的Bitmap就行疤坝。
        // Glide會(huì)自動(dòng)幫我們回收原始Bitmap跑揉。
        return result;
    }

    @Override
     public String getId() {
         // Return some id that uniquely identifies your transformation.
         return "com.wiggins.glide.MyTransformation";
     }
}

七历谍、GlideModule使用

GlideModule是一個(gè)接口,全局改變Glide行為的一種方式印蔬,通過(guò)全局GlideModule配置Glide(GlideModule#applyOptions)扛点,用GlideBuilder設(shè)置選項(xiàng)陵究,用Glide注冊(cè)ModelLoader(GlideModule#registerComponents)等铜邮。你需要?jiǎng)?chuàng)建Glide的實(shí)例松蒜,來(lái)訪問(wèn)GlideBuilder秸苗≡送剩可以通過(guò)創(chuàng)建一個(gè)公共的類秸讹,實(shí)現(xiàn)GlideModule的接口來(lái)定制Glide弧可。所有的GlideModule實(shí)現(xiàn)類必須是public的劣欢,并且只擁有一個(gè)空的構(gòu)造器凿将,以便在Glide延遲初始化時(shí)丸相,可以通過(guò)反射將它們實(shí)例化。

1座硕、自定義一個(gè)GlideModule

public class MyGlideModule implements GlideModule {

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // Apply options to the builder here.
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        // register ModelLoaders here.
    }
}

2华匾、AndroidManifest.xml注冊(cè)

<meta-data
    android:name="com.wiggins.glide.MyGlideModule"
    android:value="GlideModule" />//value是固定的

3蜘拉、混淆處理

-keepnames class com.wiggins.glide.MyGlideModule
# or more generally
#-keep public class * implements com.bumptech.glide.module.GlideModule

4旭旭、多個(gè)GlideModule沖突問(wèn)題

GlideModule不能指定調(diào)用順序持寄,所以應(yīng)該避免不同的GlideModule之間有沖突的選項(xiàng)設(shè)置稍味,可以考慮將所有的設(shè)置都放到一個(gè)GlideModule里面模庐,或者排除掉某個(gè)manifest文件的某個(gè)Module赖欣。

<meta-data
    android:name="com.wiggins.glide.MyGlideModule"
    tools:node="remove" />

5、更改Glide配置

已經(jīng)知道如何使用Glide module去自定義Glide》嘌Γ現(xiàn)在我們看一下接口的第一個(gè)方法:applyOptions(Context context, GlideBuilder builder)违寿。這個(gè)方法將GlideBuilder的對(duì)象當(dāng)作參數(shù)藤巢,并且是void返回類型掂咒,所以你在這個(gè)方法里能調(diào)用GlideBuilder可以用的方法。

.setMemoryCache(MemoryCache memoryCache)
.setBitmapPool(BitmapPool bitmapPool)
.setDiskCache(DiskCache.Factory diskCacheFactory)
.setDiskCacheService(ExecutorService service)
.setResizeService(ExecutorService service)
.setDecodeFormat(DecodeFormat decodeFormat)

顯而易見(jiàn)温圆,GlideBuilder對(duì)象可以讓你訪問(wèn)到Glide的核心部分岁歉。使用文中的方法锅移,你可以改變磁盤(pán)緩存非剃、內(nèi)存緩存等等努潘。

5.1 設(shè)置Glide內(nèi)存緩存大小

MemoryCache用來(lái)把resources緩存在內(nèi)存里疯坤,以便能馬上能拿出來(lái)顯示压怠。默認(rèn)情況下Glide使用LruResourceCache菌瘫,我們可以通過(guò)它的構(gòu)造器設(shè)置最大緩存內(nèi)存大小。

//獲取系統(tǒng)分配給應(yīng)用的總內(nèi)存大小
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//設(shè)置圖片內(nèi)存緩存占用八分之一
int memoryCacheSize = maxMemory / 8;
//設(shè)置內(nèi)存緩存大小
builder.setMemoryCache(new LruResourceCache(memoryCacheSize));

獲取默認(rèn)的使用內(nèi)存

//MemoryCache和BitmapPool的默認(rèn)大小由MemorySizeCalculator類決定栖忠,MemorySizeCalculator會(huì)根據(jù)給定屏幕大小可用內(nèi)存算出合適的緩存大小庵寞,
這也是推薦的緩存大小捐川,我們可以根據(jù)這個(gè)推薦大小做出調(diào)整
MemorySizeCalculator calculator = new MemorySizeCalculator(context);
int defaultMemoryCacheSize = calculator.getMemoryCacheSize();
int defaultBitmapPoolSize = calculator.getBitmapPoolSize();
5.2 設(shè)置Glide磁盤(pán)緩存大小
//方式一
//指定的是數(shù)據(jù)的緩存地址
File cacheDir = context.getExternalCacheDir();
//最多可以緩存多少字節(jié)的數(shù)據(jù)
int diskCacheSize = 1024 * 1024 * 30;
//設(shè)置磁盤(pán)緩存大小
builder.setDiskCache(new DiskLruCacheFactory(cacheDir.getPath(), "glide", diskCacheSize));
//方式二
//存放在data/data/xxxx/cache/
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "glide", diskCacheSize));
//方式三
//存放在外置文件
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, "glide", diskCacheSize));
5.3 設(shè)置圖片解碼格式

默認(rèn)格式RGB_565相對(duì)于ARGB_8888的4字節(jié)/像素可以節(jié)省一半的內(nèi)存古沥,但是圖片質(zhì)量就沒(méi)那么高了尊浓,而且不支持透明度纯衍。

builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
5.4 設(shè)置BitmapPool緩存內(nèi)存大小

Bitmap池用來(lái)允許不同尺寸的Bitmap被重用瓦堵,這可以顯著地減少因?yàn)閳D片解碼像素?cái)?shù)組分配內(nèi)存而引發(fā)的垃圾回收歌亲。默認(rèn)情況下Glide使用LruBitmapPool作為Bitmap池惋鸥,LruBitmapPool采用Lru算法保存最近使用的尺寸的Bitmap悍缠,我們可以通過(guò)它的構(gòu)造器設(shè)置最大緩存內(nèi)存大小滤港。

builder.setBitmapPool(new LruBitmapPool(memoryCacheSize));
5.5 設(shè)置用來(lái)檢索cache中沒(méi)有的Resource的ExecutorService
//為了使縮略圖請(qǐng)求正確工作溅漾,實(shí)現(xiàn)類必須把請(qǐng)求根據(jù)Priority優(yōu)先級(jí)排好序
builder.setDiskCacheService(ExecutorService service);
builder.setResizeService(ExecutorService service);

6添履、集成網(wǎng)絡(luò)框架

Glide包含一些小的暮胧、可選的集成庫(kù)瞎饲,目前Glide集成庫(kù)當(dāng)中包含了訪問(wèn)網(wǎng)絡(luò)操作的Volley和OkHttp妄田,也可以通過(guò)Glide的ModelLoader接口自己寫(xiě)網(wǎng)絡(luò)請(qǐng)求。

6.1 將OkHttp集成到Glide當(dāng)中

a)添加依賴

dependencies {
    //OkHttp 2.x
    compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
    compile 'com.squareup.okhttp:okhttp:2.7.5'

    //OkHttp 3.x
    compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
    compile 'com.squareup.okhttp3:okhttp:3.2.0'
}

結(jié)尾的@aar可以將庫(kù)中的AndroidManifest.xml文件一起導(dǎo)出,Gradle自動(dòng)合并必要的GlideModule到AndroidManifest.xml启具,然后使用所集成的網(wǎng)絡(luò)連接拷沸,所以不用再
將以下文本添加到項(xiàng)目的AndroidManifest.xml文件中:

<meta-data
    android:name="com.bumptech.glide.integration.okhttp.OkHttpGlideModule"
    android:value="GlideModule" />

b)創(chuàng)建OkHttp集成庫(kù)的GlideModule

<meta-data
    android:name="com.wiggins.glide.okhttp.OkHttpGlideModule"
    android:value="GlideModule" />

c)混淆配置

-keep class com.wiggins.glide.okhttp.OkHttpGlideModule
#or
-keep public class * implements com.bumptech.glide.module.GlideModule

注意:
a.OkHttp 2.x和OkHttp 3.x需使用不同的集成庫(kù)撞芍;
b.Gradle會(huì)自動(dòng)將OkHttpGlideModule合并到應(yīng)用的manifest文件中序无;
c.如果你沒(méi)有對(duì)所有的GlideModule配置混淆規(guī)則(即沒(méi)有使用-keep public class * implements com.bumptech.glide.module.GlideModule)帝嗡,則需要把OkHttp的GlideModule進(jìn)行混淆配置:-keep class com.wiggins.glide.okhttp.OkHttpGlideModule丈探。

6.2 將Volley集成到Glide當(dāng)中

a)添加依賴

dependencies {
    compile 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
    compile 'com.mcxiaoke.volley:library:1.0.8'
}

b)創(chuàng)建Volley集成庫(kù)的GlideModule

<meta-data
    android:name="com.wiggins.glide.volley.VolleyGlideModule"
    android:value="GlideModule" />

c)混淆配置

-keep class com.wiggins.glide.volley.VolleyGlideModule
#or
-keep public class * implements com.bumptech.glide.module.GlideModule

7、替換Glide組件拔莱、使用ModelLoader自定義數(shù)據(jù)源

7.1碗降、替換Glide組件

替換Glide組件功能需要在自定義模塊的registerComponents()方法中加入具體的替換邏輯。相比于更改Glide配置塘秦,替換Glide組件這個(gè)功能的難度就明顯大了不少讼渊。Glide中的組件非常繁多尊剔,也非常復(fù)雜挨稿,但其實(shí)大多數(shù)情況下并不需要我們?nèi)プ鍪裁刺鎿Q。不過(guò)疲陕,有一個(gè)組件卻有著比較大的替換需求,那就是Glide的HTTP通訊組件诅岩。

替換Glide組件功能需要在自定義模塊的GlideModule#registerComponents(Context context, Glide glide)方法中加入具體的替換邏輯,需要在方法中調(diào)用Glide#register(Class<T> modelClass, Class<Y> resourceClass, ModelLoaderFactory<T, Y> factory),其中modelClass表示 數(shù)據(jù)模型的類型懒棉,一般為GlideUrl
,Glide.with(context).load("url")底層就是將轉(zhuǎn)化為了GlideUrl;resourceClass表示URL所指向的資源的類型倔韭,一般為InputStream。

默認(rèn)情況下,Glide使用的是基于原生HttpURLConnection進(jìn)行訂制的HTTP通訊組件秧荆,但是現(xiàn)在大多數(shù)的Android開(kāi)發(fā)者都更喜歡使用OkHttp,因此將Glide中的HTTP通訊組件修改成OkHttp的這個(gè)需求比較常見(jiàn),那么今天我們也會(huì)以這個(gè)功能來(lái)作為例子進(jìn)行講解。

Model:數(shù)據(jù)模型梧疲,一般為URL字符串
Resource:URL所指向的網(wǎng)絡(luò)資源

它主要和三個(gè)接口有關(guān):

ModelLoader:數(shù)據(jù)模型Loader,將任意復(fù)雜的數(shù)據(jù)模型轉(zhuǎn)化為具體的可被DataFetcher使用的數(shù)據(jù)類型该互。需要返回一個(gè)從url拉取數(shù)據(jù)的DataFetcher胰丁,泛型類型為上面指定的類型机蔗。

public interface ModelLoader<T, Y> {
    DataFetcher<Y> getResourceFetcher(T var1, int var2, int var3);
}

ModelLoaderFactory:ModelLoader的工廠幔嗦,build方法返回ModelLoader嬉挡。

public interface ModelLoaderFactory<T, Y> {
    ModelLoader<T, Y> build(Context var1, GenericLoaderFactory var2);

    void teardown();
}

DataFetcher:獲取 由model表示的resource要解碼的數(shù)據(jù)

public interface DataFetcher<T> {
    T loadData(Priority var1) throws Exception;  //重要方法因谎,返回給glide的數(shù)據(jù)

    void cleanup();

    String getId();

    void cancel();
}

默認(rèn)地河爹,Glide內(nèi)部使用標(biāo)準(zhǔn)的HTTPUrlConnection去下載圖片。Glide也提供兩個(gè)集成庫(kù)。這三個(gè)方法優(yōu)點(diǎn)是在安全設(shè)置上都是相當(dāng)嚴(yán)格的侄刽。唯一的不足之處是當(dāng)你從一個(gè)使用HTTPS杂彭,還是self-signed的服務(wù)器下載圖片時(shí),Glide并不會(huì)下載或者顯示圖片醉箕,因?yàn)閟elf-signed認(rèn)證會(huì)被認(rèn)為存在安全問(wèn)題姻报。

首先創(chuàng)建一個(gè)跳過(guò)SSL認(rèn)證的OkHttpClient
public class UnsafeOkHttpClient {
    public static OkHttpClient getUnsafeOkHttpClient() {
        try {
            // Create a trust manager that does not validate certificate chains
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new X509Certificate[0];
                            //return null;//刪除這行,多謝下面評(píng)論的幾位小伙伴指出空指針問(wèn)題,并提供解決方案。
                        }
                    }
            };

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

            // Create an ssl socket factory with our all-trusting manager
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            OkHttpClient okHttpClient = new OkHttpClient();
            okHttpClient.setSslSocketFactory(sslSocketFactory);
            okHttpClient.setProtocols(Arrays.asList(Protocol.HTTP_1_1));
            okHttpClient.setHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            return okHttpClient;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

創(chuàng)建的OkHttpClient關(guān)閉了所有的SSL認(rèn)證檢查荞驴。

集成到 Glide

Glide的OkHTTP集成庫(kù)做的都是一樣的工作,所以我們可以跟隨他們的步驟悲雳。首先透典,我們需要在GlideModule里聲明我們的定制税弃。你應(yīng)該想到西壮,我們需要在registerComponents()方法里做適配。我們可以調(diào)用.register()方法去交換Glide基礎(chǔ)構(gòu)成。Glide使用一個(gè)ModelLoader去鏈接到數(shù)據(jù)模型創(chuàng)建一個(gè)具體的數(shù)據(jù)類型。我們的例子中,我們需要?jiǎng)?chuàng)建一個(gè)ModelLoader铣除,它連接到一個(gè)URL敲长,通過(guò)GlideUrl類響應(yīng)并轉(zhuǎn)化為輸入流泽铛。Glide需要能夠創(chuàng)建我們的新ModelLoader的實(shí)例弛随,所以我們?cè)?register()方法中傳入一個(gè)工廠:

public class UnsafeOkHttpGlideModule implements GlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder glideBuilder) {

    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
    }
}

public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {

    @Override
    public DataFetcher<InputStream> getResourceFetcher(GlideUrl glideUrl, int i, int i1) {
        return new OkHttpStreamFetcher(client, glideUrl);
    }

    private final OkHttpClient client;

    public OkHttpUrlLoader(OkHttpClient client) {
        this.client = client;
    }

    /**
     * The default factory for {@link OkHttpUrlLoader}s.
     */
    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
        private static volatile OkHttpClient internalClient;
        private OkHttpClient client;

        private static OkHttpClient getInternalClient() {
            if (internalClient == null) {
                synchronized (Factory.class) {
                    if (internalClient == null) {
                        internalClient = UnsafeOkHttpClient.getUnsafeOkHttpClient();
                    }
                }
            }
            return internalClient;
        }

        /**
         * Constructor for a new Factory that runs requests using a static singleton client.
         */
        public Factory() {
            this(getInternalClient());
        }

        /**
         * Constructor for a new Factory that runs requests using given client.
         */
        public Factory(OkHttpClient client) {
            this.client = client;
        }

        @Override
        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new OkHttpUrlLoader(client);
        }

        @Override
        public void teardown() {
            // Do nothing, this instance doesn't own the client.
        }
    }
}

public class OkHttpStreamFetcher implements DataFetcher<InputStream> {
    private final OkHttpClient client;
    private final GlideUrl url;
    private InputStream stream;
    private ResponseBody responseBody;

    public OkHttpStreamFetcher(OkHttpClient client, GlideUrl url) {
        this.client = client;
        this.url = url;
    }

    @Override
    public InputStream loadData(Priority priority) throws Exception {
        Request.Builder requestBuilder = new Request.Builder()
                .url(url.toStringUrl());

        for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
            String key = headerEntry.getKey();
            requestBuilder.addHeader(key, headerEntry.getValue());
        }

        Request request = requestBuilder.build();

        Response response = client.newCall(request).execute();
        responseBody = response.body();
        if (!response.isSuccessful()) {
            throw new IOException("Request failed with code: " + response.code());
        }

        long contentLength = responseBody.contentLength();
        stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
        return stream;
    }

    @Override
    public void cleanup() {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException e) {
                // Ignored
            }
        }
        if (responseBody != null) {
            responseBody.close();
        }
    }

    @Override
    public String getId() {
        return url.getCacheKey();
    }

    @Override
    public void cancel() {

    }
}
7.2链烈、使用ModelLoader自定義數(shù)據(jù)源

如果需要根據(jù)不同的要求請(qǐng)求不同尺寸不同質(zhì)量的圖片码荔,這時(shí)我們就可以使用自定義數(shù)據(jù)源。

a)定義處理URL接口

public interface IDataModel {
    String buildDataModelUrl(int width, int height);
}

b)實(shí)現(xiàn)不同的處理URL接口

public class JpgDataModel implements IDataModel {

    private String dataModelUrl;

    public JpgDataModel(String dataModelUrl) {
        this.dataModelUrl = dataModelUrl;
    }

    @Override
    public String buildDataModelUrl(int width, int height) {
        //http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg?imageView2/1/w/200/h/200/format/jpg
        return String.format("%s?imageView2/1/w/%d/h/%d/format/jpg", dataModelUrl, width, height);
    }
}

c)實(shí)現(xiàn)ModelLoader

public class MyDataLoader extends BaseGlideUrlLoader<IDataModel> {

    public MyDataLoader(Context context) {
        super(context);
    }

    public MyDataLoader(ModelLoader<GlideUrl, InputStream> urlLoader) {
        super(urlLoader, null);
    }

    @Override
    protected String getUrl(IDataModel model, int width, int height) {
        return model.buildDataModelUrl(width, height);
    }

    public static class Factory implements ModelLoaderFactory<IDataModel, InputStream> {

        @Override
        public ModelLoader<IDataModel, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new MyDataLoader(factories.buildModelLoader(GlideUrl.class, InputStream.class));
        }

        @Override
        public void teardown() {
        }
    }
}

d)根據(jù)不同的要求采用不同的策略加載圖片

//加載jpg圖片
Glide.with(this).using(new MyDataLoader(this)).load(new JpgDataModel(imageUrl)).into(imageView);

e)如何跳過(guò).using()

public class MyGlideModule implements GlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {

    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        glide.register(IDataModel.class, InputStream.class, new MyDataLoader.Factory());
    }
}

//加載jpg圖片
Glide.with(this).load(new JpgDataModel(imageUrl)).into(imageView);

八媒峡、特點(diǎn)

使用簡(jiǎn)單酬滤;
可配置度及自適應(yīng)程度高;
支持常見(jiàn)圖片格式如jpg蔬墩、png、gif、webp等;
支持多種數(shù)據(jù)源如網(wǎng)絡(luò)、本地、資源、Uri等;
高效緩存策略(支持Memory和Disk圖片緩存千绪,默認(rèn)Bitmap格式采用RGB_565內(nèi)存使用至少減少一半)炸茧;
生命周期集成(根據(jù)Context/Activity/Fragment/FragmentActivity生命周期自動(dòng)管理請(qǐng)求)控漠;
高效處理Bitmap(使用BitmapPool使Bitmap復(fù)用,主動(dòng)調(diào)用recycle回收需要回收的Bitmap,減小系統(tǒng)回收壓力)。

九、優(yōu)點(diǎn)

多樣化媒體加載,支持Gif、WebP、Video及縮略圖以等類型厨剪;

生命周期集成,提供多種方式與生命周期綁定,可以更好的讓加載圖片請(qǐng)求的生命周期動(dòng)態(tài)管理起來(lái);

高效的緩存策略

  • 支持Memory和Disk圖片緩存;
  • 緩存相應(yīng)大小的圖片尺寸(Picasso只會(huì)緩存原始尺寸圖片,而Glide會(huì)根據(jù)你ImageView的大小來(lái)緩存相應(yīng)大小的圖片尺寸,因此Glide會(huì)比Picasso加載的速度要快)薯鳍;
  • 內(nèi)存開(kāi)銷(xiāo)星骋邸(Picasso默認(rèn)的是ARGB_8888格式瞪讼,而Glide默認(rèn)的Bitmap格式是RGB_565格式希柿,這個(gè)內(nèi)存開(kāi)銷(xiāo)大約可以減小一半)挤悉。

十、缺點(diǎn)

使用方法復(fù)雜:由于Glide功能強(qiáng)大畏梆,所以使用的方法非常多捏卓,其源碼也相對(duì)的復(fù)雜蒜田;
包較大:Glide(v3.7.0)的大小約465kb梯捕。

十一锣笨、使用場(chǎng)景

需要更多的內(nèi)容表現(xiàn)形式(如Gif、縮略圖等)判哥;
更高的性能要求(緩存章钾、加載速度等)府寒。

十二祷蝌、特別說(shuō)明

1.ImageView的setTag問(wèn)題

問(wèn)題描述:如果使用Glide的into(imageView)為ImageView設(shè)置圖片的同時(shí)使用ImageView的setTag(final Object tag)方法糊啡,將會(huì)導(dǎo)致java.lang.IllegalArgumentException: You must not call setTag() on a view Glide is targeting異常稍算。因?yàn)镚lide的ViewTarget中通過(guò)view.setTag(tag)和view.getTag()標(biāo)記請(qǐng)求的褥紫,由于Android 4.0之前Tag存儲(chǔ)在靜態(tài)map里,如果Glide使用setTag(int key, final Object tag)方法標(biāo)記請(qǐng)求則可能會(huì)導(dǎo)致內(nèi)存泄露,所以Glide默認(rèn)使用view.setTag(tag)標(biāo)記請(qǐng)求竣灌,你就不能重復(fù)調(diào)用了坷随。

解決辦法:如果你需要為ImageView設(shè)置Tag类溢,必須使用setTag(int key, final Object tag)及getTag(int key)方法计贰,其中key必須是合法的資源id以確保key
的唯一性,典型做法就是在資源文件中聲明type="id"的item資源呛踊。

2.placeholder()導(dǎo)致的圖片變形問(wèn)題

問(wèn)題描述:使用.placeholder()方法在某些情況下會(huì)導(dǎo)致圖片顯示的時(shí)候出現(xiàn)圖片變形的情況。這是因?yàn)镚lide默認(rèn)開(kāi)啟的crossFade動(dòng)畫(huà)導(dǎo)致的TransitionDrawable繪制異常层坠,具體描述可以查看https://github.com/bumptech/glide/issues/363磅氨。根本原因就是你的placeholder圖片和你要加載顯示的圖片寬高比不一樣者蠕,而Android的
TransitionDrawable無(wú)法很好地處理不同寬高比的過(guò)渡問(wèn)題,這是Android也是Glide的Bug流济。

解決辦法:使用.dontAnimate()方法禁用過(guò)渡動(dòng)畫(huà),或者使用animate()方法自己寫(xiě)動(dòng)畫(huà)蟋恬,再或者自己修復(fù)TransitionDrawable的問(wèn)題。

3.ImageView的資源回收問(wèn)題

問(wèn)題描述:默認(rèn)情況下Glide會(huì)根據(jù)with()使用的Activity或Fragment的生命周期自動(dòng)調(diào)整資源請(qǐng)求以及資源回收出刷。但是如果有很占內(nèi)存的Fragment或Activity不銷(xiāo)毀而僅僅是隱藏視圖,那么這些圖片資源就沒(méi)辦法及時(shí)回收裸删,即使是GC的時(shí)候。

解決辦法:可以考慮使用WeakReference腹泌,如:

final WeakReference<ImageView> imageViewWeakReference = new WeakReference<>(imageView);
ImageView target = imageViewWeakReference.get();
if (target != null) {
    Glide.with(context).load(uri).into(target);
}

4.由于Bitmap復(fù)用導(dǎo)致的在某些設(shè)備上圖片錯(cuò)亂的問(wèn)題

問(wèn)題描述: Glide默認(rèn)使用BitmapPool的方式對(duì)應(yīng)用中用到的Bitmap進(jìn)行復(fù)用涤躲,以減少頻繁的內(nèi)存申請(qǐng)和內(nèi)存回收瘾蛋,而且默認(rèn)使用的Bitmap模式為RGB565以減少內(nèi)存開(kāi)銷(xiāo)取董。但在某些設(shè)備上(通常在Galaxy系列5.X設(shè)備上很容易復(fù)現(xiàn))某些情況下會(huì)出現(xiàn)圖片加載錯(cuò)亂的問(wèn)題奥洼,具體詳見(jiàn)https://github.com/bumptech/glide/issues/601尉尾。原因初步確定是OpenGL紋理渲染異常吆豹。

解決辦法:GlideModule使用PREFER_ARGB_8888(Glide4.X已經(jīng)默認(rèn)使用該模式了)蘸拔,雖然內(nèi)存占用比RGB565更多一點(diǎn)宝剖,但可以更好地處理有透明度Bitmap的復(fù)用問(wèn)
題铃芦,或者禁用Bitmap復(fù)用setBitmapPool(new BitmapPoolAdapter())來(lái)修復(fù)這個(gè)問(wèn)題(不推薦這種處理方式)征唬。

5.異步線程完成后加載圖片的崩潰問(wèn)題

問(wèn)題描述:通常情況下異步線程會(huì)被約束在Activity生命周期內(nèi)年枕,所以異步線程完成后使用Glide加載圖片是沒(méi)有問(wèn)題的典格。但如果你的異步線程在Activity銷(xiāo)毀時(shí)沒(méi)
有取消掉防嗡,那么異步線程完成后Glide就無(wú)法為一個(gè)已銷(xiāo)毀的Activity加載圖片資源,拋出的異常如下(在with()方法中就進(jìn)行判斷并拋出異常):

java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
    at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:134)
    at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:102)
    at com.bumptech.glide.Glide.with(Glide.java:653)
    at com.frank.glidedemo.TestActivity.onGetDataCompleted(TestActivity.java:23)
    at com.frank.glidedemo.TestActivity.access$000(TestActivity.java:10)
    at com.frank.glidedemo.TestActivity$BackgroundTask.onPostExecute(TestActivity.java:46)
    at com.frank.glidedemo.TestActivity$BackgroundTask.onPostExecute(TestActivity.java:28)
    at android.os.AsyncTask.finish(AsyncTask.java:632)
    at android.os.AsyncTask.access$600(AsyncTask.java:177)
    at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:157)
    at android.app.ActivityThread.main(ActivityThread.java:5356)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
    at dalvik.system.NativeStart.main(Native Method)

解決辦法:正確管理BackgroundThreads(異步線程)台谊,當(dāng)Activity停止或銷(xiāo)毀時(shí)阶冈,停止所有相關(guān)的異步線程及后續(xù)的UI操作瓮钥,或者加載前使用isFinishing()
或isDestroyed()進(jìn)行限制(不建議這種處理方式)跃惫。

參考文獻(xiàn)

圖片加載之Glide使用
Glide入門(mén)教程——1.入門(mén)簡(jiǎn)介

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市昧穿,隨后出現(xiàn)的幾起案子寥枝,更是在濱河造成了極大的恐慌身诺,老刑警劉巖重挑,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件官脓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡涝焙,警方通過(guò)查閱死者的電腦和手機(jī)卑笨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)仑撞,“玉大人赤兴,你說(shuō)我怎么就攤上這事∷硐” “怎么了桶良?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)沮翔。 經(jīng)常有香客問(wèn)我陨帆,道長(zhǎng),這世上最難降的妖魔是什么采蚀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任疲牵,我火速辦了婚禮,結(jié)果婚禮上榆鼠,老公的妹妹穿的比我還像新娘纲爸。我一直安慰自己,他們只是感情好妆够,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布识啦。 她就那樣靜靜地躺著,像睡著了一般责静。 火紅的嫁衣襯著肌膚如雪袁滥。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天灾螃,我揣著相機(jī)與錄音题翻,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嵌赠,可吹牛的內(nèi)容都是我干的塑荒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼姜挺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼齿税!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起炊豪,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凌箕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后词渤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體牵舱,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年缺虐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芜壁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡高氮,死狀恐怖慧妄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剪芍,我是刑警寧澤塞淹,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站紊浩,受9級(jí)特大地震影響窖铡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坊谁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滑臊。 院中可真熱鬧口芍,春花似錦、人聲如沸雇卷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)关划。三九已至小染,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贮折,已是汗流浹背裤翩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留调榄,地道東北人踊赠。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓呵扛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親筐带。 傳聞我的和親對(duì)象是個(gè)殘疾皇子今穿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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