Glide源碼閱讀

聲明

本文章是基于 glide 3.6.1的

個(gè)各類的功能介紹

1.Glide:向外暴露單例靜態(tài)接口,構(gòu)建Request.配置資源類型,緩存策略,圖片處理等,可以直接通過該類完整簡單的圖片請(qǐng)求和填充,內(nèi)部持有一些變量BitmapPool,MemoryCache,ByteArrayPool,便于低內(nèi)存情況時(shí)自動(dòng)清理內(nèi)存

2.RequestManagerRetriever: (RequestManager的獲取類 單例類)提供靜態(tài)方法去創(chuàng)建新的RequestManager 或者從Activity或者fragment中檢索已經(jīng)存在的請(qǐng)求

3.RequestManager:(管理請(qǐng)求的類 實(shí)現(xiàn)了LiftcycleListener) 可以依賴Activity和Fragment的聲明周期進(jìn)行請(qǐng)求的start,stop,reStart等操作

4.RequestManagerFragment:(用于竊聽Activity,Fragment頁面的生命周期變化的)一個(gè)沒有視圖的Fragment,內(nèi)部有RequestManager這個(gè)成員變量.每一個(gè)context(Activity或者Fragment)都會(huì)擁有一個(gè)RequestManagerFragment,用于監(jiān)聽Activity或者Fragment生命周期的變化,從而調(diào)用ActivityFragmentLifecycle的方法,當(dāng)內(nèi)存不足時(shí)會(huì)調(diào)用RequestManager的相關(guān)方法

5.ActivityFragmentLifecycle:(實(shí)現(xiàn)了Liftcycle接口)用于監(jiān)聽Activity和Fragment生命周期活動(dòng)的實(shí)現(xiàn)類.

6.DrawableTypeRequest:(繼承了DrawableRequestBuilder)一個(gè)用來創(chuàng)建加載請(qǐng)求的類,可以直接加載GIF動(dòng)畫,也可以直接加載Bitmap或者是資源文件

7.DrawableRequestBuilder:(繼承了GenericRequestBuilder) 創(chuàng)建請(qǐng)求GlideDrawable請(qǐng)求的類

8.GenericRequestBuilder:就是一個(gè)通用類,實(shí)現(xiàn)了一些請(qǐng)求通用的方法

9.GenericRequest:(實(shí)現(xiàn)了Request接口)這是一個(gè)請(qǐng)求的封裝類,會(huì)將資源加載到指定的目標(biāo)身上

10.RequestTracker:用于跟蹤,取消,重啟 完成和失敗請(qǐng)求的類,也就是說這個(gè)類可以對(duì)請(qǐng)求進(jìn)行開啟和暫停等操作

11.DataLoadProvider:(是一個(gè)接口) 一個(gè)負(fù)載提供程序,它提供了必要的編碼器和解碼器提完,以便從特定類型的數(shù)據(jù)中解碼特定類型的資源形纺。也就是一個(gè)內(nèi)部集成了不同情況下的編解碼器

12.LoadProvider:(是一個(gè)接口,他繼承了DataLoadProvider)其實(shí)就是DataLoadProvide的擴(kuò)展,在DataLoadProvide的基礎(chǔ)上也支持了ModelLoader和ResourceTranscoder

13.ModelLoader:(是一個(gè)接口) 用具將復(fù)雜的數(shù)據(jù)類型轉(zhuǎn)換為具體的數(shù)據(jù)類型,并且可以使用DataFetcher來獲取資源數(shù)據(jù)--->ModelLoader 是一個(gè)工廠接口。將任意復(fù)雜的 model 轉(zhuǎn)換為準(zhǔn)確具體的可以被 DataFetcher 獲取的數(shù)據(jù)類型徒欣。 每一個(gè) model 內(nèi)部實(shí)現(xiàn)了一個(gè) ModelLoaderFactory逐样,內(nèi)部實(shí)現(xiàn)就是將 model 轉(zhuǎn)換為 Data

14.DataFetcher:(是一個(gè)接口) 就是加載器的頂級(jí)接口

15.Engine:負(fù)責(zé)啟動(dòng)下載,管理活動(dòng)和緩存資源的類-->任務(wù)創(chuàng)建,發(fā)起打肝,回調(diào)脂新,管理存活和緩存的資源

16.EngineJob:(負(fù)責(zé)加載每一個(gè)請(qǐng)求,它里面有線程池成員變量)下載的管理類,也就是Engine的管理類--->將線程回調(diào)到主線程的類

