Glide 源碼(一)

Glide 源碼

Glide是google開源的一款圖片加載框架,注重性能和加載速度称近。本身采用流式Api便于操作。本文根據(jù)Glide源碼谦疾,分析一下Glide的內(nèi)部實現(xiàn)。源碼基于glide 3.7

with()

Glide的使用從Glide.with(Context context)開始犬金,with是Glide的一個靜態(tài)方法念恍,參數(shù)為context,內(nèi)部Glide會根據(jù)傳入的不同context進(jìn)行不同的操作晚顷。

    public static RequestManager with(Context context) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(context);
}

RequestManagerRetriever用于生成RequestManager樊诺,跟進(jìn)RequestManagerRetriever的get()方法:

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);
}

//以Activity舉例
@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);
    }
}

可以看到get方法根據(jù)不同的context種類進(jìn)行不同的操作,如果是ApplicationContext或者在后臺線程創(chuàng)建的Glide會默認(rèn)走getApplicationManager()方法音同。

接下來進(jìn)入fragmentGet()方法:

@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());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

getRequestManagerFragment方法使用默認(rèn)的tag通過findFragmentByTag獲取RequestManagerFragment词爬。如果沒有則新建一個返回。這里問什么要創(chuàng)建一個RequestManagerFragment呢权均?其實RequestManagerFragment是Fragment的一個子類顿膨,RequestManager是它的成員變量。Glide這里的做法很巧妙叽赊,通過創(chuàng)建一個與當(dāng)前Activity綁定的沒有布局的Fragment用于監(jiān)聽Activity的生命周期恋沃,并在一些特定時期調(diào)用RequestManager的方法。比如必指,F(xiàn)ragment實現(xiàn)了ComponentCallbacks2接口囊咏,有兩個方法onTrimMemory()和onLowMemory()。用于在內(nèi)存不足時調(diào)用RequestManager的方法用于釋放內(nèi)存。因為Glide內(nèi)部對Activity的生命周期進(jìn)行監(jiān)聽梅割,在onDestory()的時候釋放了資源并調(diào)用了RequestTacker的clearRequests()方法取消網(wǎng)絡(luò)請求霜第,防止內(nèi)存泄漏。所以對于使用者來說不用手動的在onDestory()中對Glide進(jìn)行操作户辞。這也解釋了為什么在子線程創(chuàng)建會傳入ApplicationContext泌类,使Glide與整個應(yīng)用的生命周期保持一致。

注意到在獲取RequestManager的時候有一個assertNotDestroyed(activity)方法:

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static void assertNotDestroyed(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {
        throw new IllegalArgumentException("You cannot start a load for a destroyed activity");
    }
}

在實際開發(fā)中在網(wǎng)絡(luò)比較慢的時候比如去服務(wù)器請求圖片url比較慢底燎,當(dāng)請求過程中按下了返回鍵刃榨,url返回調(diào)用Glide加載時activity已經(jīng)銷毀,將會拋出異常双仍。這里的解決思路是盡可能早的把RequestManager創(chuàng)建出來枢希,比如可以在Activity的onCreate()方法或者Fragment的onAttach()方法進(jìn)行RequestManager的初始化操作。

RequestManager manager = Glide.with(this);

load()

通過Glide.with()方法得到RequestManager后朱沃,調(diào)用RequestManager的load方法進(jìn)行圖片加載苞轿。

load()有多個重載方法,分別對應(yīng)從uri为流、url呕屎、文件让簿、資源id等地方進(jìn)行加載敬察。我們選取最常用的從url加載進(jìn)行進(jìn)一步分析。

public DrawableTypeRequest<String> load(String string) {
    return (DrawableTypeRequest<String>) fromString().load(string);
}

public DrawableTypeRequest<String> fromString() {
    return loadGeneric(String.class);
}

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));
}

