Glide源码分析之一 with() + into()解析

相关文章

Glide源码分析之一
Glide源码分析之二
Glide源码分析之三

文章基于3.7.0。主要参考郭神的Glide源码解析。

简单使用

      String imgUrl = "https://www.baidu.com/img/bd_logo1.png?where=super";
       
        Glide.with(this).load(imgUrl).into(imageView);

        Glide.with(getApplicationContext())
                .load(imgUrl)
                .asGif()
                .asBitmap()
                .placeholder(R.mipmap.ic_launcher)
                .error(R.mipmap.ic_launcher)
                .override(300,300)
                .fitCenter()
                .centerCrop()
                .skipMemoryCache(true)
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .diskCacheStrategy(DiskCacheStrategy.RESULT)
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                .priority(Priority.HIGH)
                .into(imageView);

Model -(ModelLoader)-> Data -(Decoder)-> Resource -(Transform)-> TransformedResource -(Transcode)-> TranscodedResource --> Target

with() 到底做了什么?

==关键类==:RequestManagerRetriever、RequestManager

首先需要注意 with方法传入的context对象将会决定我们Glide存活的生命周期。

    /** Begin a load with Glide by passing in a context.
     * <p>
     *     This method is appropriate for resources that will be used outside of the normal fragment or activity
     *     lifecycle (For example in services, or for notification thumbnails).
     * </p>
     *
     * @see #with(android.app.Activity)
     * @see #with(android.app.Fragment)
     * @see #with(android.support.v4.app.Fragment)
     * @see #with(android.support.v4.app.FragmentActivity)
     *
     * @param context Any context, will not be retained.
     * @return A RequestManager for the top level application that can be used to start a load.
     */
    public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }
    
    
    /**
     * Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle and that uses the
     * given {@link Activity}'s default options.
     *
     * @param activity The activity to use.
     * @return A RequestManager for the given activity that can be used to start a load.
     */
    public static RequestManager with(Activity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }

可以看到,with()方法的重载种类非常多,既可以传入Activity,也可以传入Fragment或者是Context。其实都是先调用RequestManagerRetriever的静态get()方法得到一个RequestManagerRetriever对象,这个静态get()方法是一个最基础的单例模式。然后再调用RequestManagerRetriever的实例get()方法,去获取RequestManager对象。其实无非就是两种情况而已,即传入Application类型的参数,和传入非Application类型的参数。

传入Application类型的参数

代码如下:

    public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }
    
    
    
    public RequestManager get(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("You cannot start a load on a null Context");
        } else if (Util.isOnMainThread() && !(context instanceof Application)) {
            if (context instanceof FragmentActivity) {
                return get((FragmentActivity) context);
            } else if (context instanceof Activity) {
                return get((Activity) context);
            } else if (context instanceof ContextWrapper) {
                return get(((ContextWrapper) context).getBaseContext());
            }
        }

        return getApplicationManager(context);
    }


    private RequestManager getApplicationManager(Context context) {
        // Either an application context or we're on a background thread.
        if (applicationManager == null) {
            synchronized (this) {
                if (applicationManager == null) {
                    // Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
                    // However, in this case since the manager attached to the application will not receive lifecycle
                    // events, we must force the manager to start resumed using ApplicationLifecycle.
                    applicationManager = new RequestManager(context.getApplicationContext(),
                            new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
                }
            }
        }

        return applicationManager;
    }

这里插一句 不知道有没有人好奇

RequestManagerTreeNode是干嘛的呢?

上文提到获取所有childRequestManagerFragments的RequestManager就是通过该类获得,就一个方法:getDescendants,作用就是基于给定的Context,获取所有层级相关的RequestManager。上下文层级由Activity或者Fragment获得,ApplicationContext的上下文不会提供RequestManager的层级关系,而且Application生命周期过长,所以Glide中对请求的控制只针对于Activity和Fragment。

继续说,传入Application类型,其实这是最简单的一种情况,因为Application对象的生命周期即应用程序的生命周期,因此Glide并不需要做什么特殊的处理,它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide的加载也会同时终止。

传入非Application参数的情况

代码如下:

public RequestManager get(Fragment fragment) {
        if (fragment.getActivity() == null) {
            throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
        }
        if (Util.isOnBackgroundThread()) {
            return get(fragment.getActivity().getApplicationContext());
        } else {
            FragmentManager fm = fragment.getChildFragmentManager();
            return supportFragmentGet(fragment.getActivity(), fm);
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public RequestManager get(Activity activity) {
        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return get(activity.getApplicationContext());
        } else {
            assertNotDestroyed(activity);
            android.app.FragmentManager fm = activity.getFragmentManager();
            return fragmentGet(activity, fm);
        }
    }

如果我们是在非主线程当中使用的Glide,那么不管你是传入的Activity还是Fragment,都会被强制当成Application来处理。

方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的,那就是会调用fragmentGet()方法,向当前的Activity当中添加一个隐藏的RequestManagerFragment。

其实,最终都是调用了fragmentGet()这个方法去获取RequestManager,

 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
        RequestManagerFragment current = getRequestManagerFragment(fm);
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
            requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
            //非常重要的一个方法,就是通过这个方法将我们空的fragment关联到了Requestmanager关联绑定到了一起
            //RequestManager和RequestManagerFragment都是一一对应的
            current.setRequestManager(requestManager);
        }
        return requestManager;
    }
    
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {
        RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
        if (current == null) {
            current = pendingRequestManagerFragments.get(fm);
            if (current == null) {
                current = new RequestManagerFragment();
                pendingRequestManagerFragments.put(fm, current);
                fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
                //这里
                handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
            }
        }
        return current;
    }