17.EngineRunnable:(實(shí)現(xiàn)了Runnable接口) 就是一個(gè)任務(wù),在下載完成后或者找到緩存圖片后進(jìn)行解碼

18.DecodeJob:調(diào)度任務(wù)的核心類闯睹,整個(gè)請(qǐng)求的繁重工作都在這里完成,處理來自緩存或者原始的資源戏羽,應(yīng)用轉(zhuǎn)換動(dòng)畫以及 transcode

19.HttpUrlFetcher:(實(shí)現(xiàn)了DataFetcher) 該類是專門從網(wǎng)絡(luò)中加載圖片的加載器.

20.EngineRunnableManager:(是一個(gè)接口,繼承了ResourceCallback)用于監(jiān)聽圖片獲取成功的監(jiān)聽器,

21.ResourceCallback:(是一個(gè)接口)用于監(jiān)聽資源是否加載成功或者失敗的回到

22.Target:(是一個(gè)接口)request 的載體担神,各種資源對(duì)應(yīng)的加載類楼吃,含有生命周期的回調(diào)方法,方便開發(fā)人員進(jìn)行相應(yīng)的準(zhǔn)備以及資源回收工作

23.Resource:(是一個(gè)接口)對(duì)資源進(jìn)行包裝的接口妄讯,提供 get孩锡,recycle,getSize亥贸,以及原始類的 getResourceClass 方法躬窜。 其實(shí)現(xiàn)類都在resource包下,resource 包下也就是各種資源:bitmap,bytes炕置,drawable荣挨,file,gif朴摊,以及相關(guān)解碼器默垄,轉(zhuǎn)換器

Glide.with(this).load("...imageurl..").into(iv);這段代碼內(nèi)部調(diào)用流程如下

首先上段代碼可以分解為

    RequestManager requestManager = Glide.with(this);
    DrawableTypeRequest<String> drawableTypeRequest = requestManager .load("...imageurl..");
    drawableTypeRequest .into(iv);

首先我們先來看看 RequestManager requestManager = Glide.with(this); 這段代碼

1.Glide.with()方法中會(huì)調(diào)用RequestManagerRetriever的靜態(tài)get()方法獲取到RequestManagerRetriever的實(shí)例,然后調(diào)用RequestManagerRetriever的get()方法獲取到RequestManager的實(shí)例并返回

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

2.在RequestManagerRetriever的get()方法中有一個(gè)重要的操作就是給當(dāng)前的activity也就是Glide.with()方法中傳入的activity添加一個(gè)SupportRequestManagerFragment(繼承自Fragment TAG 為"com.bumptech.glide.manager")用于監(jiān)控傳入activity的聲明周期變化,從而讓Glide的操作達(dá)到和Activity的聲明周期一致的狀態(tài).還有一個(gè)重要的操作就是在RequestManager的構(gòu)造方法中將ActivityFragmentLifecycle和Lifecycle(RequestManager)進(jìn)行了綁定,也就是說將傳入Activity的生命周期傳入到了RequestManager中(這個(gè)過程下面有具體的分析) ** RequestManager requestManager = Glide.with(this); **

public RequestManager get(FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
        return get(activity.getApplicationContext());
    } else {
        assertNotDestroyed(activity);
        //獲取到的當(dāng)前Activity的FragmentManager并傳入給下面方法
        FragmentManager fm = activity.getSupportFragmentManager();
        return supportFragmentGet(activity, fm);
    }
}


RequestManager supportFragmentGet(Context context, FragmentManager fm) {
    //獲取到SupportRequestManagerFragment實(shí)例并使用FragmentManager添加到當(dāng)前Activity中
    SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        //創(chuàng)建RequestManager并設(shè)置到SupportRequestManagerFragment中保存起來
        requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}


RequestManager(Context context, final Lifecycle lifecycle, RequestManagerTreeNode treeNode,
        RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
    this.context = context.getApplicationContext();
    this.lifecycle = lifecycle;
    this.treeNode = treeNode;
    this.requestTracker = requestTracker;
    this.glide = Glide.get(context);
    this.optionsApplier = new OptionsApplier();

    ConnectivityMonitor connectivityMonitor = factory.build(context,
            new RequestManagerConnectivityListener(requestTracker));

        // 在這里將傳入的Activity的生命周期傳到了RequestManager身上
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                lifecycle.addListener(RequestManager.this);
            }
        });
    } else {
        lifecycle.addListener(this);
    }
    lifecycle.addListener(connectivityMonitor);
}