與從url加載類似尔当,其他集中l(wèi)oad()方法同樣最后調(diào)用loadGeneric()方法莲祸,傳入不同的class對象,構(gòu)建不同的ModelLoader椭迎。loadGeneric()方法構(gòu)建兩個ModelLoader锐帜,新建一個DrawableTypeRequest并返回。ModelLoader在Glide中是一個比較重要的概念畜号,作用是從原始數(shù)據(jù)源中取出數(shù)據(jù)缴阎。即把抽象的數(shù)據(jù)模型轉(zhuǎn)化成具體的數(shù)據(jù),一般為InputSteam简软,比如根據(jù)url網(wǎng)絡(luò)請求蛮拔。

實際上,Glide 在初始化的時候,對于每種類型的輸入:String痹升、int建炫、Integer、File疼蛾、Uri肛跌,都注冊了能夠?qū)⑺鼈冝D(zhuǎn)化為 InputStream 和 ParcelFileDescriptor 的 ModelLoader,保存在HashMap中。這兩個 buildxxxModelLoader() 方法實際上就是從 HashMap 中獲取對應(yīng)的 ModelLoader衍慎。在當(dāng)前情景下转唉,獲取到的是 StreamStringLoader 和 FileDescriptorStringLoader。

buildxxxModelLoader內(nèi)部實現(xiàn)都是一樣的西饵,都是先從緩存中取酝掩,如果取不到通過Factory創(chuàng)建一個并加入到緩存中,以buildStreamModelLoader()為例:

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);
}

public synchronized <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass) {
    ModelLoader<T, Y> result = getCachedLoader(modelClass, resourceClass);
    if (result != null) {
        if (NULL_MODEL_LOADER.equals(result)) {
            return null;
        } else {
            return result;
        }
    }

    final ModelLoaderFactory<T, Y> factory = getFactory(modelClass, resourceClass);
    if (factory != null) {
        result = factory.build(context, this);
        cacheModelLoader(modelClass, resourceClass, result);
    } else {
        cacheNullLoader(modelClass, resourceClass);
    }
    return result;
}

Glide.get(context).getLoaderFactory()工廠GenericLoaderFactory眷柔,調(diào)用工廠的buildModelLoader()方法期虾,首先通過getCacheLoader方法判斷緩存是否存在。如果不存在驯嘱,同樣從緩存中獲取ModelLoaderFactory镶苞,調(diào)用build()方法,添加緩存并返回當(dāng)前ModelLoader鞠评。因為當(dāng)前的modelClass為url即為String.class茂蚓,所以最終得到StreamStringLoader。

同理剃幌,得到FileDescriptorModelLoader聋涨,并最終構(gòu)建出DrawableTypeRequest「合纾回到RequestManager的load()方法牍白,調(diào)用了DrawableTypeRequest的load()方法,DrawableTypeRequest繼承自DrawableRequestBuilder抖棘,最終調(diào)用了DrawableRequestBuilder的父類GenericRequestBuilder的load()方法:

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
    this.model = model;
    isModelSet = true;
    return this;
}

load()方法里只做了一些復(fù)制操作茂腥。至此load()方法告一段落,主要是根據(jù)數(shù)據(jù)源類型構(gòu)建ModelLoader切省,并創(chuàng)建一個DrawableTypeRequest最岗。

into()

load()方法返回了一個DrawableTypeRequest對象,into()是它的父類DrawableRequestBuilder的方法朝捆,內(nèi)部調(diào)用了父類的into()方法般渡,即GenericRequestBuilder。

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));
}

into()方法中芙盘,如果當(dāng)前ImageView有設(shè)置ScaleType驯用,則進(jìn)行對應(yīng)的裁剪操作。以applyCenterCrop()為例何陆,發(fā)現(xiàn)GenericRequestBuilder的applyCenterCrop()由它的三個子類來實現(xiàn):DrawableRequestBuilder晨汹、BitmapRequestBuilder、GifRequestBuilder贷盲。DrawableRequestBuilder上節(jié)我們提到了淘这,BitmapRequestBuilder和GifRequestBuilder是怎么得到的呢剥扣?答案在DrawableTypeRequest里。DrawableTypeRequest里有兩個方法asBitmap()铝穷、asGif()分別得到BitmapRequestBuilder和GifRequestBuilder钠怯。以常規(guī)的DrawableRequestBuilder的applyCenterCrop()為例:

@Override
void applyCenterCrop() {
    centerCrop();
}

public DrawableRequestBuilder<ModelType> centerCrop() {
    return transform(glide.getDrawableCenterCrop());
}

public DrawableRequestBuilder<ModelType> transform(Transformation<GifBitmapWrapper>... transformation) {
    super.transform(transformation);
    return this;
}

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> transform(
        Transformation<ResourceType>... transformations) {
    isTransformationSet = true;
    if (transformations.length == 1) {
        transformation = transformations[0];
    } else {
        transformation = new MultiTransformation<ResourceType>(transformations);
    }

    return this;
}

經(jīng)過層層調(diào)用,最后到了GenericRequestBuilder的transform方法曙聂,transform方法只是一個簡單的賦值操作晦炊,具體的轉(zhuǎn)換操作不是在這里進(jìn)行的。

回到into()方法調(diào)用了glide中的buildImageViewTarget()方法宁脊,構(gòu)建一個ImageViewTarget:


<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}

public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
    if (GlideDrawable.class.isAssignableFrom(clazz)) {
        return (Target<Z>) new GlideDrawableImageViewTarget(view);
    } else if (Bitmap.class.equals(clazz)) {
        return (Target<Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
        return (Target<Z>) new DrawableImageViewTarget(view);
    } else {
        throw new IllegalArgumentException("Unhandled class: " + clazz
                + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
}

在ImageViewTargetFactory的buildTarget方法中断国,根據(jù)transcodedClass的類型構(gòu)造出不同的ImageViewTarget,transcodedClass是GenericRequestBuilder的構(gòu)造器傳進(jìn)來的榆苞,即上文提到的asBitmap()稳衬、asGif()。

得到ImageViewTarget后調(diào)用GenerocRequestBuilder中的into()的重載方法:


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;
}

Glide 通過 View 的 setTag() 方法將特定的 View 和其對應(yīng)的圖片加載請求綁定在一起坐漏。這樣做的好處是能夠容易地判斷 Target 所封裝的 View 是否被復(fù)用薄疚,復(fù)用時先取消之前的請求,避免了不必要的請求赊琳,也能防止圖片錯位街夭。

構(gòu)建新的Request對象:


private Request buildRequest(Target<TranscodeType> target) {
    if (priority == null) {
        priority = Priority.NORMAL;
    }
    return buildRequestRecursive(target, null);
}

private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
    if (thumbnailRequestBuilder != null) {
        if (isThumbnailBuilt) {
            throw new IllegalStateException("You cannot use a request as both the main request and a thumbnail, "
                    + "consider using clone() on the request(s) passed to thumbnail()");
        }
        // Recursive case: contains a potentially recursive thumbnail request builder.
        if (thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) {
            thumbnailRequestBuilder.animationFactory = animationFactory;
        }

        if (thumbnailRequestBuilder.priority == null) {
            thumbnailRequestBuilder.priority = getThumbnailPriority();
        }

        if (Util.isValidDimensions(overrideWidth, overrideHeight)
                && !Util.isValidDimensions(thumbnailRequestBuilder.overrideWidth,
                        thumbnailRequestBuilder.overrideHeight)) {
          thumbnailRequestBuilder.override(overrideWidth, overrideHeight);
        }

        ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
        Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
        // Guard against infinite recursion.
        isThumbnailBuilt = true;
        // Recursively generate thumbnail requests.
        Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
        isThumbnailBuilt = false;
        coordinator.setRequests(fullRequest, thumbRequest);
        return coordinator;
    } else if (thumbSizeMultiplier != null) {
        // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
        ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
        Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
        Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
        coordinator.setRequests(fullRequest, thumbnailRequest);
        return coordinator;
    } else {
        // Base case: no thumbnail.
        return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
    }
}

