Glide 学习笔记

入行一年半的android程序员,半个月前的一个上午还在跟同事讨论发不发年终奖,下午就被裁了。找工作快两个礼拜了,连面试的都没有,更别说找个好工作了。估计再呆下去真得抑郁症了

同理,上来先喷一下,横线之后开始写内容


Glide的基本用法

Glide.with(this).load("").into();

Glide :: with

创建了一个RequestManagerRetriever 的实例,调用了get方法

public static RequestManager with(FragmentActivity activity) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(activity);
}

RequestManagerRetriever :: get

先检查一下线程,如果不在主线程,执行get方法 。 ps: 不过我记得,加载图片,是不能放在线程里的,这里却放过了线程

public RequestManager get(FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
        return get(activity.getApplicationContext());
    } else {
        assertNotDestroyed(activity);
        FragmentManager fm = activity.getSupportFragmentManager();
        return supportFragmentGet(activity, fm);
    }
}

RequestManager :: supportFragmentGet

  • 从FragmentManager中取出当前页面的fragment
    (SupportRequestManagerFragment 这个fragment主要的功能就是同步activity的生命周期)
  • 要是activity没有这个fragment的话,pendingSupportRequestManagerFragment这儿拿
  • 还没有的话创建一个fragment

ps:这里有个问题,每次创建完之后,发一个handler,把存储在pendingSupportRequestManagerFragments中的fragment干掉了,不知道是干嘛的,猜测是避免重复创建frgment的一个手段吧

RequestManager supportFragmentGet(Context context, FragmentManager fm) {
    SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}
SupportRequestManagerFragment getSupportRequestManagerFragment(final FragmentManager fm) {
    SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(

    FRAGMENT_TAG);
    if (current == null) {
        current = pendingSupportRequestManagerFragments.get(fm);
        if (current == null) {
            current = new SupportRequestManagerFragment();
            pendingSupportRequestManagerFragments.put(fm, current);
            fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
            handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
        }
    }
    return current;
}

接下来最主要的就是RequestManager 了

RequestManager

RequestManager管理了每个acitivity上的图片请求,RequestManager 含有了activity的生命周期,并且含有一个RequestTracker这样的请求栈,这个栈是一个包含有请求(request)的arrayList

RequestManager :: load

还记得上面说到的request吗?这里就根据传入的资源去构建一个request build,这里就是构建了一个DrawableTypeRequest 。在RequestManager 中有各种load,load调用了各种from,但都调用了一个loadGeneric方法

public DrawableTypeRequest < Integer > load(Integer resourceId) {
    return (DrawableTypeRequest < Integer > ) fromResource().load(resourceId);
}

RequestManager :: loadGeneric

从代码上看,主要创建了两个modelLoader 和调用了 optionsApplier.apply。(这里要打一个大大的问号了......)这段代码不是很懂,结合这注释看,总之这里创建了DrawableTypeRequest (具体过程先不管了),调用父类的load方法,也就是GenericRequestBuilder的load

private < T > DrawableTypeRequest < T > loadGeneric(Class < T > modelClass) {
    ModelLoader < T, InputStream > streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
    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));
}

GenericRequestBuilder :: into

GenericRequestBuilder中的into方法,首先进行了线程的检查,等异常的处理和transform的几种缩放处理,之后统一调用了into方法

public Target < TranscodeType > into(ImageView view) {
    Util.assertMainThread();
    if (view == null) {
        throw new IllegalArgumentException("You must pass in a non null View");
    }

    if (!isTransformationSet && view.getScaleType() != null) {
        switch (view.getScaleType()) {
        case CENTER_CROP:
            applyCenterCrop();
            break;
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
            applyFitCenter();
            break;
            //$CASES-OMITTED$
        default:
            // Do nothing.
        }
    }

    return into(glide.buildImageViewTarget(view, transcodeClass));
}

GenericRequestBuilder :: into(target)

这个方法主要看target有没有请求,若有的话清掉,在构建一个请求,添加到生命周期,并执行请求