看完這些代碼以后我們來捋一捋到底R(shí)equestManager是如何獲取到傳入的Activity的生命周期的,

首先在執(zhí)行RequestManagerRetriever的get()方法的時(shí)候會(huì)給傳入的Activity設(shè)置一個(gè)沒有視圖的Fragment也就是SupportRequestManagerFragment,在SupportRequestManagerFragment中有一個(gè)成員變量是ActivityFragmenLifecycle它實(shí)現(xiàn)了Lifecycle接口是專門用來監(jiān)視SupportRequestmanagerFragment的生命周期的,其實(shí)這個(gè)聲明周期也就是傳入的Activity的生命周期.在ActivityFragmentLifecycle中有一個(gè)存儲(chǔ)著LifecycleListener的Set集合.當(dāng)Activity的生命周期方法被調(diào)用的時(shí)候在SupportRequestManagerFragment中也會(huì)調(diào)用ActivityFragmentLifecycle的方法,然后ActivityFragmentLifecycle在調(diào)用LifecycleListener的方法,而剛好RequestManager實(shí)現(xiàn)了LifecycleListener這個(gè)接口,所以從這里生命周期就傳到了RequestManager的身上.

在這里ActivityFragmentLifecycle是一個(gè)被觀察者,RequestManager是一個(gè)觀察者他們的綁定是在RequestManager的構(gòu)造方法中.

    .....
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            lifecycle.addListener(RequestManager.this);
        }
    });
    .....

如下圖所示

glide獲取聲明周期流程圖.jpg

下來我們?cè)倏纯? DrawableTypeRequest<String> drawableTypeRequest = requestManager .load("...imageurl.."); 這段代碼

1.這里首先調(diào)用了RequestManager的load()方法,然后在RequestManager中調(diào)用fromString()方法,然后在fromString()中調(diào)用loadGeneric()方法創(chuàng)建一個(gè)DrawableTypeRequest,然后右調(diào)用了DrawableTypeRequest方法并返回

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

@Override
public DrawableRequestBuilder<ModelType> load(ModelType model) {
    super.load(model);
    return this;
}

其實(shí)load()方法干的事兒很簡單,就是根據(jù)load()方法中傳入的不同類型的值,來生成不同的DrawableTypeRequest并進(jìn)行返回

最后我們來看看 drawableTypeRequest .into(iv); 這段代碼

1.首先會(huì)調(diào)用DrawableTypeRequest中的into方法,其實(shí)是DrawableRequestBuilder的into方法,因?yàn)镈rawableTypeRequest繼承了DrawableRequestBuilder而且沒有重寫into方法,然后在調(diào)用DrawableRequestBuilder的父類GenericRequestBuilder的into方法,然后在調(diào)用GenericRequestBuilder中的into的重載方法,在這個(gè)into方法中首先會(huì)判斷 傳入的ImageView是否已經(jīng)攜帶請(qǐng)求,如果攜帶請(qǐng)求就將這個(gè)請(qǐng)求清除掉,再加上新的請(qǐng)求,(這就解決了listView中圖片錯(cuò)位的現(xiàn)象).

2.下來就會(huì)調(diào)用buildRequest()方法去創(chuàng)建一個(gè)請(qǐng)求

3.buildRequest()方法中會(huì)調(diào)用buildRequestRecursive()方法去創(chuàng)建一個(gè)請(qǐng)求

4.buildRequestRecursive()方法中回到用obtainRequest()方法去真正的創(chuàng)建一個(gè)請(qǐng)求在該方法中回去判斷是否先加載隨略圖,如果是則會(huì)創(chuàng)建一個(gè)載縮略圖請(qǐng)求和一個(gè)原始尺寸的請(qǐng)求否則只創(chuàng)建原始尺寸的請(qǐng)求

5.obtainRequest()方法中調(diào)用 GenericRequest中obtain()方法

6.在obtain()方法中會(huì)調(diào)用init()方法對(duì)請(qǐng)求進(jìn)行初始化 這一上2-6步驟都是在創(chuàng)建一個(gè)請(qǐng)求.

7.在執(zhí)行完buildRequest()方法以后,下面就執(zhí)行了RequestTracker 的runRequest(Request request)方法

8.在runRequest(Request request)方法中 調(diào)用了Request的begin()方法,因?yàn)镚enericRequest就是實(shí)現(xiàn)了Request接口,所以也就是GenericRequest的begin()方法

9.在begin()方法中執(zhí)行了onSizeReady()方法