首先是與縮略圖有關(guān)的邏輯,即是否調(diào)用thumbnail()方法躏筏,如果有縮略圖構(gòu)建兩個request板丽。如果沒有,調(diào)用obtainRequest()方法,obtainRequest()方法構(gòu)建了一個GenericRequest對象寸士。接下來將target與request綁定起來檐什,設(shè)置監(jiān)聽碴卧,調(diào)用RequestTracker的runRequest()方法弱卡。

RequestTracker是一個用來跟蹤、取消或重啟一個正在進(jìn)行住册、完成或失敗的請求婶博。


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

把請求加入請求隊列中,如果tracker沒有被停止荧飞,調(diào)用request的begin()方法凡人,如果停止了,就加入到準(zhǔn)備請求隊列中叹阔。

Request的實現(xiàn)類GenericRequest中的begin()方法:


@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));
    }
}

begin()方法主要確定請求圖片的大小挠轴,即有沒有調(diào)用override()方法,以及設(shè)置占位圖耳幢。如果沒有調(diào)用override方法岸晦,則調(diào)用target.getSize()方法(target父類ViewTarget):


public void getSize(SizeReadyCallback cb) {
    sizeDeterminer.getSize(cb);
}

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);
        }
    }
}

根據(jù)view的寬高以及LayoutParam得到圖片寬高欧啤,調(diào)用SizeReadyCallback的onSizeReady()方法。GenericRequest實現(xiàn)了SizeReadyCallback接口启上,onSizeReady方法與重寫了圖片寬高所調(diào)用的onSizeReady()方法一致邢隧。


@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));
    }
}

計算過寬高之后通過loadProvider得到當(dāng)前ModelLoader。那么loadProvider從哪來呢冈在?在DrawableTypeRequest的構(gòu)造函數(shù)看到一個buildProvider()方法:


buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class,
                        GlideDrawable.class, null),
                glide, requestTracker, lifecycle);

private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide,
        ModelLoader<A, InputStream> streamModelLoader,
        ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader, Class<Z> resourceClass,
        Class<R> transcodedClass,
        ResourceTranscoder<Z, R> transcoder) {
    if (streamModelLoader == null && fileDescriptorModelLoader == null) {
        return null;
    }

    if (transcoder == null) {
        transcoder = glide.buildTranscoder(resourceClass, transcodedClass);
    }
    DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider = glide.buildDataProvider(ImageVideoWrapper.class,
            resourceClass);
    ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader,
            fileDescriptorModelLoader);
    return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>(modelLoader, transcoder, dataLoadProvider);
}

buildTranscoder()方法獲取將GifBitmapWrapper轉(zhuǎn)換成GlideDrawable的Transcoder倒慧,DataLoadProvider提供指定的類型的Data和Resource之間的編解碼,
在這里獲取的是ImageVideoWrapper與GlideDrawable之間的編解碼provider包券。ImageVideoModelLoader是streamModelLoader和fileDescriptorModelLoader的封裝纫谅。

回到onSizeReady()方法中,從loadProvider中獲取到ImageVideoModelLoader以及ResourceTranscoder溅固,并從ImageVideoModelLoader中獲取到一個ImageVideoFetcher系宜。調(diào)用Engine中的load()方法:


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);
}

loadFromCache()、loadFromActiveResources()指從緩存中讀取发魄,以后會展開分析盹牧。接著往下分析,根據(jù)生成的key查詢?nèi)蝿?wù)是否已經(jīng)存在励幼,如果存在汰寓,返回一個LoadStatus。LoadStatus指一個不再加載的請求苹粟。如果不存在有滑,新建一個。并創(chuàng)建一個DecodeJob和EngineRunnable嵌削,調(diào)用EngineJob的start()方法

EngineJob 負(fù)責(zé)統(tǒng)一管理加載請求的 ResourceCallback毛好,EngineJob 本身也實現(xiàn)了 ResourceCallback 接口,當(dāng)加載請求完成時 EngineRunnable 回調(diào) EngineJob 的 onResourceReady() 方法苛秕,EngineJob 在分發(fā)給所有的監(jiān)聽者肌访。DecodeJob 的工作特別繁重,負(fù)責(zé)了 Resource 的所有解碼工作艇劫,包括從 Data 解碼和從緩存解碼吼驶,同時承擔(dān)了解碼之后的轉(zhuǎn)換和轉(zhuǎn)碼工作。

