從源碼看Glide圖片加載

Glide源碼有不少值得學習的地方,各類設計模式用的堪比AOSP,不過也實在是有夠複雜,以前擼了簡單的圖片加載框架,是參照Volley設計的,如今該像Glide源碼學習了.java

調用方式

咱們先看一下Glide的用法,相信你們都會. 精彩之處在於android

  • 功能強大,RESTful調用
  • 不用咱們處理取消問題,自綁定生命週期
  • 不用在Application初始化
Glide.with(TodayFragment.this)
        .load(gankDay.results.福利.get(0).getUrl())
        .centerCrop()
        .crossFade()
        .error(R.drawable.jay)
        .into(mImageView);複製代碼

with是幹什麼的

with起到綁定生命週期的做用,這裏的with多是多種類型,Glide都幫咱們複寫了.with的效果主要是幫咱們給當前對象綁定上生命週期.git

以Fragment爲調用方舉例.github

public static RequestManager with(Fragment fragment) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(fragment);
}複製代碼

咱們去RequestManagerRetriever看看.設計模式

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

RequestManager supportFragmentGet(Context context, final FragmentManager fm) {
    SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(TAG);
    if (current == null) {
        current = pendingSupportRequestManagerFragments.get(fm);
        if (current == null) {
            current = new SupportRequestManagerFragment();
            pendingSupportRequestManagerFragments.put(fm, current);
            fm.beginTransaction().add(current, TAG).commitAllowingStateLoss();
            handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
        }
    }
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        requestManager = new RequestManager(context, current.getLifecycle());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}複製代碼

能夠看到主要流程就是,去 FragmentManager尋找fragment,fm.findFragmentByTag(TAG);, 若是找不到,就實類化一個current = new SupportRequestManagerFragment();,並添加進去緩存

SupportRequestManagerFragment是什麼呢?咱們進去看網絡

public class SupportRequestManagerFragment extends Fragment {
    private RequestManager requestManager;
    private final ActivityFragmentLifecycle lifecycle;

    public SupportRequestManagerFragment() {
        this(new ActivityFragmentLifecycle());
    }
    ...
}複製代碼

SupportRequestManagerFragment是繼承於Fragment,並實現了各個生命週期的回調,最終回調lifecycle接口.這裏就實現了生命週期的監聽.app

ActivityFragmentLifecycle是一個觀察者,裏面有一個Set集合存放了LifecycleListener, 這是標準的觀察者模式的寫法,每次回調都會遍歷集合分發事件.代碼以下.框架

class ActivityFragmentLifecycle implements Lifecycle {
    private final Set<LifecycleListener> lifecycleListeners =
            Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>()));
    private boolean isStarted;
    private boolean isDestroyed;

    @Override
    public void addListener(LifecycleListener listener) {
        lifecycleListeners.add(listener);

        if (isDestroyed) {
            listener.onDestroy();
        } else if (isStarted) {
            listener.onStart();
        } else {
            listener.onStop();
        }
    }

    void onStart() {
        isStarted = true;
        for (LifecycleListener lifecycleListener : lifecycleListeners) {
            lifecycleListener.onStart();
        }
    }

    void onStop() {
        isStarted = false;
        for (LifecycleListener lifecycleListener : lifecycleListeners) {
            lifecycleListener.onStop();
        }
    }

    void onDestroy() {
        isDestroyed = true;
        for (LifecycleListener lifecycleListener : lifecycleListeners) {
            lifecycleListener.onDestroy();
        }
    }
}複製代碼

此時咱們再回到RequestManagerRetriever的裏面,該走下一步了,咱們要返回的是RequestManager,
同樣的套路,先去拿,拿不到就實類化.ide

RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
    requestManager = new RequestManager(context, current.getLifecycle());
    current.setRequestManager(requestManager);
}
return requestManager;複製代碼

經過current.getLifecycle()RequestManagerFragment的生命週期傳給RequestManager,又把這個RequestManager設置進RequestManagerFragment,這邊至關因而互相持有了.其實設置生命週期給RequestManager徹底沒有必要這麼寫,由於Current已經持有RequestManager了.

最終咱們返回RequestManager,咱們如今看RequestManager是什麼?

RequestManager-很是重要

更精彩的來了!!!

