Glide Gif加载

作者: 三十二蝉 | 来源:发表于2018-03-12 21:52 被阅读1490次

    综述

    Glide支持Gif加载,且不需要使用自定义的ImageView,直接使用系统的ImageView即可,接入成本很低。在做Gif这个功能的时候,涉及到以下几个功能点:
    - 带下载进度回调的图片下载
    - 判断图片是否来自缓存
    - Gif加载卡顿的问题
    - GifWifif自动播放逻辑,以及控制单页面只有一个Gif播放的逻辑

    带下载进度回调的图片下载

    Glide底层网络库可选择使用okhttp,基于Intercepor撰写ProgressIntercepor:

    public class ProgressInterceptor implements Interceptor {
      private DownloadProgressListener progressListener;
    
      public ProgressInterceptor(DownloadProgressListener progressListener){
        this.progressListener = progressListener;
      }
    
      @Override
      public Response intercept(Chain chain) throws IOException {
        Response originalResponse = chain.proceed(chain.request());
        return originalResponse.newBuilder()
            .body(new DownloadProgressResponseBody(originalResponse.body(), progressListener))
            .build();
      }
    
      private static class DownloadProgressResponseBody extends ResponseBody {
    
        private final ResponseBody responseBody;
        private final DownloadProgressListener progressListener;
        private BufferedSource bufferedSource;
    
        public DownloadProgressResponseBody(ResponseBody responseBody,
                                            DownloadProgressListener progressListener) {
          this.responseBody = responseBody;
          this.progressListener = progressListener;
        }
    
        @Override public MediaType contentType() {
          return responseBody.contentType();
        }
    
        @Override public long contentLength(){
          return responseBody.contentLength();
        }
    
        @Override public BufferedSource source(){
          if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
          }
          return bufferedSource;
        }
    
        private Source source(Source source) {
          return new ForwardingSource(source) {
            long totalBytesRead = 0L;
    
            @Override public long read(Buffer sink, long byteCount) throws IOException {
              long bytesRead = super.read(sink, byteCount);
              // read() returns the number of bytes read, or -1 if this source is exhausted.
              totalBytesRead += bytesRead != -1 ? bytesRead : 0;
    
              if (null != progressListener) {
                progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
              }
              return bytesRead;
            }
          };
        }
      }
    
      public interface DownloadProgressListener {
        void update(long bytesRead, long contentLength, boolean done);
      }
    }
    
    

    实现Glide的DataFetcher接口,ProgressDataFetcher实现网络数据拉取的具体实现:

    public class ProgressDataFetcher implements DataFetcher<InputStream> {
      private String url;
      private ProgressInterceptor.DownloadProgressListener listener;
      private Call progressCall;
      private InputStream stream;
      private boolean isCancelled;
    
    
      public ProgressDataFetcher(String url, ProgressInterceptor.DownloadProgressListener listener){
        this.url = url;
        this.listener = listener;
      }
    
      @Override
      public InputStream loadData(Priority priority) throws Exception {
        Request request = new Request.Builder().url(url).build();
        OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new ProgressInterceptor(listener))
            .build();
    
        try {
          progressCall = client.newCall(request);
          Response response = progressCall.execute();
          if (isCancelled) {
            return null;
          }
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
          stream = response.body().byteStream();
        } catch (IOException e) {
          e.printStackTrace();
          return null;
        }
        return stream;
      }
    
      @Override
      public void cleanup() {
        if (stream != null) {
          try {
            stream.close();
            stream = null;
          } catch (IOException e) {
            stream = null;
          }
        }
        if (progressCall != null) {
          progressCall.cancel();
        }
      }
    
      @Override
      public String getId() {
        return url;
      }
    
      @Override
      public void cancel() {
        isCancelled = true;
      }
    }
    

    将ProgressDataFetcher封装为ProgressModelLoader:

    public class ProgressModelLoader implements StreamModelLoader<String> {
      private ProgressInterceptor.DownloadProgressListener listener;
    
      public ProgressModelLoader(ProgressInterceptor.DownloadProgressListener listener){
        this.listener = listener;
      }
    
      @Override
      public DataFetcher<InputStream> getResourceFetcher(String model, int width, int height) {
        return new ProgressDataFetcher(model, listener);
      }
    }
    

    业务层调用方法:

    /**
     * Gilde 带进度回调的图片加载
     * Note: Gif 图片加载,disk cache必须是SOURCE/NONE;否则Gif有卡顿
     * 
     * @param imageView
     * @param url
     * @param drawable
     * @param roundDp 可设置圆角大小
     * @param listener 可为null
     */
    public static Target<GlideDrawable> bindGifWithProgress(ImageView imageView, String url,
        Drawable drawable, int roundDp,
        ProgressInterceptor.DownloadProgressListener listener) {
      RequestManager requestManager = Glide.with(imageView.getContext().getApplicationContext());
      DrawableTypeRequest request = null;
      if (listener != null) {
        request = requestManager.using(new ProgressModelLoader(listener)).load(url);
      } else {
        request = requestManager.load(url);
      }
      DrawableRequestBuilder builder = request.dontAnimate();
    
      if (drawable != null)
        builder.placeholder(drawable);
    
      if (roundDp != 0)
        builder.transform(
            new GlideRoundTransform(imageView.getContext().getApplicationContext(), roundDp));
    
      builder.diskCacheStrategy(DiskCacheStrategy.SOURCE);
    
      return builder.into(imageView);
    }
    

    判断图片是否来自缓存

    业务需要判断Gif图片是否已经在本地缓存,如果未缓存,加载静态占位图,从网络加载显示Gif字样的圆形进度条;如果已缓存,加载Gif,判断是否播放。

    因为Glide的缓存逻辑是会缓存原图,以及根据ImageView大小裁剪之后的图片;所以没有办法仅根据一个图片URL判断缓存是否存在。这里想了一个折中的方法:先调用一次禁用网络加载的Glide图片加载,从onResoureReady和onException判断图片是否被缓存命中。

    这里还是对Glide的单次加载实现一个NetworkDisablingLoader,来实现这个仅从本地加载的功能:

    public class NetworkDisablingLoader implements StreamModelLoader<String> {
      @Override public DataFetcher<InputStream> getResourceFetcher(final String model, int width, int height) {
        return new DataFetcher<InputStream>() {
          @Override public InputStream loadData(Priority priority) throws Exception {
            throw new IOException("Forced Glide network failure");
          }
          @Override public void cleanup() { }
          @Override public String getId() { return model; }
          @Override public void cancel() { }
        };
      }
    }
    

    业务层调用方法:

    /**
     * Glide 仅从本地加载图片
     * 
     * @param imageView
     * @param url
     * @param defaultImage 不需要,填 -1
     * @param roundDp 可设置圆角大小
     * @param listener 可为null
     */
    public static Target<GlideDrawable> bindWithoutNet(ImageView imageView, String url,
        int defaultImage, int roundDp,
        RequestListener listener) {
      RequestManager requestManager = Glide.with(imageView.getContext().getApplicationContext());
      DrawableTypeRequest request = requestManager.using(new NetworkDisablingLoader()).load(url);
      DrawableRequestBuilder builder = request.dontAnimate();
      if (roundDp != 0) {
        builder.transform(
            new GlideRoundTransform(imageView.getContext().getApplicationContext(), roundDp));
      }
      if (defaultImage != -1) {
        builder.placeholder(defaultImage);
      }
      if (listener != null) {
        builder.listener(listener);
      }
      builder.diskCacheStrategy(DiskCacheStrategy.SOURCE);
    
      return builder.into(imageView);
    
    }
    

    Gif加载卡顿的问题

    Gilde在缓存Gif资源的时候,可以有两种模式:SOURCE和RESULT。卡顿的原因是RESULT类型的缓存造成的。(Glide为了节省空间,会对原始缓存做压缩生成RESULT;对于Gif类型而言,这种压缩算法显示效率有问题,从RESULT到原始Gif的转化会很慢)。这里的解决方法是将缓存的类型设置为ALL/SOURCE。(在ALL模式下,应该优先那SOURCE缓存使用,所有也不会有问题)。

    Gif播放控制逻辑

    为什么做Gif的播放控制逻辑?Gif的播放非常消耗资源,我们应该控制单个页面正在播放的gif个数,这里限定为一个。此外,还可以在此基础上实现Gif Wifi下自动播放的逻辑。(当页面滚动时自动播放下一个)。
    这个功能主要是根据RecycleView的LayoutManager实现的,主要涉及到如下方法:
    - linearLayoutManager.findFirstVisibleItemPosition()
    - linearLayoutManager.findLastVisibleItemPosition()
    - linearLayoutManager.findViewByPosition
    - recyclerView.getChildViewHolder(itemView)
    - linearLayoutManager.findFirstCompletelyVisibleItemPosition()
    - linearLayoutManager.findLastCompletelyVisibleItemPosition()

    具体的业务流程:
    - 通过LinearLayoutManager获取可见范围内item的范围
    - 在可见的item范围内,找到正在播放Gif的item是否满足播放条件,如果满足,直接跳出流程
    - 当正在播放Gif的item不满足播放条件,先对所有可见item执行暂停Gif播放的功能。
    - 然后在所有已经暂停的Gif item中找到第一个满足播放条件的Gif item

    具体代码如下:

    public class GifFeedHelper {
      public static final String GIF_TAB_ID = "10283";
    
      private LinearLayoutManager linearLayoutManager;
      private RecyclerView recyclerView;
    
      public static WeakReference<GifFeedHelper> gifFeedHelper;
    
    
      public GifFeedHelper(LinearLayoutManager linearLayoutManager, RecyclerView recyclerView){
        this.linearLayoutManager = linearLayoutManager;
        this.recyclerView = recyclerView;
        gifFeedHelper = new WeakReference<GifFeedHelper>(this);
      }
    
      /**
       * 首页Gif Feed Tab
       * Gif 调度播放逻辑
       */
      public void dispatchGifPlay() {
    
        int start_index = linearLayoutManager.findFirstVisibleItemPosition();
        int end_index = linearLayoutManager.findLastVisibleItemPosition();
    
        if (findCurrentPlayGifCardSatisfy(start_index, end_index))
          return;
    
        stopAllGifCardOnScreen(start_index, end_index);
    
        actionFirstGifCardOnScreen(start_index, end_index);
      }
    
      /**
       * 外部可强制停止页面内Gif Card 的播放
       * Note:HPGifNormalCardPresenter中的点击事件,需要暂停其他的Gif Card
       * */
      public void forceStopAllGifs() {
        int start_index = linearLayoutManager.findFirstVisibleItemPosition();
        int end_index = linearLayoutManager.findLastVisibleItemPosition();
    
        stopAllGifCardOnScreen(start_index, end_index);
      }
    
      private boolean findCurrentPlayGifCardSatisfy(int start_index,int end_index) {
    
        for(int i= start_index; i<=end_index; i++){
          View itemView = linearLayoutManager.findViewByPosition(i);
          if (null == itemView)
            continue;
          RippleViewHolder holder = (RippleViewHolder) recyclerView.getChildViewHolder(itemView);
          CardPresenter presenter = holder.presenter;
          if(presenter == null)
            continue;
    
          BasePresenter basePresenter = presenter.get(0); //默认是0
          if (basePresenter != null && basePresenter instanceof HPGifNormalCardPresenter) {
            HPGifNormalCardPresenter gifNormalCardPresenter = (HPGifNormalCardPresenter) basePresenter;
            int fullHeight = itemView.getHeight();
            if(fullHeight <=0)
              continue;
            Rect rect = new Rect();
            boolean visible = itemView.getGlobalVisibleRect(rect);
            int visibleHeight = rect.height();
            if(gifNormalCardPresenter.isGifActive() &&
                visible && ((1.0f * visibleHeight / fullHeight) >= 0.3f)) {
              //Fix: 找到满足条件的第一个Gif,之后的Gif强制停止播放
              if (i<end_index)
                stopAllGifCardOnScreen(i+1,end_index);
              return true;
            }
          }
        }
    
        return false;
      }
    
      /**
       * 暂停屏幕中所有可见Gif Card的播放
       * */
      private void stopAllGifCardOnScreen(int start_index, int end_index) {
    
    
        for (int i = start_index; i <= end_index; i++) {
          View itemView = linearLayoutManager.findViewByPosition(i);
          if (null == itemView)
            continue;
          RippleViewHolder holder = (RippleViewHolder) recyclerView.getChildViewHolder(itemView);
          CardPresenter presenter = holder.presenter;
          if (presenter == null)
            return;
          BasePresenter basePresenter = presenter.get(0); //默认是0
          if (basePresenter != null && basePresenter instanceof HPGifNormalCardPresenter) {
            HPGifNormalCardPresenter gifNormalCardPresenter = (HPGifNormalCardPresenter) basePresenter;
            gifNormalCardPresenter.onStop();
          }
        }
      }
    
      /**
       * 在所有已经暂停的Gif Card中找到第一个满足播放条件的Gif Card
       * */
      private void actionFirstGifCardOnScreen(int start_index, int end_index) {
        View activeGifView = null;
        for (int i = start_index; i <= end_index; i++) {
          View view = linearLayoutManager.findViewByPosition(i);
          if (null == view)
            continue;
    
          int fullHeight = view.getHeight();
          if (fullHeight <= 0) {
            continue;
          }
    
          Rect rect = new Rect();
          boolean visible = view.getGlobalVisibleRect(rect);
          int visibleHeight = rect.height();
    
          RippleViewHolder holder = (RippleViewHolder) recyclerView.getChildViewHolder(view);
          CardPresenter presenter = holder.presenter;
          if (presenter == null)
            continue;
          BasePresenter basePresenter = presenter.get(0); //默认是0
          if (basePresenter == null)
            continue;
          if (visible && ((1.0f * visibleHeight / fullHeight) >= 0.3f)
              && (basePresenter instanceof HPGifNormalCardPresenter)) {
            activeGifView = view;
            break;
          }
        }
    
        if (activeGifView != null) {
          RippleViewHolder holder = (RippleViewHolder) recyclerView.getChildViewHolder(activeGifView);
          CardPresenter presenter = holder.presenter;
          BasePresenter basePresenter = presenter.get(0); //默认是0
    
          if (basePresenter instanceof HPGifNormalCardPresenter) {
            HPGifNormalCardPresenter gifNormalCardPresenter = (HPGifNormalCardPresenter) basePresenter;
            if (gifNormalCardPresenter.canAutoPlay()) {
              gifNormalCardPresenter.performAutoPlay();
            } else {
              gifNormalCardPresenter.onStart();
            }
          }
        }
      }
    }
    

    相关文章

      网友评论

      本文标题:Glide Gif加载

      本文链接:https://www.haomeiwen.com/subject/tcwofftx.html