Glide的使用

PREVIEW

  • 导入

    • 使用Project Structure导入
    • 添加依赖导入
  • 正文

    • 简单使用
    • GlideRequests.load()方法
    • 加载GIF
    • GlideRequest的更多方法
    • 缓存处理
    • 优先级设置
    • 使用Target
    • 图片变换
    • 使用动画
    • 使用Module

使用Project Structure导入

  • 打开Project Structure
->Project Structure
  • 添加库依赖

选择Library Dependency

->Library Dependency
  • 选择版本

搜索com.github.bumptech.glide:glide*,选择你要的版本

搜索Glide库

添加依赖导入

如果你不使用以上方式,在build.gradle下添加依赖,具体版本号参考Glide's github

dependencies {
    ...
    implementation 'com.github.bumptech.glide:glide:4.11.0'
}

简单使用

本文使用的Glide是4.11.0版本。Glide v4相比Glide v3拥有新的特性——Generated API

String url="https://w.wallhaven.cc/full/md/wallhaven-md5z28.jpg";

ImageView imageView = findViewById(R.id.imageView);

Glide.with(this)//返回RequestManager以调用RequestManager.load方法,默认为RequestManager<Drawable>
        .load(url)//加载指定url的图片,返回RequestBuilder(在into之前均返回RequestBuilder),默认为RequestBuilder<Drawable>
        .into(imageView);//显示图片的ImageView,返回用于完成本次请求的Target

确保网络权限已申请

  • 使用Generated API时的写法

需要使用Glide v4新的Generated API时,我们需要继承AppGlideModule,但暂时不需要重写其中的方法。之后我们就可以使用GlideApp.with(this)代替Glide.with(this)。而使用Glide.with(this)是无法调用Generated API的

@GlideModule//加上模块注解
public class MyAppGlideModule extends AppGlideModule {

}

GlideApp.with(this)//返回GlideRequests以调用GlideRequests.load方法
        .load(url)//加载指定url的图片,返回GlideRequest(在into之前均返回GlideRequest),默认为GlideRequest<Drawable>
        .into(imageView);//显示图片的ImageView,返回用于完成本次请求的Target

GlideRequests.load()方法

GlideRequests.load()有多种重载,一般使用的是GlideRequests.load(String string),其中传入URL字符串。另有三种常用重载,分别是GlideRequests.load(Integer id)GlideRequests.load(File file)GlideRequests.load(Uri uri)

  • 从资源文件加载
GlideApp.with(this)
    .load(R.drawable.close_filled)
    .into(imageView);
  • 从文件加载
File file = new File(Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_DCIM), "building.jpg");//DCIM目录下的一张图片

GlideApp.with(this)
    .load(file)
    .into(imageView);

确保访问外部存储权限已申请

  • 从URI加载
File file = new File(Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_DCIM), "building.jpg");//DCIM目录下的一张图片

Uri uri=Uri.fromFile(file);

GlideApp.with(this)
    .load(uri)
    .into(imageView);

加载GIF

用法同静态图片

String url = "https://media0.giphy.com/media/3oriObQfEBGIn9qlIA/" +
    "giphy.gif?cid=ecf05e476515e2a51ff992790e3c19c96cbdbd1716e8f0fe&rid=giphy.gif";

GlideApp.with(this)
    .load(url)
    .into(imageView);

GlideRequest的更多方法

  • placeholder(int id)
    占位符,当url未加载完成时显示的图片

  • error(int id)
    当url加载失败时显示的图片

  • fallback(int id)
    当url为null时显示的图片

  • thumbnail(RequestBuilder<TranscodeType> builder)
    以另一张图片作为缩略图

  • thumbnail(float sizeMultiplier)
    以本张图片为缩略图,按长度宽度缩小

  • dontAnimate()
    不显示动画

  • override(int width, int height)
    按(width,height)规则裁剪图片,但不会拉伸图片,长度宽度中较大的不合理值将被调整为较小的合理值,最终显示时依据scaleType可能进行拉伸,但缓存中为裁剪后非拉伸的图片

  • centerCrop()
    设置显示图片的ImageView的scaleType值为CENTER_CROP

  • fitCenter()
    设置显示图片的ImageView的scaleType值为FIT_CENTER(scaleType的默认值就是FIT_CENTER)

  • circleCrop()
    将图片裁剪为圆形,缓存中也是圆形