看一下RequestManager代碼

public class RequestManager implements LifecycleListener {
    private final Context context;
    private final Lifecycle lifecycle;
    private final RequestTracker requestTracker;
    private final Glide glide;
    private final OptionsApplier optionsApplier;
    private DefaultOptions options;

    ...
    this.glide = Glide.get(context);
    lifecycle.addListener(this);
    ...
    @Override
    public void onStart() {
        // onStart might not be called because this object may be created after the fragment/activity's onStart method.
        resumeRequests();
    }

    /** * Lifecycle callback that unregisters for connectivity events (if the android.permission.ACCESS_NETWORK_STATE * permission is present) and pauses in progress loads. */
    @Override
    public void onStop() {
        pauseRequests();
    }

    /** * Lifecycle callback that cancels all in progress requests and clears and recycles resources for all completed * requests. */
    @Override
    public void onDestroy() {
        requestTracker.clearRequests();
    }

}複製代碼

咱們獲得幾個有用信息,

  1. implements LifecycleListener,實現了這個接口,咱們上面還講到觀察者模式,在ActivityFragmentLifecycle裏面,此時RequestManager擁有了生命週期的回調,咱們從代碼能夠看到,它在每一個生命週期裏面進行了取消,暫停,恢復請求的操做.
  2. 持有一個全局的Glide變量,Glide類是單例的.咱們看經過Glide.get(context);拿到Glide類.

咱們知道,不少框架須要在Application裏面初始化,由於確定要持有Context,又不想綁定某個Activity的Context,所以用ApplicationContext.這就形成了要在Application裏面初始化的結果.包括個人SherlockImageLoader也是這麼寫的.如今看樣子須要改進了.

咱們看看get方法.

/** * Get the singleton. * * @return the singleton */
    public static Glide get(Context context) {
        if (glide == null) {
            synchronized (Glide.class) {
                if (glide == null) {
                    Context applicationContext = context.getApplicationContext();
                    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;
    }複製代碼

從上面能夠看到,get時候傳入context,但並非每次傳的context都會被用到的,只有第一次使用的時候這個Context會被用到,也只是用它來獲取Application的Context. 而後利用ApplicationContext來惰性初始化咱們的全局Glide對象.

好咱們RequestManager先暫停一下,咱們看with完了,咱們拿到RequestManager後該幹什麼.

Glide.with(TodayFragment.this).load(gankDay.results.福利.get(0).getUrl())...

咱們會調用load()方法,或者是fromUri(),loadFromMediaStore(),load(File file)..等多種重載.可是咱們會獲得一個新的對象叫DrawableTypeRequest,很明顯,這是用來幫助咱們初始化請求的類.咱們去看看.

public class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> implements DownloadOptions {
  public BitmapTypeRequest<ModelType> asBitmap() {
    return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
            fileDescriptorModelLoader, optionsApplier));
  }
  @Override
  public DrawableRequestBuilder<ModelType> animate(ViewPropertyAnimation.Animator animator) {
      super.animate(animator);
      return this;
  }

  /** * {@inheritDoc} */
  @Override
  public DrawableRequestBuilder<ModelType> animate(int animationId) {
      super.animate(animationId);
      return this;
  }
  /** * {@inheritDoc} */
  @Override
  public DrawableRequestBuilder<ModelType> placeholder(int resourceId) {
      super.placeholder(resourceId);
      return this;
  }
}複製代碼

果真是,是一個Builder,咱們能夠用來設置各類模式,各類狀況.這個Builder很是的大.由於要考慮到全世界的需求啊.裏面的動畫和編解碼等都是不少的,不過這些都是應付各類各樣的業務.

咱們能夠看一下UML圖就知道了.

builder
builder

知道了是Builder後,咱們直接跳去builder的into方法看.

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

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);
    //將target加入lifecycle
    lifecycle.addListener(target);
    //執行請求
    requestTracker.runRequest(request);

    return target;
}複製代碼

咱們知道了三點:

  1. Util.assertMainThread();這裏會檢查是否主線程,不是的話會拋出異常,因此into方法必須在主線程中調用.
  2. 當你沒有調用transform方法,而且你的ImageView設置了ScaleType,那麼他會根據你的設置,對圖片作處理(具體處理能夠查看DrawableRequestBuilder的applyCenterCrop或者applyFitCenter方法,咱們本身自定義BitmapTransformation也能夠參考這裏的處理).
  3. view在這裏被封裝成一個Target.