10.onSizeReady()方法中首先調(diào)用ModelLoader.getResourceFetcher發(fā)方法去獲取對(duì)應(yīng)資源的加載器,然后調(diào)用Engine.load()方法開始加載

11.Engine.load()方法也是先從內(nèi)存中找 ,然后在本地找最后在從網(wǎng)絡(luò)下載,執(zhí)行下載流程的話首先會(huì)獲取到EngineJob的實(shí)例

12.創(chuàng)建一個(gè)工作線程 EngineRunnable用于解碼下載得到的流或者是緩存中找的的圖片

13.將EngineRunnable添加到EngineJob中開始執(zhí)行

14.EngineRunnable既然已經(jīng)開始執(zhí)行那我們就來看看他的run方法,在run方法中首先會(huì)調(diào)用decode()方法,在decode方法中首先會(huì)判斷是否從緩存中找,我們這里直接看在網(wǎng)絡(luò)中下載的流程,所以這里走的是decodeFromSource()

15.decodeFromSource()中又調(diào)用了DecodeJob的decodeFromSource()方法

16.在DecodeFromSource方法中又調(diào)用了decodeSource()方法

17.decodeSource()方法中調(diào)用了一個(gè)重要的方法,fetcher.loadData()這個(gè)方法就是從不同的途徑中找到我們要的圖片,比如緩存或者網(wǎng)絡(luò),這里的fetcher就是HttpUrlFetcher

18.HttpUrlFetcher的loadData()方法會(huì)調(diào)用loadDataWithRedirects()方法,在這個(gè)方法中我們看到了久違的HttpUrlConnection,也就是說Glide這個(gè)框架是從這里進(jìn)行聯(lián)網(wǎng)請(qǐng)求圖片的,最后這個(gè)方法返回一個(gè)流對(duì)象,然后loadData()也返回了一個(gè)流對(duì)象到DecodeJob的decodeSource()方法中

19.然后在DecodeJob的decodeSource()中對(duì)返回的這個(gè)流對(duì)象使用decodeFromSourceData()方法進(jìn)行包裝,包裝成Resource對(duì)象進(jìn)行返回,返回到DecodeJob的decodeFromSource()方法中.

20DecodeJob的decodeFromSource()方法中再調(diào)用transformEncodeAndTranscode()方法.

21.在transformEncodeAndTranscode()方法中會(huì)調(diào)用writeTransformedToCache()方法將獲得的流對(duì)象緩存到硬盤中.

22.緩存完以后調(diào)用transcode()方法將獲得的流對(duì)象進(jìn)行轉(zhuǎn)化然后進(jìn)行返回,返回到EngineRunnabl的decodeFormSource()中.

24.然后在返回到EngineRunnable的Decode()方法中

25.然后再返回到EngineRunnable的run()方法中

26.然后在run()方法中執(zhí)行onLoadComplete()方法

27.然后執(zhí)行了EngineRunnableManager的onResourceReady()方法,因?yàn)閷?shí)現(xiàn)了EngineRunnableManager接口的只有EngineJob,所以這里執(zhí)行的是EngineJob的OnResourceReady()方法

28.在EngineJob的OnResourceReady()方法中做了一件重要的事情,就是使用主線程的Handler將線程切換到主線程中

29.然后進(jìn)入到了EngineJob的MainTreadCallback類的handleMessage()方法中,在加載成功的情況下回執(zhí)行EngineJob中的handleResultOnMainThread()方法

30.在EngineJob的handleResultOnMainThread()方法中會(huì)循環(huán)調(diào)用ResourceCallBack的OnResourceReady()方法,因?yàn)镚enericRequest實(shí)現(xiàn)了RequestCallback接口,所以就執(zhí)行了GenericRequest的onResourceReady()方法

31.在GenericRequest的onResourceReady()方法中調(diào)用了Target的onResourceReady()方法,因?yàn)檫@里我們傳入的是ImageView,所以這里的Target是ImageViewTarget,所以也就是ImageViewTarget的onResourceReady.

32.ImageViewTarget的onResourceReady中有調(diào)用了setResource()方法這是一個(gè)抽象方法,所以我們隨便看一個(gè)他的子類是怎么實(shí)現(xiàn)的,比如DrawableImageViewTarget中就是調(diào)用 view.setImageDrawable(resource); 方法將Bitmap設(shè)置到ImageView中.到此為止整個(gè)加載流程就完畢了.

//DrawableRequestBuilder中
@Override
public Target<GlideDrawable> into(ImageView view) {
    return super.into(view);
}