start()方法內(nèi)部實現(xiàn)是把EngineRunnable提交給線程池店煞,由線程池調(diào)用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);
    }
}


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

private boolean isDecodingFromCache() {
    return stage == Stage.CACHE;
}

private Resource<?> decodeFromCache() throws Exception {
    Resource<?> result = null;
    try {
        result = decodeJob.decodeResultFromCache();
    } catch (Exception e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Exception decoding result from cache: " + e);
        }
    }

    if (result == null) {
        result = decodeJob.decodeSourceFromCache();
    }
    return result;
}

private Resource<?> decodeFromSource() throws Exception {
    return decodeJob.decodeFromSource();
}

默認(rèn)的stage是從cache中取的蟹演,所以decode()方法中調(diào)用decodeFromCache(),如果在decodeFromCache中得到的result為空顷蟀,會走onLoadFailed()邏輯:


private void onLoadFailed(Exception e) {
    if (isDecodingFromCache()) {
        stage = Stage.SOURCE;
        manager.submitForSource(this);
    } else {
        manager.onException(e);
    }
}

@Override
public void submitForSource(EngineRunnable runnable) {
    future = sourceService.submit(runnable);
}

如果從緩存中取不到酒请,則更改策略,從source中取鸣个。第二次調(diào)用decodeFromSource():


private Resource<?> decodeFromSource() throws Exception {
    return decodeJob.decodeFromSource();
}

public Resource<Z> decodeFromSource() throws Exception {
    Resource<T> decoded = decodeSource();
    return transformEncodeAndTranscode(decoded);
}

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;
}

最終調(diào)用的是DecodeJob中的decodeSource()方法羞反。通過fetcher的loadData()方法獲取數(shù)據(jù)哮兰,這里的fetcher就是loadProvider中的ImageVideoFetcher:



@Override
 public ImageVideoWrapper loadData(Priority priority) throws Exception {
     InputStream is = null;
     if (streamFetcher != null) {
         try {
             is = streamFetcher.loadData(priority);
         } catch (Exception e) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "Exception fetching input stream, trying ParcelFileDescriptor", e);
             }
             if (fileDescriptorFetcher == null) {
                 throw e;
             }
         }
     }
     ParcelFileDescriptor fileDescriptor = null;
     if (fileDescriptorFetcher != null) {
         try {
             fileDescriptor = fileDescriptorFetcher.loadData(priority);
         } catch (Exception e) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "Exception fetching ParcelFileDescriptor", e);
             }
             if (is == null) {
                 throw e;
             }
         }
     }
     return new ImageVideoWrapper(is, fileDescriptor);
 }

從streamFetcher中獲取InputStream,此時的streamFetcher是HttpUrlFetcher對象苟弛,調(diào)用它的loadData()方法:


@Override
public InputStream loadData(Priority priority) throws Exception {
   return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
}

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
       throws IOException {
   if (redirects >= MAXIMUM_REDIRECTS) {
       throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
   } else {
       // Comparing the URLs using .equals performs additional network I/O and is generally broken.
       // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
       try {
           if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
               throw new IOException("In re-direct loop");
           }
       } catch (URISyntaxException e) {
           // Do nothing, this is best effort.
       }
   }
   urlConnection = connectionFactory.build(url);
   for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
     urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
   }
   urlConnection.setConnectTimeout(2500);
   urlConnection.setReadTimeout(2500);
   urlConnection.setUseCaches(false);
   urlConnection.setDoInput(true);

   // Connect explicitly to avoid errors in decoders if connection fails.
   urlConnection.connect();
   if (isCancelled) {
       return null;
   }
   final int statusCode = urlConnection.getResponseCode();
   if (statusCode / 100 == 2) {
       return getStreamForSuccessfulRequest(urlConnection);
   } else if (statusCode / 100 == 3) {
       String redirectUrlString = urlConnection.getHeaderField("Location");
       if (TextUtils.isEmpty(redirectUrlString)) {
           throw new IOException("Received empty or null redirect url");
       }
       URL redirectUrl = new URL(url, redirectUrlString);
       return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
   } else {
       if (statusCode == -1) {
           throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
       }
       throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
   }


   private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
         throws IOException {
     if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
         int contentLength = urlConnection.getContentLength();
         stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
     } else {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
         }
         stream = urlConnection.getInputStream();
     }
     return stream;
 }
}