咱們看看上面代碼裏面的buildRequest方法.

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,
            requestListener,
            requestCoordinator,
            glide.getEngine(),
            transformation,
            transcodeClass,
            isCacheable,
            animationFactory,
            overrideWidth,
            overrideHeight,
            diskCacheStrategy);
}複製代碼

裏面有一個享元模式,有點相似於Message.obtain同樣,都是去生成Request,而且都是複用.

這是一個插曲,回到into()裏面來,最後生成了Request後, 調用requestTracker.runRequest(request);

/** * Starts tracking the given request. */
    public void runRequest(Request request) {
    //添加request對象到集合中
        requests.add(request);
        if (!isPaused) {
        //若是當前狀態是非暫停的,調用begin方法發送請求
            request.begin();
        } else {
        //將請求加入到掛起的請求集合
            pendingRequests.add(request);
        }
    }複製代碼

咱們能夠看到.將Request添加進一個set後,仍是調用了Requestbegin方法,這個Request是GenericRequest

咱們進去看GenericRequestbegin方法

@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));
    }
}複製代碼

在這邊回調了target.onLoadStarted(getPlaceholderDrawable());方法,去設置佔位圖.這裏的Target有不少種

不過都是回調他們的生命週期onLoadStart了.

這裏咱們來注意幾個細節,首先若是model等於null,model也就是咱們在第二步load()方法中傳入的圖片URL地址,這個時候會調用onException()方法。若是你跟到onException()方法裏面去看看,你會發現它最終會調用到一個setErrorPlaceholder()當中.就是加載錯誤圖片

GenericRequest類中還有個重要方法,剛剛的begin方法裏面會調用到.

咱們看看代碼.

/** * A callback method that should never be invoked directly. */
@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("Got null fetcher from model loader"));
        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));
    }
}複製代碼

在上面咱們看到了不少關鍵詞Loader,ResourceTranscoder,loadProvider,loadedFromMemoryCache,這些不正是咱們設計圖片加載框架最核心的地方麼,加載器,轉碼器,加載器管理器,緩存池都在此處有影子.

加載器,轉碼器等是怎麼根據類型判斷的咱們能夠跳過,加載器和轉碼器有不少種,又是一個大的抽象樹.可是不是重點,咱們看這些只要看其頂級接口就好了.

咱們直接去看engin怎麼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) {
        // 獲取數據成功,會回調target的onResourceReady() 結束
        cb.onResourceReady(cached);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
        }
        return null;
    }
    // 嘗試從活動Resources 中獲取,它表示的是當前正在使用的Resources,與內存緩存不一樣之處是clear緩存時不會clear它。
    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;
    }
    //判斷jobs中是否已經存在任務,若是存在說明任務以前已經提交了
    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 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);
}複製代碼

上面步驟爲:

  1. 先根據調用loadFromCache從內存加載,成功獲取後直接回調返回.失敗繼續
  2. 嘗試從活動Resources 中獲取,成功返回,失敗繼續
  3. 去判斷job是否已經存在,存在了就返回新的狀態,沒有則繼續
  4. 建立job,建立decodejob,建立runnable,開啓job.EngineRunnable的run()方法在子線程當中執行了

如今咱們去看EngineRunnable到底幹了啥.

@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);
    }
}複製代碼

媽的好像沒幹什麼事情,就主要調了decode()方法啊,以後都回調成功或者失敗的結果了.說明確定就在decode()方法裏面.

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

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();
}複製代碼

上面一看,decode()又去調用decodeFromCache()decodeFromSource()了.抓頭,這麼快就decode了?source在哪來的啊,咱們直接點進去看,咱們傳入的是url的時候,目前而言,圖片還沒下載下來呢.

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;
}複製代碼

有一絲但願,咱們看到了原來這裏面不是decode咱們的圖片.並且是decode咱們的url資源. 看到了關鍵詞fetcher.loadData(priority);

由於事先就用uml生成工具看了Glide的uml圖,fetcher但是擔任load任務的人.

咱們看一下fetcher的uml