//GenericRequestBuilder中
public Target<TranscodeType> into(ImageView view) {
    ............
    return into(glide.buildImageViewTarget(view, transcodeClass));
}



//GenericRequestBuilder中
public <Y extends Target<TranscodeType>> Y into(Y target) {
    ..........
    Request previous = target.getRequest();
    //如果傳入的ImageView已經(jīng)攜帶請(qǐng)求就像這個(gè)請(qǐng)求給清除
    if (previous != null) {
        previous.clear();
        requestTracker.removeRequest(previous);
        previous.recycle();
    }

    //創(chuàng)建了一個(gè)請(qǐng)求
    Request request = buildRequest(target);
    target.setRequest(request);
    lifecycle.addListener(target);
    //開始執(zhí)行這個(gè)請(qǐng)求
    requestTracker.runRequest(request);

    return target;
}



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



//GenericRequestBuilder中
private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
    ..........
else if (thumbSizeMultiplier != null) {
        // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.

        ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
//創(chuàng)建一個(gè)原始尺寸的請(qǐng)求
        Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
//創(chuàng)建一個(gè)縮略圖請(qǐng)求
        Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
        coordinator.setRequests(fullRequest, thumbnailRequest);
        return coordinator;
    } else {
//去建造一個(gè)原始尺寸的請(qǐng)求
 return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
    ..........
}



//GenericRequestBuilder中
 private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
        RequestCoordinator requestCoordinator) {
//創(chuàng)建一個(gè)請(qǐng)求并對(duì)其初始化
    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);
}



//GenericRequest中
 public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(
        LoadProvider<A, T, Z, R> loadProvider,
        A model,
        Key signature,
        Context context,
        Priority priority,
        Target<R> target,
        float sizeMultiplier,
        Drawable placeholderDrawable,
        int placeholderResourceId,
        Drawable errorDrawable,
        int errorResourceId,
        Drawable fallbackDrawable,
        int fallbackResourceId,
        RequestListener<? super A, R> requestListener,
        RequestCoordinator requestCoordinator,
        Engine engine,
        Transformation<Z> transformation,
        Class<R> transcodeClass,
        boolean isMemoryCacheable,
        GlideAnimationFactory<R> animationFactory,
        int overrideWidth,
        int overrideHeight,
        DiskCacheStrategy diskCacheStrategy) {
    @SuppressWarnings("unchecked")
    GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
    if (request == null) {
//創(chuàng)建請(qǐng)求 
        request = new GenericRequest<A, T, Z, R>();
    }
//初始化請(qǐng)求
    request.init(loadProvider,
            model,
            signature,
            context,
            priority,
            target,
            sizeMultiplier,
            placeholderDrawable,
            placeholderResourceId,
            errorDrawable,
            errorResourceId,
            fallbackDrawable,
            fallbackResourceId,
            requestListener,
            requestCoordinator,
            engine,
            transformation,
            transcodeClass,
            isMemoryCacheable,
            animationFactory,
            overrideWidth,
            overrideHeight,
            diskCacheStrategy);
//將請(qǐng)求返回 最終返回到到GenericRequestBuilder的into()方法中,然后執(zhí)行  requestTracker.runRequest(request);這句代碼,開始執(zhí)行請(qǐng)求
    return request;
}



//RequestTracker中
public void runRequest(Request request) {
    requests.add(request);
    if (!isPaused) {
//開始執(zhí)行請(qǐng)求
        request.begin();
    } else {
        pendingRequests.add(request);
    }
}



//GenericRequest中
@Override
public void begin() {
   ......
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
//當(dāng)ImageView的大小已經(jīng)確定
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        target.getSize(this);
    }
   ......
}



//GenericRequest中
 @Override
public void onSizeReady(int width, int height) {
   ......
    ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
    final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);

   ......
    ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
   ......
    loadedFromMemoryCache = true;
    //開始加載圖片
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
            priority, isMemoryCacheable, diskCacheStrategy, this);
    loadedFromMemoryCache = resource != null;
   ......
}



//Engine中
 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);
//創(chuàng)建從網(wǎng)絡(luò)加載圖片的線程
    EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
//啟動(dòng)線程下載圖片,啟動(dòng)線程后就會(huì)進(jìn)入到EngineRunnable的run()方法
    engineJob.start(runnable);

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



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

    Exception exception = null;
    Resource<?> resource = null;
    try {
//進(jìn)行下載
        resource = decode();
    } catch (Exception e) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Exception decoding", e);
        }
        exception = e;
    }

    ........

    if (resource == null) {
        onLoadFailed(exception);
    } else {
//當(dāng)下載成功
        onLoadComplete(resource);
    }
}