可以看到RequestManagerRetriever其实就是一个RequestManager的生产类。

那这个RequsetManager是干什么的呢?

其实RequestManager是用于管理Glide的图片加载请求的和完成glide对象的构造。最重要的一点就是用于监听我们整个组件的生命周期。

那么这里为什么要添加一个隐藏的Fragment呢?

因为Glide需要知道加载的生命周期。很简单的一个道理,如果你在某个Activity上正在加载着一张图片,结果图片还没加载出来,Activity就被用户关掉了,那么图片还应该继续加载吗?当然不应该。可是Glide并没有办法知道Activity的生命周期,于是Glide就使用了添加隐藏Fragment的这种小技巧,因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止图片加载了。

生命周期事件的传递

Glide精妙设计之一

with()的源码设计中比较重要,核心的一点来说就是,将Glide和组件的生命周期相挂钩。

总体来说,第一个with()方法的源码还是比较好理解的。其实就是为了得到一个RequestManager对象而已,然后Glide会根据我们传入with()方法的参数来确定图片加载的生命周期,并没有什么特别复杂的逻辑,就是一个准备好基础配置的方法。

load()方法到底做了什么?

==关键词==
DrawableTypeRequest,GenericRequestBuilder(是我们在glide当中配置所有参数的父类,也就是说,只要是在glide当中配置参数,就一定是通过这个类或者他的子类来实现的)

ModelLoader(通过数据来源,将数据来源加载成原始数据)

RequestTracker(直译的话就是请求追踪器,跟踪图片请求的整个周期,可以做取消,重启一些失败的图片请求生命周期的管理主要由RequestTracker和TargetTracker处理。builder.createGlide() 创建Glide对象。

由于with()方法返回的是一个RequestManager对象,那么很容易就能想到,load()方法是在RequestManager类当中的,所以说我们首先要看的就是RequestManager这个类。

那么我们先来看load()方法,这个方法中的逻辑是非常简单的,只有一行代码,就是先调用了fromString()方法,再调用load()方法,然后把传入的图片URL地址传进去。(也可以从源码中看出,load有多个重载方法,支持String,file,Integer,byte等各种数据来源)

而fromString()方法也极为简单,就是调用了loadGeneric()方法,并且指定参数为String.class,因为load()方法传入的是一个字符串参数。那么看上去,好像主要的工作都是在loadGeneric()方法中进行的了。

 /**
     * Returns a request builder to load the given {@link java.lang.String}.
     * signature.
     *
     * @see #fromString()
     * @see #load(Object)
     *
     * @param string A file path, or a uri or url handled by {@link com.bumptech.glide.load.model.UriLoader}.
     */
    public DrawableTypeRequest<String> load(String string) {
        return (DrawableTypeRequest<String>) fromString().load(string);
    }
    
     public DrawableTypeRequest<String> fromString() {
     //传入的是String的class对象
        return loadGeneric(String.class);
    }
    
     private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);as
        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
                Glide.buildFileDescriptorModelLoader(modelClass, context);
        if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
            throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
                    + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
                    + " Glide#register with a ModelLoaderFactory for your custom model class");
        }

        return optionsApplier.apply(
                new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                        glide, requestTracker, lifecycle, optionsApplier));
    }

在loadGeneric()方法第一行可以看到有一句Glide.buildStreamModelLoader(modelClass, context)点进去可以看到他不仅返回了ModelLoader对象,而且还初始化了Glide。点进去看看:

/**
     * A method to build a {@link ModelLoader} for the given model that produces {@link InputStream}s using a registered
     * factory.
     *
     * @see #buildModelLoader(Class, Class, android.content.Context)
     */
    public static <T> ModelLoader<T, InputStream> buildStreamModelLoader(Class<T> modelClass, Context context) {
        return buildModelLoader(modelClass, InputStream.class, context);
    }
    
    
     public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass,
            Context context) {
         if (modelClass == null) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Unable to load null model, setting placeholder only");
            }
            return null;
        }
        return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass);
    }
    
    /**
     * Get the singleton.
     *
     * @return the singleton
     */
    public static Glide get(Context context) {
        if (glide == null) {
            synchronized (Glide.class) {
                if (glide == null) {
                    Context applicationContext = context.getApplicationContext();
                    //解析清单文件配置的自定义GlideModule的metadata标签,返回一个GlideModule集合
                    List<GlideModule> modules = new ManifestParser(applicationContext).parse();

                    GlideBuilder builder = new GlideBuilder(applicationContext);
                    for (GlideModule module : modules) {
                        module.applyOptions(applicationContext, builder);
                    }
                    //初始化了glide的单例。
                    glide = builder.createGlide();
                    for (GlideModule module : modules) {
                        module.registerComponents(applicationContext, glide);
                    }
                }
            }
        }

        return glide;
    }

我们看到通过反射的方式获取我们在清单文件中声明的自定义的GlideModule对象。在获取到GlideModule集合之后,遍历了集合并调用相应的applyOptions和registerComponents方法,而Glide对象的生成是通过GlideBuilder的createGlide方法创建。(底下有例子)

看到这里不知道大家会不会跟我有一样的疑问,就是

GlideModule是个啥?干嘛用的?

可以通过GlideBuilder进行一些延迟的配置和ModelLoaders的注册。注意:
所有的实现的module必须是public的,并且只拥有一个空的构造函数,以便Glide懒加载的时候可以通过反射调用。
GlideModule是不能指定调用顺序的。因此在创建多个GlideModule的时候,要注意不同Module之间的setting不要冲突了。

接下来看一下glide = builder.createGlide();这句代码做了什么

Glide createGlide() {
        if (sourceService == null) {
        //查看核心线程数
            final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
            //初始化线程池
            sourceService = new FifoPriorityThreadPoolExecutor(cores);
        }
        if (diskCacheService == null) {
            diskCacheService = new FifoPriorityThreadPoolExecutor(1);
        }

        MemorySizeCalculator calculator = new MemorySizeCalculator(context);
        //初始化bitmapPool
        //图片池用的是targetPoolSize(即一般是缓存大小是屏幕的宽高4*4).
        if (bitmapPool == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
               
                int size = calculator.getBitmapPoolSize();
                bitmapPool = new LruBitmapPool(size);
            } else {
                bitmapPool = new BitmapPoolAdapter();
            }
        }

        ////内存缓存用的是targetMemoryCacheSize (即一般是缓存大小是屏幕的宽 * 高 * 4 * 2)
        if (memoryCache == null) {
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
        }

        if (diskCacheFactory == null) {
        //磁盘缓存 默认大小:250 MB,默认目录:image_manager_disk_cache.
        //Glide默认是用InternalCacheDiskCacheFactory类来创建硬盘缓存的,这个类会在应用的内部缓存目录下面创建一个最大容量250MB的缓存文件夹,使用这个缓存目录而不用sd卡,意味着除了本应用之外,其他应用是不能访问缓存的图片文件的。
            diskCacheFactory = new InternalCacheDiskCacheFactory(context);
        }

        if (engine == null) {
        //引擎初始化
            engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
        }

        if (decodeFormat == null) {
            decodeFormat = DecodeFormat.DEFAULT;
        }

        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }

看到这里其实大体的glide所做的内容我们已经清楚,其实Glide还支持动态的缓存大小调整,在存在大量图片的Activity/Fragment中,可以通过setMemoryCategory方法来提高Glide的内存缓存大小,从而加快图片的加载速度。

Glide.get(getApplicationContext()).setMemoryCategory(MemoryCategory.HIGH);

MemoryCategory有3个值可供选择:

  1. MemoryCategory.HIGH(初始缓存大小的1.5倍)
  2. MemoryCategory.NORMAL(初始缓存大小的1倍)
  3. MemoryCategory.LOW(初始缓存大小的0.5倍)

Glide磁盘缓存策略分为四种,默认的是RESULT:

  1. ALL:缓存原图和处理图
  2. NONE:什么都不缓存
  3. SOURCE:只缓存原图
  4. RESULT:只缓存处理图