public < Y extends Target < TranscodeType >> Y into(Y target) {
    Util.assertMainThread();
    if (target == null) {
        throw new IllegalArgumentException("You must pass in a non null Target");
    }
    if (!isModelSet) {
        throw new IllegalArgumentException("You must first set a model (try #load())");
    }

    Request previous = target.getRequest();

    if (previous != null) {
        previous.clear();
        requestTracker.removeRequest(previous);
        previous.recycle();
    }

    Request request = buildRequest(target);
    target.setRequest(request);
    lifecycle.addListener(target);
    requestTracker.runRequest(request);

    return target;
}

通过obtainRequest 去获得Request

private Request obtainRequest(Target < TranscodeType > target, float sizeMultiplier, Priority priority, RequestCoordinator requestCoordinator) {
    return GenericRequest.obtain(loadProvider, model, signature, context, priority, target, sizeMultiplier, placeholderDrawable, placeholderId, errorPlaceholder, errorId, fallbackDrawable, fallbackResource, requestListener, requestCoordinator, glide.getEngine(), transformation, transcodeClass, isCacheable, animationFactory, overrideWidth, overrideHeight, diskCacheStrategy);
}

RequestTracker :: runRequest

将请求添加到请求队列中,如果没有暂停,将Request开始,并且添加到挂起的队列

public void runRequest(Request request) {
    requests.add(request);
    if (!isPaused) {
        request.begin();
    } else {
        pendingRequests.add(request);
    }
}

GenericRequest :: begin

begin 方法中onSizeReady() 是主要的方法,与此之外,这里还调用了target的一些方法,主要是添加占位和错误的图片,基本上就是调用了view 的设置图片,就不粘代码了。主要还是看onSizeReady

@Override public void begin() {
    startTime = LogTime.getLogTime();
    if (model == null) {
        onException(null);
        return;
    }

    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        target.getSize(this);
    }

    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
    }
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
}
@Override public void onSizeReady(int width, int height) {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
        return;
    }
    status = Status.RUNNING;

    width = Math.round(sizeMultiplier * width);
    height = Math.round(sizeMultiplier * height);

    ModelLoader < A,
    T > modelLoader = loadProvider.getModelLoader();
    final DataFetcher < T > dataFetcher = modelLoader.getResourceFetcher(model, width, height);

    if (dataFetcher == null) {
        onException(new Exception("Failed to load model: \'" + model + "\'"));
        return;
    }
    ResourceTranscoder < Z,
    R > transcoder = loadProvider.getTranscoder();
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    loadedFromMemoryCache = true;
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this);
    loadedFromMemoryCache = resource != null;
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
}

主要的是这一段

loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this);

调用loadFromCache从内存加载,若返回值为空再次从活动的资源中加载,若再次为空查看jobs是否提交过任务,若没有提交则创建EngineRunnable,并将任务提交到engineJob中

public < T,Z,R > LoadStatus load(Key signature, int width, int height, DataFetcher < T > fetcher, DataLoadProvider < T, Z > loadProvider, Transformation < Z > transformation, ResourceTranscoder < Z, R > transcoder, Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    final String id = fetcher.getId();
    EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder());

    EngineResource < ?>cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        cb.onResourceReady(cached);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
        }
        return null;
    }

    EngineResource < ?>active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
        cb.onResourceReady(active);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from active resources", startTime, key);
        }
        return null;
    }

    EngineJob current = jobs.get(key);
    if (current != null) {
        current.addCallback(cb);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Added to existing load", startTime, key);
        }
        return new LoadStatus(cb, current);
    }

    EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
    DecodeJob < T,Z, R > decodeJob = new DecodeJob < T, Z,R > (key, width, height, fetcher, loadProvider, transformation, transcoder, diskCacheProvider, diskCacheStrategy, priority);
    EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    engineJob.start(runnable);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
}

看一下EngineRunnable的run方法,对资源进行解码

@Override 
public void run() {
    if (isCancelled) {
        return;
    }

    Exception exception = null;
    Resource < ?>resource = null;
    try {
        resource = decode();
    } catch(Exception e) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Exception decoding", e);
        }
        exception = e;
    }

    if (isCancelled) {
        if (resource != null) {
            resource.recycle();
        }
        return;
    }

    if (resource == null) {
        onLoadFailed(exception);
    } else {
        onLoadComplete(resource);
    }
}

是从缓存中拿到还是从资源中拿到,应该在decodeFromCache指的是缓存,FromSource拉取资源。缓存是通过DiskLruCache进行获取的。继续跟到decodeFromSource中,最后到了decodeSource这个方法中