根據(jù)HttpUrlConnection獲取InputStream喝滞,封裝成ImageVideoWrapper并返回「囡回到decodeFromSourceData()方法:

private Resource<T> decodeFromSourceData(A data) throws IOException {
    final Resource<T> decoded;
    if (diskCacheStrategy.cacheSource()) {
        decoded = cacheAndDecodeSourceData(data);
    } else {
        long startTime = LogTime.getLogTime();
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Decoded from source", startTime);
        }
    }
    return decoded;
}

首先進(jìn)行緩存操作右遭,然后從loadeProvider中獲取Decoder,進(jìn)行decode操作缤削。這里得到的Decoder是GifBitmapWrapperDrawableTranscoder對象窘哈,調(diào)用它的decode()方法:


@Override
public Resource<GifBitmapWrapper> decode(ImageVideoWrapper source, int width, int height) throws IOException {
    ByteArrayPool pool = ByteArrayPool.get();
    byte[] tempBytes = pool.getBytes();

    GifBitmapWrapper wrapper = null;
    try {
        wrapper = decode(source, width, height, tempBytes);
    } finally {
        pool.releaseBytes(tempBytes);
    }
    return wrapper != null ? new GifBitmapWrapperResource(wrapper) : null;
}

private GifBitmapWrapper decode(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException {
    final GifBitmapWrapper result;
    if (source.getStream() != null) {
        result = decodeStream(source, width, height, bytes);
    } else {
        result = decodeBitmapWrapper(source, width, height);
    }
    return result;
}

private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes)
        throws IOException {
    InputStream bis = streamFactory.build(source.getStream(), bytes);
    bis.mark(MARK_LIMIT_BYTES);
    ImageHeaderParser.ImageType type = parser.parse(bis);
    bis.reset();

    GifBitmapWrapper result = null;
    if (type == ImageHeaderParser.ImageType.GIF) {
        result = decodeGifWrapper(bis, width, height);
    }
    // Decoding the gif may fail even if the type matches.
    if (result == null) {
        // We can only reset the buffered InputStream, so to start from the beginning of the stream, we need to
        // pass in a new source containing the buffered stream rather than the original stream.
        ImageVideoWrapper forBitmapDecoder = new ImageVideoWrapper(bis, source.getFileDescriptor());
        result = decodeBitmapWrapper(forBitmapDecoder, width, height);
    }
    return result;
}

private GifBitmapWrapper decodeGifWrapper(InputStream bis, int width, int height) throws IOException {
    GifBitmapWrapper result = null;
    Resource<GifDrawable> gifResource = gifDecoder.decode(bis, width, height);
    if (gifResource != null) {
        GifDrawable drawable = gifResource.get();
        // We can more efficiently hold Bitmaps in memory, so for static GIFs, try to return Bitmaps
        // instead. Returning a Bitmap incurs the cost of allocating the GifDrawable as well as the normal
        // Bitmap allocation, but since we can encode the Bitmap out as a JPEG, future decodes will be
        // efficient.
        if (drawable.getFrameCount() > 1) {
            result = new GifBitmapWrapper(null /*bitmapResource*/, gifResource);
        } else {
            Resource<Bitmap> bitmapResource = new BitmapResource(drawable.getFirstFrame(), bitmapPool);
            result = new GifBitmapWrapper(bitmapResource, null /*gifResource*/);
        }
    }
    return result;
}

內(nèi)部調(diào)用了decodeStream()將InputStream轉(zhuǎn)成圖片。首先讀取stream前兩個字節(jié)判讀是否為Gif圖亭敢,如果是Gif滚婉,調(diào)用decodeGifWrapper(),如果不是調(diào)用decodeBitmapWrapper():