String url = "https://w.wallhaven.cc/full/md/wallhaven-md5z28.jpg";
String url2 = "https://img.alicdn.com/tps/TB1ld1GNFXXXXXLapXXXXXXXXXX-200-200.png";

GlideRequest<Drawable> thumbnailRequest = GlideApp
    .with(this)
    .load(url2);

ImageView imageView = findViewById(R.id.imageView);
GlideApp.with(this)//返回GlideRequests以调用GlideRequests.load方法
    .load(url)////加载指定url的图片,返回GlideRequest(在into之前均返回GlideRequest),默认为GlideRequest<Drawable>
    .placeholder(R.drawable.help_filled)
    .error(R.drawable.close_filled)
    .fallback(R.drawable.close_filled)
    .thumbnail(thumbnailRequest)
    .thumbnail(0.2f)
    .dontAnimate()
    .override(100,100)
    .centerCrop()
    .fitCenter()
    .circleCrop()
    .into(imageView);//显示图片的ImageView,返回用于完成本次请求的Target

使用Generated API

Generated API是Glide v4的新特性,让我们能够通过编写自定义方法来复用一系列option的流式调用,相当于扩展了Glide的API。Glide将会根据@GlideExtension注解找到所有扩展类,在编译阶段全部合并,并作为Glide API的一部分

  • 添加Glide注解处理器的依赖
dependencies {
    ...
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'//版本与Glide一致
}
  • 新建工具类MyGlideExtension
    Glide4.9.0以后的写法(包括4.9.0)
@GlideExtension//加上扩展注解
public class MyGlideExtension {
    private MyGlideExtension() { }//必须是私有的空的构造方法

    @GlideOption//加上扩展注解
    public static BaseRequestOptions<?> basicPreparation(BaseRequestOptions<?> options) {
        return options
                .placeholder(R.drawable.help_filled)
                .error(R.drawable.close_filled)
                .fallback(R.drawable.close_filled);
    }
}

Glide4.9.0之前的写法

@GlideExtension
public class MyGlideExtension {
    private MyGlideExtension() { }

    @GlideOption
    public static void basicPreparation(RequestOptions options) {
        options
            .placeholder(R.drawable.help_filled)
            .error(R.drawable.close_filled)
            .fallback(R.drawable.close_filled);
    }
}

@GlideExtension注解的类应以工具类的思维编写。这种类应该有一个私有的、空的构造方法,应为final类型,并且仅包含静态方法。被注解的类可以含有静态变量可以引用其他的类或对象。所有的扩展类都会在编译时合并如果构造方法不是私有的、空的或是合并时产生冲突都会产生编译错误。

  • 扩展API的使用
String url = "https://w.wallhaven.cc/full/md/wallhaven-md5z28.jpg";

GlideApp.with(this)
    .load(url)
    .basicPreparation()//完成占位符等一系列方法的调用
    .into(imageView);

缓存处理

Glide默认使用LRU缓存。关于检查缓存的过程,以下内容的一部分是来自Glide中文文档的介绍:

默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:

活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?(例如使用了.transform().circleCrop().override()等方法改变了长宽或像素之后的图片)
数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?(检查原图)

前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。

内存缓存和磁盘缓存设置

GlideApp.with(this)
    .load(url)
    .skipMemoryCache(true)//跳过内存缓存
    .diskCacheStrategy(DiskCacheStrategy.NONE)//不使用磁盘缓存
    .into(imageView);

DiskCacheStrategy参数