private Resource < ?>decode() throws Exception {
    if (isDecodingFromCache()) {
        return decodeFromCache();
    } else {
        return decodeFromSource();
    }
}

最主要的是fetcher的loadData方法,从注释上来看,fetcher是一个用于加载资源的接口,实现这个接口的类很多, 取一个最常用的 HttpUrlFetcher 试着去看看

private Resource < T > decodeSource() throws Exception {
    Resource < T > decoded = null;
    try {
        long startTime = LogTime.getLogTime();
        final A data = fetcher.loadData(priority);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Fetched data", startTime);
        }
        if (isCancelled) {
            return null;
        }
        decoded = decodeFromSourceData(data);
    } finally {
        fetcher.cleanup();
    }
    return decoded;
}
Paste_Image.png

最后关注了一下loadDataWithRedirects中的联网请求

HttpUrlFetcher ::loadDataWithRedirects

在代码中使用了HttpURLConnection作为网络请求等一些属性

Paste_Image.png

基本上glide的流程都走了一遍,但每个模块都没有深入的研究,有时间再去看看几个有疑问地方的代码实现。
这里贴一张网络图片,记录一下glide的总体设计

Paste_Image.png

Glide 怎么加载的okhttp作为网络请求的 ?

在一个app,为了保持网络请求的一致性,通常也会把Glide的数据加载换成与本来项目中的网络请求一致的框架,glide也支持这些,这是官方的文档 https://github.com/bumptech/glide/wiki/Integration-Libraries
比如我用的ide是android studio, 当我添加了这个依赖

dependencies { 
compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar' //compile 'com.squareup.okhttp:okhttp:2.2.0'
}

gradle会自动在AndroidManifest.xml中添加一下这段标签,ant 或者 maven就得手动添加

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

在Glide创建RequestManager时,RequestManager的构造方法会调用Glide的get方法。 这里读取了ManifestParser的标签,去找到GlideModule标签对应的android:name
在代码指定的是com.bumptech.glide.integration.okhttp.OkHttpGlideModule
最后通过ManifestParser::parseModule()去创建一个GlideModule, 这里实现GlideModule接口的是OkHttpGlideModule

在for循环中, 调用了applyOptions方法,但是什么都没干,最后调用了registerComponents方法,创建了一个OkHttpUrlLoader.Factory(), 去注册一个 GenericLoaderFactory loaderFactory。在上面看的源码中,看不太懂的modeloader()那里, 正好获得了这个OkHttpUrlLoader。

public static Glide get(Context context) {
    if (glide == null) {
        synchronized(Glide.class) {
            if (glide == null) {
                Context applicationContext = context.getApplicationContext();
                List < GlideModule > modules = new ManifestParser(applicationContext).parse();

                GlideBuilder builder = new GlideBuilder(applicationContext);
                for (GlideModule module: modules) {
                    module.applyOptions(applicationContext, builder);
                }
                glide = builder.createGlide();
                for (GlideModule module: modules) {
                    module.registerComponents(applicationContext, glide);
                }
            }
        }
    }
    return glide;
}

private static GlideModule parseModule(String className) {
    Class < ?>clazz;
    try {
        clazz = Class.forName(className);
    } catch(ClassNotFoundException e) {
        throw new IllegalArgumentException("Unable to find GlideModule implementation", e);
    }

    Object module;
    try {
        module = clazz.newInstance();
    } catch(InstantiationException e) {
        throw new RuntimeException("Unable to instantiate GlideModule implementation for " + clazz, e);
    } catch(IllegalAccessException e) {
        throw new RuntimeException("Unable to instantiate GlideModule implementation for " + clazz, e);
    }

    if (! (module instanceof GlideModule)) {
        throw new RuntimeException("Expected instanceof GlideModule, but found: " + module);
    }
    return (GlideModule) module;
}
public class OkHttpGlideModule implements GlideModule {@Override public void applyOptions(Context context, GlideBuilder builder) {
        // Do nothing.
    }

    @Override public void registerComponents(Context context, Glide glide) {
        glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
    }
}
public class OkHttpGlideModule implements GlideModule {
@Override
 public void applyOptions(Context context, GlideBuilder builder) {
        // Do nothing.
    }

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

Glide 怎么实现对生命周期的处理?

Glide 使用创建一个fragment的方式,监视了activity的生命周期。

FragmentManager fm = getSupportFragmentManager();
FeedFragment current = (FeedFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null){    
    current = new FeedFragment();   
    fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
}

这样就可以同步activity的生命周期