private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
    GifBitmapWrapper result = null;

    Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height);
    if (bitmapResource != null) {
        result = new GifBitmapWrapper(bitmapResource, null);
    }

    return result;
}

方法內(nèi)部調(diào)用了bitmapDecoder的decode方法,生成一個Resource對象帅刀,并封裝成GifBitmapWrapper让腹。這里的bitmapDecoder實際是ImageVideoBitmapDecoder對象,調(diào)用它的decode方法扣溺,最終調(diào)用了Downsampler的decode()方法:


public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {
    final ByteArrayPool byteArrayPool = ByteArrayPool.get();
    final byte[] bytesForOptions = byteArrayPool.getBytes();
    final byte[] bytesForStream = byteArrayPool.getBytes();
    final BitmapFactory.Options options = getDefaultOptions();

    RecyclableBufferedInputStream bufferedStream = new RecyclableBufferedInputStream(
            is, bytesForStream);
    ExceptionCatchingInputStream exceptionStream =
            ExceptionCatchingInputStream.obtain(bufferedStream);

    MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
    try {
        exceptionStream.mark(MARK_POSITION);
        int orientation = 0;
        try {
            orientation = new ImageHeaderParser(exceptionStream).getOrientation();
        } catch (IOException e) {
            if (Log.isLoggable(TAG, Log.WARN)) {
                Log.w(TAG, "Cannot determine the image orientation from header", e);
            }
        } finally {
            try {
                exceptionStream.reset();
            } catch (IOException e) {
                if (Log.isLoggable(TAG, Log.WARN)) {
                    Log.w(TAG, "Cannot reset the input stream", e);
                }
            }
        }

        options.inTempStorage = bytesForOptions;

        final int[] inDimens = getDimensions(invalidatingStream, bufferedStream, options);
        final int inWidth = inDimens[0];
        final int inHeight = inDimens[1];

        final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
        final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);

        final Bitmap downsampled =
                downsampleWithSize(invalidatingStream, bufferedStream, options, pool, inWidth, inHeight, sampleSize,
                        decodeFormat);

        final Exception streamException = exceptionStream.getException();
        if (streamException != null) {
            throw new RuntimeException(streamException);
        }

        Bitmap rotated = null;
        if (downsampled != null) {
            rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation);

            if (!downsampled.equals(rotated) && !pool.put(downsampled)) {
                downsampled.recycle();
            }
        }

        return rotated;
    } finally {
        byteArrayPool.releaseBytes(bytesForOptions);
        byteArrayPool.releaseBytes(bytesForStream);
        exceptionStream.release();
        releaseOptions(options);
    }
}

自此生成了需要的bitmap骇窍。

回到decodeFromSource()方法,接下來調(diào)用transformEncodeAndTranscode(decoded)方法:


private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
    long startTime = LogTime.getLogTime();
    Resource<T> transformed = transform(decoded);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Transformed resource from source", startTime);
    }

    writeTransformedToCache(transformed);

    startTime = LogTime.getLogTime();
    Resource<Z> result = transcode(transformed);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Transcoded transformed from source", startTime);
    }
    return result;
}

如果由transform锥余,調(diào)用transform方法腹纳,接著把轉(zhuǎn)換后的resource寫入到緩存。經(jīng)過一系列編解碼驱犹,最終回到了EngineRunnable的run()方法嘲恍,調(diào)用onLoadComplete():


private void onLoadComplete(Resource resource) {
    manager.onResourceReady(resource);
}

manager是一個EngineRunnableManager接口,它的實現(xiàn)類是EngineJob雄驹,進(jìn)入EngineJob的onResourceReady()方法:


@Override
public void onResourceReady(final Resource<?> resource) {
    this.resource = resource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}