Glide v4参数

  • DiskCacheStrategy.NONE
    不缓存

  • DiskCacheStrategy.DATA
    只缓存下载后的原图

  • DiskCacheStrategy.RESOURCE
    只缓存通过各种变换最终显示在界面上的的图片

  • DiskCacheStrategy.ALL
    同时进行DATA和RESOURCE两种缓存,但如果数据来自本地,则只进行RESOURCE缓存

  • DiskCacheStrategy.AUTOMATIC
    如果是远程下载的图片则只缓存DATA,如果是本地加载的图片则只缓存RESOURCE(默认策略)

Glide v3参数

  • DiskCacheStrategy.NONE
    不缓存

  • DiskCacheStrategy.SOURCE
    只缓存下载后的原图

  • DiskCacheStrategy.RESULT
    只缓存通过各种变换最终显示在界面上的的图片(默认策略)

  • DiskCacheStrategy.ALL
    同时进行SOURCE和RESULT两种缓存

仅尝试从缓存中检索图片

GlideApp.with(this)
    .load(url)
    .onlyRetrieveFromCache(true)//若未从缓存中检索到图片则加载失败
    .into(imageView);

清除磁盘缓存

final Context context=getApplicationContext();

new AsyncTask<Void, Void, Void>() {
    @Override
    protected Void doInBackground(Void... params) {
        Glide.get(context).clearDiskCache();//清除磁盘缓存,必须在子线程上调用
        return null;
    }
}.execute(null,null,null);

动态URL处理

当需要向服务器传递一些参数时,我们可能会使用这样的URL:
http://www.placehold.it/500x500?source=feed
http://www.placehold.it/500x500?source=detail
两个URL不同但指向同一张图片,而Glide在缓存时会以URL作为图片缓存的唯一标识。不同的URL将导致多余的缓存。加载第一个URl后,Glide会缓存对应的图片,再加载第二个URL时,Glide会因找不到这个URL对应的缓存而再次发送请求,消耗不必要的网络资源,实际上两个URL都应该指向同一个缓存

  • 解决方案——继承GlideUrl

GlideUrl.getCacheKey()返回的值是Glide对图片进行缓存时使用的唯一标识,我们只需要确保Glide使用的是经过处理得到的不含参数的URL即可,以下GlideUrlWithQueryParameter类来自Future Studio

public class GlideUrlWithQueryParameter extends GlideUrl {
    private String mSourceUrl;

    public GlideUrlWithQueryParameter(String baseUrl, String key, String value) {
        super(buildUrl(baseUrl, key, value));

        mSourceUrl = baseUrl;
    }

    public GlideUrlWithQueryParameter(String baseUrl, Map<String, Object> queryParams) {
        super(buildUrl(baseUrl, queryParams));

        mSourceUrl = baseUrl;
    }

    private static String buildUrl(String baseUrl, String key, String value) {
        StringBuilder stringBuilder = new StringBuilder(baseUrl);

        if (stringBuilder.toString().contains("?")) {
            stringBuilder.append("&");
        }
        else {
            stringBuilder.append("?");
        }

        stringBuilder.append(key);
        stringBuilder.append("=");
        stringBuilder.append(value);

        return stringBuilder.toString();
    }

    private static String buildUrl(String baseUrl, Map<String, Object> queryParams) {
        StringBuilder stringBuilder = new StringBuilder(baseUrl);

        for (Map.Entry<String, Object> entry : queryParams.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            if (stringBuilder.toString().contains("?")) {
                stringBuilder.append("&");
            }
            else {
                stringBuilder.append("?");
            }

            stringBuilder.append(key);
            stringBuilder.append("=");
            stringBuilder.append(value);
        }

        return stringBuilder.toString();
    }

    @Override
    public String getCacheKey() {
        return mSourceUrl;
    }

    @Override
    public String toString() {
        return super.getCacheKey();
    }
}

优先级设置

如果有多张图片请求同时发起,而你希望优先加载其中一张,可以将其请求优先级调高,但Glide不保证实际加载顺序一定依据优先级而定