  • 当启动一个应用

Activity onCreate:
Fragment onAttach:
Fragment onCreate:
Fragment onCreateView:
Fragment onActivityCreated
Fragment onStart:
Activity onStart:
Activity onResume:
Fragment onResume:

  • 按下home

Fragment onPause:
Activity onPause:
Fragment onStop:
Activity onStop:

  • 重新唤醒

Fragment onStart:
Activity onStart:
Activity onResume:
Fragment onResume:

  • 退出应用

Fragment onPause:
Activity onPause:
Fragment onStop:
Activity onStop:
Fragment onDestroyView:
Fragment onDestroy:
Fragment onDetach:
Activity onDestroy:

Glide怎么判断的图片大小?

根据上面的流程,我定位到了GenericRequest :: begin方法

这里先对宽高,进行判断,若不合法,走target的getSize, getSize也会对宽高判断,失败了就会抛出异常(throw new IllegalArgumentException("You cannot set the tag id more than once or change" + " the tag id after the first request has been made");)我准备沿着代码逻辑向前走,看看是哪里来的overrideWidth。

@Override 
public void begin() {
    startTime = LogTime.getLogTime();
    if (model == null) {
        onException(null);
        return;
    }

    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        target.getSize(this);
    }

    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
    }
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
}

GenericRequestBuilder中对宽和高进行初始化,初始化的值是-1,发现以下这些类都调用了对宽高进行了赋值,这些类都是GenericRequestBuilder的子类,都会调用GenericRequestBuilder :: override()
方法

Paste_Image.png

RequestManager :: loadGeneric创建了DrawableTypeRequest, DrawableTypeRequest是DrawableRequestBuilder的子类, DrawableRequestBuilder这个继续调用父类的构造。最后找到Glide :: 的buildImageViewTargetImageViewTargetFactory :: buildTarget
DrawableImageViewTarget分析,一直跟到ViewTarget的构造, 这里创建了一个SizeDeterminer,最上面的代码target.getSize(this);实际上调用了就是这个SizeDeterminer的getSize.看了一圈, 又回到了GenericRequest类;
所以,在GenericRequest的begin中,当指定了override时, 直接调用onSizeReady;没有则调用了target(target理解成view就好)getSize,看当前的view是否已经有宽高了,若没有则去监听view的宽高,再去调用onSizeReady

ViewTarget :: getSize

public void getSize(SizeReadyCallback cb) {
    int currentWidth = getViewWidthOrParam();
    int currentHeight = getViewHeightOrParam();
    if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
        cb.onSizeReady(currentWidth, currentHeight);
    } else {
        // We want to notify callbacks in the order they were added and we only expect one or two callbacks to
        // be added a time, so a List is a reasonable choice.
        if (!cbs.contains(cb)) {
            cbs.add(cb);
        }
        if (layoutListener == null) {
            final ViewTreeObserver observer = view.getViewTreeObserver();
            layoutListener = new SizeDeterminerLayoutListener(this);
            observer.addOnPreDrawListener(layoutListener);
        }
    }
}

Glide 设置缩略图之后回莫名其妙的‘闪’一下

在加载一个列表页的时候,通常会添加一个占位图,并设置crossfade这个属性,能让显示更加平稳,显示效果也比较好看。但出现一个问题。图片加载的时候, 图片不仅仅回做透明度的变化,并且大小也改变了, 显示效果页比较难看。

GlideDrawableImageViewTarget::onResourceReady

最终图片加载会走到这里,中间一堆注释先不看(看不懂)

看一下父类的onResourceReady方法

Paste_Image.png

能看到这里new 了一个TransitionDrawwable, 这个drawable正式能显示渐变动画的drawable

getCurrentDrawable 是view当前的drawale

再看下 #### GenericRequest::begin()

当未完成未失败的情况下,加载了placeholder,这时候view显示的占位图试placeholder, currentDrawable却是占位图的, 但进行动画之后glide回按照imageview的宽高裁剪图片,这样一来,必然会出现闪一下的这种情况了

Glide本地缓存是什么样的?

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

推荐阅读更多精彩内容