private void handleResultOnMainThread() {
     if (isCancelled) {
         resource.recycle();
         return;
     } else if (cbs.isEmpty()) {
         throw new IllegalStateException("Received a resource without any callbacks to notify");
     }
     engineResource = engineResourceFactory.build(resource, isCacheable);
     hasResource = true;

     // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it
     // synchronously released by one of the callbacks.
     engineResource.acquire();
     listener.onEngineJobComplete(key, engineResource);

     for (ResourceCallback cb : cbs) {
         if (!isInIgnoredCallbacks(cb)) {
             engineResource.acquire();
             cb.onResourceReady(engineResource);
         }
     }
     // Our request is complete, so we can release the resource.
     engineResource.release();
 }

onResourceReady()中發(fā)出一條消息佃牛,在handleResultOnMainThread()中調(diào)用了ResourceCallback的onResourceReady()方法,GenericRequest實現(xiàn)了ResourceCallback:

@Override
public void onResourceReady(Resource<?> resource) {
    if (resource == null) {
        onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass
                + " inside, but instead got null."));
        return;
    }

    Object received = resource.get();
    if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
        releaseResource(resource);
        onException(new Exception("Expected to receive an object of " + transcodeClass
                + " but instead got " + (received != null ? received.getClass() : "") + "{" + received + "}"
                + " inside Resource{" + resource + "}."
                + (received != null ? "" : " "
                    + "To indicate failure return a null Resource object, "
                    + "rather than a Resource object containing null data.")
        ));
        return;
    }

    if (!canSetResource()) {
        releaseResource(resource);
        // We can't set the status to complete before asking canSetResource().
        status = Status.COMPLETE;
        return;
    }

    onResourceReady(resource, (R) received);
}

private void onResourceReady(Resource<?> resource, R result) {
    // We must call isFirstReadyResource before setting status.
    boolean isFirstResource = isFirstReadyResource();
    status = Status.COMPLETE;
    this.resource = resource;

    if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
            isFirstResource)) {
        GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
        target.onResourceReady(result, animation);
    }

    notifyLoadSuccess();

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
                + (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
    }
}


調(diào)用了target.onResourceReady(result, animation)荠医,target就是我們前面所提到的把ImageView封裝成的ImageViewTarget:


@Override
public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
    if (glideAnimation == null || !glideAnimation.animate(resource, this)) {
        setResource(resource);
    }
}

protected abstract void setResource(Z resource);

以BitmapImageViewTarget為例:

@Override
protected void setResource(Bitmap resource) {
    view.setImageBitmap(resource);
}

最終圖片顯示出來吁脱。

參考文章

Glide源碼解析(一):加載流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桑涎,一起剝皮案震驚了整個濱河市彬向,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌攻冷,老刑警劉巖娃胆,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異等曼,居然都是意外死亡里烦,警方通過查閱死者的電腦和手機(jī)凿蒜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胁黑,“玉大人废封,你說我怎么就攤上這事∩フ海” “怎么了漂洋?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長力喷。 經(jīng)常有香客問我刽漂,道長,這世上最難降的妖魔是什么弟孟? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任贝咙,我火速辦了婚禮,結(jié)果婚禮上拂募,老公的妹妹穿的比我還像新娘庭猩。我一直安慰自己,他們只是感情好陈症,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布眯娱。 她就那樣靜靜地躺著,像睡著了一般爬凑。 火紅的嫁衣襯著肌膚如雪徙缴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天嘁信,我揣著相機(jī)與錄音于样,去河邊找鬼。 笑死潘靖,一個胖子當(dāng)著我的面吹牛穿剖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播卦溢,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼糊余,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了单寂?” 一聲冷哼從身側(cè)響起贬芥,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宣决,沒想到半個月后蘸劈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡尊沸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年威沫,在試婚紗的時候發(fā)現(xiàn)自己被綠了贤惯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡棒掠,死狀恐怖孵构,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布裆站,位于F島的核電站,受9級特大地震影響精盅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谜酒,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一叹俏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧僻族,春花似錦粘驰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至度秘,卻和暖如春顶伞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剑梳。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工唆貌, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人垢乙。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓锨咙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親追逮。 傳聞我的和親對象是個殘疾皇子酪刀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

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