//EngineRunnable中
private Resource<?> decode() throws Exception {
    if (isDecodingFromCache()) {
//從緩存中加載
        return decodeFromCache();
    } else {
//從網(wǎng)絡(luò)中加載
        return decodeFromSource();
    }
}



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



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


//DecodeJob中
private Resource<T> decodeSource() throws Exception {
    Resource<T> decoded = null;
    try {
        long startTime = LogTime.getLogTime();
//使用加載器去網(wǎng)絡(luò)中加載圖片
        final A data = fetcher.loadData(priority);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Fetched data", startTime);
        }
        if (isCancelled) {
            return null;
        }
//對(duì)結(jié)果進(jìn)行包裝
        decoded = decodeFromSourceData(data);
    } finally {
        fetcher.cleanup();
    }
    return decoded;
}



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



//HttpUrlFetcher中
//這里是真正使用HttpURLConnection下載圖片的地方,
//最后將結(jié)果返回,在經(jīng)過各種包裝最終返回到EngineRunnable中調(diào)用onLoadComplete(resource);執(zhí)行加載成功后的流程
 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());
    }
}



//EngineRunnable中
private void onLoadComplete(Resource resource) {\
    //這里的manager是EngineRunnableManager,實(shí)現(xiàn)了這個(gè)接口的只有EngineJob,也就是說調(diào)用的是EngineJob中國的onRequestReady()方法
    manager.onResourceReady(resource);
}



//EngineJob中
@Override
public void onResourceReady(final Resource<?> resource) {
    this.resource = resource;
//使用Handler將結(jié)果發(fā)送到主線程中 ,結(jié)果會(huì)在EngineJob中的內(nèi)部類MainThreadCallBack中收到       MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}



//EngineJob中
private static class MainThreadCallback implements Handler.Callback {

    @Override
    public boolean handleMessage(Message message) {
        if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
            EngineJob job = (EngineJob) message.obj;
            if (MSG_COMPLETE == message.what) {
//接收到成功的消息
                job.handleResultOnMainThread();
            } else {
//接收到失敗的消息
                job.handleExceptionOnMainThread();
            }
            return true;
        }

        return false;
    }
}



//EngineJob中
 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);

  //循環(huán)接口集合,調(diào)用接口的onResourceReady()方法,通知資源已經(jīng)加載完畢,因?yàn)镚enericRequest實(shí)現(xiàn)了該接口,
//所以GenericRequest的onResourceReady()方法會(huì)被電泳
    for (ResourceCallback cb : cbs) {
        if (!isInIgnoredCallbacks(cb)) {
            engineResource.acquire();
            cb.onResourceReady(engineResource);
        }
    }
    // Our request is complete, so we can release the resource.
    engineResource.release();
}


//GenericRequest中
 @Override
public void onResourceReady(Resource<?> resource) {

.......

    if (!canSetResource()) {
        releaseResource(resource);
        // We can't set the status to complete before asking canSetResource().
        status = Status.COMPLETE;
        return;
    }
    
//調(diào)用重載方法onResourceReady()
    onResourceReady(resource, (R) received);
}



//GenericRequest中
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);
    //調(diào)用Target子類的OnResourceReady()方法將圖片加載到ImageView上
        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);
    }
}



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

//BitmapImageViewTarget中
@Override
protected void setResource(Bitmap resource) {
  //調(diào)用Android API將資源加載到ImageView上 
    view.setImageBitmap(resource);
}

Glide Engine中into()方法的流程圖

Engine_into().jpg

Glide加載圖片的總流程圖

glide_uml.png
Glide.png
glide加載圖片流程.png

幾個(gè)問題

1.glide中的線程池的核心線程數(shù)有幾個(gè)
glide中線程池的初始化是在GlideBuilder中的createGlide中的,glide中的線程池有兩種,一個(gè)是sourceService他的核心線程數(shù)是CPU核心數(shù),最大線程數(shù)也是CPU核心數(shù),另一個(gè)是diskCacheService他的核心線程數(shù)是1,最大線程數(shù)也是1.為什么會(huì)核心線程數(shù)和最大線程數(shù)是一樣的?我們可以去看看FifoPriorityThreadPoolExecutor的構(gòu)造方法,這兩個(gè)線程池都是FifoPriorityThreadPoolExecutor,該線程池使用的是PriorityBlockingQueue這種阻塞隊(duì)列,該隊(duì)列是具有優(yōu)先級(jí)而且空間無限的隊(duì)列.所以也就是說這種線程池只有核心線程,是一種依賴優(yōu)先級(jí)的線程池,又來執(zhí)行g(shù)lide的加載,解碼和轉(zhuǎn)換任務(wù).sourceService和diskCacheService兩個(gè)線程池的區(qū)別是,sourceService是當(dāng)緩存中沒有展示的對(duì)象時(shí)使用,而diskCacheService是緩存中存在展示對(duì)象時(shí)使用