这么恶心的ModelLoader到底是干嘛用的?

ModelLoader对象是用于加载图片各种资源的,而我们给load()方法传入不同类型的参数,这里也会得到不同的ModelLoader对象。该接口有两个目的:将任意复杂的model转换为可以被decode的数据类型,允许model结合View的尺寸获取特定大小的资源













最后我们可以看到,loadGeneric()方法是要返回一个DrawableTypeRequest对象的,因此在loadGeneric()方法的最后又去new了一个DrawableTypeRequest对象,然后把刚才获得的ModelLoader对象,还有一大堆杂七杂八的东西都传了进去。

那DrawableTypeRequest是做什么的呢

DrawableTypeRequest
这个类中的代码本身就不多,主要看一下构造方法和我们会用到的两个比较重要的方法asGif()和asBitmap()。这两个方法分别是用于强制指定加载静态图片和动态图片。将我们的图片转化为BitmapTypeRequest或者GifTypeRequest两种图片格式。

asBitmap()与asGif()

不管我们传入的是一张普通图片,还是一张GIF图片,Glide都会自动进行判断,并且可以正确地把它解析并展示出来。

但是如果我想指定图片的格式该怎么办呢?就比如说,我希望加载的这张图必须是一张静态图片,我不需要Glide自动帮我判断它到底是静图还是GIF图。

好的我们只需要反向操作下,两种情况:

1. 传入gif链接,使用asBitmap()方法,gif图则无法正常播放,而是会停在第一帧的图片。
2. 传入静态图片链接,使用asGif()方法,会显示error()设置的图片,没错,如果指定了只能加载动态图片,而传入的图片却是一张静图的话,那么结果自然就只有加载失败。

看一下代码:

  DrawableTypeRequest(Class<ModelType> modelClass, ModelLoader<ModelType, InputStream> streamModelLoader,
            ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader, Context context, Glide glide,
            RequestTracker requestTracker, Lifecycle lifecycle, RequestManager.OptionsApplier optionsApplier) {
        super(context, modelClass,
                buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class,
                        GlideDrawable.class, null),
                glide, requestTracker, lifecycle);
        this.streamModelLoader = streamModelLoader;
        this.fileDescriptorModelLoader = fileDescriptorModelLoader;
        this.optionsApplier = optionsApplier;
    }

  
    public BitmapTypeRequest<ModelType> asBitmap() {
        return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
                fileDescriptorModelLoader, optionsApplier));
    }

  
    public GifTypeRequest<ModelType> asGif() {
        return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier));
    }

而从源码中可以看出,它们分别又创建了一个BitmapTypeRequest和GifTypeRequest,如果没有进行强制指定的话,那默认就是使用DrawableTypeRequest。
好的,那么我们再回到RequestManager的load()方法中。刚才已经分析过了,fromString()方法会返回一个DrawableTypeRequest对象,接下来会调用这个对象的load()方法,把图片的URL地址传进去。点进去看看load()是在DrawableRequestBuilder类中,我们也可以看到DrawableRequestBuilder是DrawableTypeRequest的父类。看代码:

   @Override
    public DrawableRequestBuilder<ModelType> load(ModelType model) {
        super.load(model);
        return this;
    }
   
   
       public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
        this.model = model;
        isModelSet = true;  //注意这个boolean 在into方法时我们会讲到
        return this;
    } 

其实这个model是什么,说白了就是我们传进来的数据对象。就是数据来源,可以支持多种类型,图片,url,字节,文件等。

DrawableTypeRequest的父类是DrawableRequestBuilder,DrawableRequestBuilder中有很多个方法,这些方法其实就是Glide绝大多数的API了。里面有不少我们在上篇文章中已经用过了,比如说placeholder()方法、error()方法、diskCacheStrategy()方法、override()方法,当然还有最重要的into()方法。其实通过源码得知,DrawableRequestBuilder在这些方法中也没有做什么处理,主要是通过父类的方法来做相应处理。

最重要的来了,在DrawableRequestBuilder类中有一个into()方法,也就是说,最终load()方法返回的其实就是一个DrawableTypeRequest对象。

    @Override
    public Target<GlideDrawable> into(ImageView view) {
        return super.into(view);
    }
Glide精妙设计之二

其实通过Glide支持链式调用就可以知道,他是使用了建造者模式构建的,类似于我们的Dialog,Retrofit。泛型,接口的使用。

相关文章:

获取到系统可用的处理器核心数目

glideModule使用例子

Class的isAssignableFrom方法

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

推荐阅读更多精彩内容