fetcher.png
fetcher.png

裏面但是有加載方法的.咱們去看一個Fetcher,就使用率最高的HttpUrlFetcher吧.

public InputStream loadData(Priority priority) throws Exception {
        return this.loadDataWithRedirects(this.glideUrl.toURL(), 0, (URL)null);
    }

    private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl) throws IOException {
        if(redirects >= 5) {
            throw new IOException("Too many (> 5) redirects!");
        } else {
            try {
                if(lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
                    throw new IOException("In re-direct loop");
                }
            } catch (URISyntaxException var7) {
                ;
            }

            this.urlConnection = this.connectionFactory.build(url);
            this.urlConnection.setConnectTimeout(2500);
            this.urlConnection.setReadTimeout(2500);
            this.urlConnection.setUseCaches(false);
            this.urlConnection.setDoInput(true);
            this.urlConnection.connect();
            if(this.isCancelled) {
                return null;
            } else {
                int statusCode = this.urlConnection.getResponseCode();
                if(statusCode / 100 == 2) {
                    this.stream = this.urlConnection.getInputStream();
                    return this.stream;
                } else if(statusCode / 100 == 3) {
                    String redirectUrlString = this.urlConnection.getHeaderField("Location");
                    if(TextUtils.isEmpty(redirectUrlString)) {
                        throw new IOException("Received empty or null redirect url");
                    } else {
                        URL redirectUrl = new URL(url, redirectUrlString);
                        return this.loadDataWithRedirects(redirectUrl, redirects + 1, url);
                    }
                } else if(statusCode == -1) {
                    throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
                } else {
                    throw new IOException("Request failed " + statusCode + ": " + this.urlConnection.getResponseMessage());
                }
            }
        }
    }複製代碼

不出所料,在這裏加載url,從網絡獲取資源. 感恩,咱們終於找到調用urlConnection的源碼了!能夠看到咱們拿到了網絡請求的InputStream,

對應於HttpUrlFetcher的是ImageVideoBitmapDecoder, 它是接收InputStream,由於這個InputStream類型對於這兩貨來講都是T類型.相對應的.

代碼以下,能夠看到,咱們的經過decode,成功拿到bitmap.剛剛的InputStream已經被封裝到ImageVideoWrapper source裏面了.經過InputStream is = source.getStream();拿到.

public class ImageVideoBitmapDecoder implements ResourceDecoder<ImageVideoWrapper, Bitmap> {
    private static final String TAG = "ImageVideoDecoder";
    private final ResourceDecoder<InputStream, Bitmap> streamDecoder;
    private final ResourceDecoder<ParcelFileDescriptor, Bitmap> fileDescriptorDecoder;

    public ImageVideoBitmapDecoder(ResourceDecoder<InputStream, Bitmap> streamDecoder, ResourceDecoder<ParcelFileDescriptor, Bitmap> fileDescriptorDecoder) {
        this.streamDecoder = streamDecoder;
        this.fileDescriptorDecoder = fileDescriptorDecoder;
    }

    @SuppressWarnings("resource")
    // @see ResourceDecoder.decode
    @Override
    public Resource<Bitmap> decode(ImageVideoWrapper source, int width, int height) throws IOException {
        Resource<Bitmap> result = null;
        InputStream is = source.getStream();
        if (is != null) {
            try {
                result = streamDecoder.decode(is, width, height);
            } catch (IOException e) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Failed to load image from stream, trying FileDescriptor", e);
                }
            }
        }

        if (result == null) {
            ParcelFileDescriptor fileDescriptor = source.getFileDescriptor();
            if (fileDescriptor != null) {
                result = fileDescriptorDecoder.decode(fileDescriptor, width, height);
            }
        }
        return result;
    }

    @Override
    public String getId() {
        return "ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap";
    }
}複製代碼

都拿到了bitmap了,下面水到渠成了

哎呀我去,太長了,寫了三小時,好不容易拿到bitmap了.接下來就是如何顯示如何回調了. 顯示回調部分,之後有機會再分析.


本文做者:Anderson/Jerey_Jobs

博客地址 : jerey.cn/

簡書地址 : Anderson大碼渣

github地址 : github.com/Jerey-Jobs

相關文章
相關標籤/搜索