Glide createGlide() {
    if (sourceService == null) {
        final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
//依賴于優(yōu)先級(jí)的線程池,用來執(zhí)行Glide的加載甚纲,解碼和轉(zhuǎn)換任務(wù)(當(dāng)從緩存中沒有找到對(duì)應(yīng)的對(duì)象時(shí))
//線程數(shù)是CPU核心數(shù)
        sourceService = new FifoPriorityThreadPoolExecutor(cores);
    }
    if (diskCacheService == null) {
//依賴于優(yōu)先級(jí)的線程池口锭,用來執(zhí)行Glide的加載,解碼和轉(zhuǎn)換任務(wù)(當(dāng)從緩存中有對(duì)應(yīng)的對(duì)象時(shí))
//線程數(shù)量為1
        diskCacheService = new FifoPriorityThreadPoolExecutor(1);
    }
.....

public FifoPriorityThreadPoolExecutor(int poolSize) {
    this(poolSize, UncaughtThrowableStrategy.LOG);
}

/**
 * Constructor to build a fixed thread pool with the given pool size using
 * {@link com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor.DefaultThreadFactory}.
 *
 * @param poolSize The number of threads.
 * @param uncaughtThrowableStrategy Dictates how the pool should handle uncaught and unexpected throwables
 *                                  thrown by Futures run by the pool.
 */
public FifoPriorityThreadPoolExecutor(int poolSize, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
    this(poolSize, poolSize, 0, TimeUnit.MILLISECONDS, new DefaultThreadFactory(),
        uncaughtThrowableStrategy);
}

public FifoPriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAlive, TimeUnit timeUnit,
        ThreadFactory threadFactory, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
    super(corePoolSize, maximumPoolSize, keepAlive, timeUnit, new PriorityBlockingQueue<Runnable>(), threadFactory);
    this.uncaughtThrowableStrategy = uncaughtThrowableStrategy;
}

2.glide中圖片的默認(rèn)格式
圖片的默認(rèn)格式是RGB-565

//DecodeFormat中
/** The default value for DecodeFormat. */
public static final DecodeFormat DEFAULT = PREFER_RGB_565;

3.glide的加載策略,glide的加載策略也是按照 內(nèi)存->硬盤->網(wǎng)絡(luò)的順序來的,但是他比其他的框架做的更細(xì)化一些,首先他會(huì)去內(nèi)存中找,如果沒有他會(huì)從最近活躍的資源中找,然后還會(huì)去下載隊(duì)列中去找,最后才是去下載

  /** 
 *  1. 檢查內(nèi)存緩存
 *  2. 檢查最近的活躍資源
 *  3. 檢查最近的加載任務(wù)
 *  4.去網(wǎng)絡(luò)中下載
 *  活躍資源指的是那些不止一次被加載并沒有進(jìn)行過資源釋放的圖片介杆,一旦被釋放鹃操,
 *  那么該資源則會(huì)從近期活躍資源中刪除并進(jìn)入到內(nèi)存緩存中韭寸,
 *  但是如果該資源再次從內(nèi)存緩存中讀取,則會(huì)重新添加到活躍資源中
 */
public <R> LoadStatus load( 
GlideContext glideContext, 
Object model,    
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,    
Map<Class<?>, Transformation<?>> transformations,    
boolean isTransformationRequired,    
Options options,    
boolean isMemoryCacheable,    
ResourceCallback cb) {  

Util.assertMainThread();  
long startTime = LogTime.getLogTime();  
//內(nèi)存緩存的唯一鍵值
EngineKey key = keyFactory.buildKey(model, signature,     width, height, transformations, resourceClass,   transcodeClass, options); 
//首先從緩存中查找
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);  
if (cached != null) {    
 cb.onResourceReady(cached,   DataSource.MEMORY_CACHE);    
...  
  return null;  
}  
//如果緩存中沒有找到荆隘,則去活躍資源中加載
//memCache中該bitmap則會(huì)被remove掉bitmap并進(jìn)入activeResource中
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);  
if (active != null) {    
 cb.onResourceReady(active, DataSource.MEMORY_CACHE);    
 ...   
}    
 return null;  
}  
//如果該任務(wù)之前已經(jīng)在隊(duì)列中恩伺,則添加新的callback,然后返回
EngineJob current = jobs.get(key);  
if (current != null) {    
current.addCallback(cb);    
...   
return new LoadStatus(cb, current);  
}  
//如果是新的加載任務(wù)椰拒,先創(chuàng)建EngineJob和DecodeJob莫其,然后開始任務(wù)
EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable);  
DecodeJob<R> decodeJob = decodeJobFactory.build(glideContext,model,key,signature,width,height,resourceClass,transcodeClass,      priority,diskCacheStrategy,transformations,      isTransformationRequired,options,engineJob); 
jobs.put(key, engineJob);  
engineJob.addCallback(cb);  
//開始任務(wù)
engineJob.start(decodeJob);  
...
return new LoadStatus(cb, engineJob);
}