GlideApp.with(this)
    .load(url)
    .priority(Priority.HIGH)
    .into(imageView);

Priority参数

  • Priority.LOW
  • Priority.NORMAL
  • Priority.HIGH
  • Priority.IMMEDIAT

使用Target

由以上内容可以看出into()方法完成了加载图片到imageView的全部过程,但如果要将图片用于其他控件(比如自定义控件)时就需要借助Target。由于SimpleTargetViewTargetGlide4.8.0已弃用,我们应该在Glide新版本中使用CustomTargetCustomViewTarget分别代替SimpleTargetViewTarget

弃用的原因是在过去使用SimpleTargetViewTarget时继承的Target.onLoadCleared()拥有默认的空实现。用户容易忽略在bitmap回收时清除来自UI的引用,这就有可能导致崩溃等问题

CustomTargetCustomViewTarget分别将onLoadCleared()onResourceCleared()写成了抽象方法,强制用户去实现,为的是尽可能避免以往的问题。以下是来自CustomTarget的API文档的说明:

You MUST implement Target.onLoadCleared(Drawable) and ensure that all references to any resource passed into the target in Target.onResourceReady(Object, Transition) are removed before Target.onLoadCleared(Drawable) completes.Failing to do so can result in graphical corruption, crashes caused by recycled Bitmaps, and other undefined behavior. It is never safe to leave Target.onLoadCleared(Drawable) unimplemented or empty.

  • CustomTarget的使用
String url = "https://w.wallhaven.cc/full/md/wallhaven-md5z28.jpg";

CustomTarget<Bitmap> target = new CustomTarget<Bitmap>() {
    @Override
    public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
        //回调内容
        imageView.setImageBitmap(resource);
    }

    @Override
    public void onLoadCleared(@Nullable Drawable placeholder) {
        //这个方法在target被回收时调用,如果在除了imageView以外的地方引用了imageView中的bitmap,在这里清除引用以避免崩溃
    }
};

GlideApp.with(this)
        .load(url)
        .into(target);
  • CustomViewTarget的使用
String url = "https://w.wallhaven.cc/full/md/wallhaven-md5z28.jpg";

CustomViewTarget<ImageView,Drawable> customViewTarget=new CustomViewTarget<ImageView, Drawable>(imageView) {
    @Override
    protected void onResourceCleared(@Nullable Drawable placeholder) {
        //这个方法在drawable被回收时调用,如果在除了imageView以外的地方引用了imageView中的bitmap,在这里清除引用以避免崩溃
    }

    @Override
    public void onLoadFailed(@Nullable Drawable errorDrawable) {

    }

    @Override
    public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
        //回调内容
        imageView.setImageDrawable(resource);
    }
};

GlideApp.with(this)
        .load(uri)
        .into(customViewTarget);

图片变换

  • 为圆角卡片效果实现抽象类BitmapTransformation
public class CardTransformation extends BitmapTransformation {
    private int radius_px;

    public CardTransformation(int radius_px) {
        this.radius_px = radius_px;
    }