4.glide的diskLruCache的默認(rèn)緩存路徑在哪里,默認(rèn)緩存容量是多少?
在項(xiàng)目的cache目錄image_manager_disk_cache文件夾下,容量是250M

    //DiskCache中
    /** 250 MB of cache. */
    int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
    String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

5.diskLruCache的四種緩存策略
DiskCacheStrategy.NONE :不緩存圖片
DiskCacheStrategy.SOURCE :緩存圖片源文件
DiskCacheStrategy.RESULT:緩存修改過的圖片
DiskCacheStrategy.ALL:緩存所有的圖片,默認(rèn)

  1. 使用thumbnail(0.1f)//設(shè)置縮略圖, 以后Glide 會(huì)先去請(qǐng)求縮略圖,然后在請(qǐng)求 原圖,因?yàn)榭s略圖一般比較小所以請(qǐng)求的比較快.

7.如果一個(gè)應(yīng)用中有多個(gè)地方要使用同一張照片,但是可能尺寸有可能不一樣,那么我們只需要設(shè)置所有地方的Glide的磁盤緩存策略為 Source就可以使用一張照片了(因?yàn)槟憔彺娴氖莝ource,那么去查找緩存的時(shí)候肯定找的也是source了)

總結(jié)

Glide加載圖片,首先會(huì)在內(nèi)存中找,如果內(nèi)存中沒有就去最近本地磁盤中找,然后磁盤中沒有的話會(huì)去,任務(wù)隊(duì)列中找有沒有正在加載下載此圖片的任務(wù),如果也沒有那就會(huì)使用HttpURLConnaction進(jìn)行下載,現(xiàn)在完成后就會(huì)用new Handler(Loop.getMainLooper)獲取到主線程的Handler然后將獲取到的圖片發(fā)送到主線程中.

參考文章

http://www.cnblogs.com/guanmanman/p/7008259.html
這篇文章講的更細(xì)一點(diǎn)http://www.reibang.com/p/7125feef0ddf

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耸三,一起剝皮案震驚了整個(gè)濱河市乱陡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌仪壮,老刑警劉巖憨颠,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昂勒,死亡現(xiàn)場(chǎng)離奇詭異撬陵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)昂利,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門缚陷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來适篙,“玉大人,你說我怎么就攤上這事箫爷∪陆冢” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵虎锚,是天一觀的道長硫痰。 經(jīng)常有香客問我,道長窜护,這世上最難降的妖魔是什么效斑? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮柱徙,結(jié)果婚禮上缓屠,老公的妹妹穿的比我還像新娘。我一直安慰自己护侮,他們只是感情好敌完,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著概行,像睡著了一般蠢挡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天业踏,我揣著相機(jī)與錄音禽炬,去河邊找鬼。 笑死勤家,一個(gè)胖子當(dāng)著我的面吹牛腹尖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伐脖,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼热幔,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了讼庇?” 一聲冷哼從身側(cè)響起绎巨,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蠕啄,沒想到半個(gè)月后场勤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡歼跟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年和媳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哈街。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡留瞳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骚秦,到底是詐尸還是另有隱情她倘,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布骤竹,位于F島的核電站帝牡,受9級(jí)特大地震影響往毡,放射性物質(zhì)發(fā)生泄漏蒙揣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一开瞭、第九天 我趴在偏房一處隱蔽的房頂上張望懒震。 院中可真熱鬧,春花似錦嗤详、人聲如沸个扰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽递宅。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間办龄,已是汗流浹背烘绽。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俐填,地道東北人安接。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像英融,于是被迫代替她去往敵國和親盏檐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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