    @Override
    protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
        //尝试从缓存中获取长宽及config与下载图片(toTransform)相同的bitmap
        //若找到则将全部像素置为透明并返回,反之重新分配内存新建bitmap并返回
        Bitmap blankBitmap = pool.get(toTransform.getWidth(), toTransform.getHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(blankBitmap);
        Paint paint = new Paint();
        paint.setShader(new BitmapShader(toTransform, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
        paint.setAntiAlias(true);
        canvas.drawRoundRect(0, 0, toTransform.getWidth(), toTransform.getHeight(),
                radius_px, radius_px, paint);
        return blankBitmap;
    }

    @Override
    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { }
}
  • BitmapTransformation的使用
String url = "https://w.wallhaven.cc/full/md/wallhaven-md5z28.jpg";

Transformation cardTransformation = new CardTransformation(48);//半径像素为48的圆角卡片效果

GlideApp.with(this)
    .asBitmap()//使用BitmapTransformation需要GlideRequest<Bitmap>,而不是默认的GlideRequest<Drawable>
    .load(url)
    .transform(cardTransformation)
    .into(imageView);

使用动画

Glide v4开始已取消RequestBuilder.animate()方法,我们使用GlideRequest.transition()代替

通过XML资源实现动画

  • 在res/anim目录下定义XML动画fade_in_and_fill.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200">
    <alpha
        android:fromAlpha="0"
        android:toAlpha="1" />
    <scale
        android:fromXScale="0.7"
        android:fromYScale="0.7"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1"
        android:toYScale="1" />
</set>
  • GlideRequest.transition()的使用
String url = "https://w.wallhaven.cc/full/md/wallhaven-md5z28.jpg";

Transformation cardTransformation = new CardTransformation(48);

GlideApp.with(this)
    .load(url)
    .transform(cardTransformation)
    .transition(GenericTransitionOptions.with(R.anim.fade_in_and_fill))
    .into(imageView);

通过继承ViewPropertyTransition.Animator实现动画

使用属性动画的方式可用于自定义控件

  • 继承ViewPropertyTransition.Animator
public class EnterAnimator implements ViewPropertyTransition.Animator {
    @Override
    public void animate(View view) {
        AnimatorSet set=new AnimatorSet();
        ObjectAnimator oaAlpha=ObjectAnimator.ofFloat(view,"alpha",0,1);
        ObjectAnimator oaScaleX=ObjectAnimator.ofFloat(view,"scaleX",0.7f,1);
        ObjectAnimator oaScaleY=ObjectAnimator.ofFloat(view,"scaleY",0.7f,1);
        set.playTogether(oaAlpha,oaScaleX,oaScaleY);
        set.setDuration(200);
        set.start();
    }
}
  • GlideRequest.transition()的使用
String url = "https://w.wallhaven.cc/full/md/wallhaven-md5z28.jpg";

ViewPropertyTransition.Animator amin=new EnterAnimator();

GlideApp.with(this)
    .asBitmap()
    .load(url)
    .transform(cardTransformation)
    .transition(GenericTransitionOptions.with(amin))
    .into(imageView);

使用Module

集成OkHttp3

在build.gradle中添加除了基本的OkHttp3依赖以外,还需要添加集成库的依赖,之后Glide会自动使用OkHttp3代替默认的HttpURLConnection

dependencies {
    ...
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
    implementation 'com.squareup.okhttp3:okhttp:4.7.2'
    implementation 'com.github.bumptech.glide:okhttp3-integration:4.11.0'//版本和Glide一致
}

Glide设置

  • 继承AppGlideModule
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        //将解码方式设置为ARGB_8888,但Glide4.11.0中默认解码方式已经是ARGB_8888
        RequestOptions options=new RequestOptions().format(DecodeFormat.PREFER_ARGB_8888);
        builder.setDefaultRequestOptions(options);
    }
}
  • 配置Manifest文件
<application
    ...
    <meta-data
        android:name="包名.MyAppGlideModule"
        android:value="AppGlideModule"/>
</application>
  • 设置内存缓存大小和位图池大小
MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context).build();
int defaultMemoryCacheSize = calculator.getMemoryCacheSize();//单位为字节
int defaultBitmapPoolSize = calculator.getBitmapPoolSize();//单位为字节,默认为内存缓存的一半

int customMemoryCacheSize = (int) (1.2 * defaultMemoryCacheSize);
int customBitmapPoolSize = (int) (1.2 * defaultBitmapPoolSize);

builder.setMemoryCache(new LruResourceCache(customMemoryCacheSize));
builder.setBitmapPool(new LruBitmapPool(customBitmapPoolSize));
  • 设置磁盘缓存大小
int _6MB = 6 * 1024 * 1024;

builder.setDiskCache(new InternalCacheDiskCacheFactory(context, _6MB));//6MB,单位为字节
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258