項目重構之路——Fresco非侵入式替換Glide

本文GitHub項目地址 : 統一的圖片加載架構html

前言

咱們目前的項目對於圖片加載的需求很大,一直以來,咱們使用的是Glide做爲圖片加載的底層包,爲了節省圖片佔用的空間,但願使用webp的格式來展現動圖。因爲Glide不支持Animated WebP(即WebP動圖) 格式,因此咱們須要把底層的Glide替換爲Fresco。相信用過這兩個包的同窗都知道,二者的差別仍是比較大的,想要保證在不大量修改代碼,也不入侵業務代碼的前提下遷移到Fresco上,是個值得思考的問題。java

因爲以前已經把項目的圖片加載模塊重構整合了一次,把圖片加載和業務代碼有效分離開,所以此次遷移也算有一個較好的基礎。至於整合的具體思路以及爲何整合這些模塊的意義,我在《封裝並實現統一的圖片加載架構》文章裏面已經講了不少,沒看過那篇文章的同窗建議看看,由於接下來就是在這篇文章的基礎上去分析如何遷移到Fresco上的問題。git

替換Glide遇到的難題

咱們都知道,Glide是使用了ImageView來加載圖片的,而實際項目中還會有咱們自定義的ImageView,好比CircleImageView等,而Fresco則是經過DraweeView來加載圖片的,這是一個很是嚴重的問題。github

因此咱們須要考慮的是在項目遷移到Fresco上的時候,究竟是想辦法沿用ImageView來加載圖片,仍是想辦法使用SimpleDraweeView來加載圖片,若是是前者,那麼咱們應該怎麼作?假如是後者,要怎樣才能保證Fresco的代碼不會大量入侵到業務代碼中,同時又能兼容原來項目的接口呢?web

Tips : 對於Fresco本身的圖片加載容器DraweeView,目前暫時繼承自ImageView,可是官方表示將來會直接繼承自View,咱們開發時經常使用DraweeView的子類SimpleDraweeView來加載圖片。緩存

解決的思路

方案一:沿用ImageView加載

  • 思路
    • 尋找一種辦法從Fresco中獲取圖片數據,而後使用ImageView來加載。而正好,Fresco有一個Image pipeline 做爲圖片加載的控制核心,它負責判斷圖片緩存,而且能從各類渠道解碼,變換,返回bitmap,而後緩存起來。也就是說,咱們能夠直接經過這個模塊去獲取緩存的bitmap,把它加載到ImageView上,這樣的話,就不須要使用Fresco的DraweeView了。

  • 優勢bash

    在我看官方文檔的時候,暗喜了良久。由於若是能夠這麼作的話,那麼這就是最簡單的直接的遷移到Fresco上的方法。架構

  • 缺陷
    • 我發現若是使用Image Pipline去獲取Bitmap來加載圖片,那麼也就必須放棄Fresco特有的Drawee各類效果配置。不只如此,Fresco對本身返回的bitmap的生命週期控制十分嚴格,以致於咱們在使用時必須十分當心,並且一旦再也不使用必須釋放引用,換言之,咱們必須手動控制整個圖片加載的緩存釋放策略,稍有不慎就會報錯。因爲咱們的項目是一個直播App,許多複雜的業務都涉及到圖片加載模塊的使用,我沒法保證能在那麼多複雜得場景中控制好Bitmap的生命週期。
  • 結論
    • 這種策略看起來最簡單,實際上須要付出的代價極大,基本上是沒法承受的。Pass。

方案二 :直接替換(使用SimpleDraweeView)

  • 思路:
    • 既然咱們本身控制圖片加載和內存回收太過吃力,就只能把這些工做交給Fresco,最直接的方法就是,把xml,Java文件中的涉及到ImageView以及自定義的ImageView的引用粘貼複製成SimpleDraweeView就好了(並且Fresco雖然有本身的DraweeView,可是也都繼承自ImageView,這也會給咱們帶來一些便利)。固然,須要修改的還會有不少。當第一個思路行不通的時候,這個思路能夠說是很容易想到的,無非就是統一替換的問題,簡單粗暴,不會有太多難題。
  • 缺陷:框架

    • 可是,能最早想到的未必是最好的方案,首先,xml,Java文件中大量出現Fresco的引用,是個比較糟糕的狀況。會破壞咱們的以前對於圖片加載框架的一些封裝,之後再修改框架,或者替換Fresco就很被動。(想想,萬一之後Glide之後支持了Webp格式,而咱們又對Fresco包的大小很不爽,那麼就免不了要替換掉Fresco了)
  • 結論:ide

    • 難度很低,工做量較大,也會破壞項目的封裝性。保留做爲萬不得已的選擇

方案三 : 動態替換(使用SimpleDraweeView)

  • 思路:
    保持原有的代碼不變,當須要調用圖片加載接口的時候,動態的把原有的ImageView及其子類替換成Fresco的SimpleDraweeView,而後使用SimpleDraweeView去加載圖片。

具體操做大概就是當調用了以下加載接口的時候,把ImageView動態替換成SimpleDraweeView,而後加載圖片

void showImage(ImageLoaderOptions options);  // ImageLoaderOptions包含ImageView,Url 等等複製代碼
  • 優點:
    • 這個方案很好的解決了上面的擔心,既不改動原有的代碼,也不將Fresco的代碼大量參雜到業務中去,未來的替換或者拓展將十分輕鬆。
  • 缺陷:
    • 這個方案在實現上卻有不少難點,好比如何動態替換,原有的ImageView中設置的click監聽事件怎麼轉移過去?最重要的一個問題是:listview,recycleview中的ImageView(緩存)如何替換?
  • 結論:
    • 在實際測試的過程當中,我發現RecycleView中加載的ImageView會有緩存的問題,沒法完成替換,所以這個方案也只能拋棄掉。

方案四 : 動態添加(使用SimpleDraweeView)

  • 思路:
    • 保持原有代碼中的ImageView不變,在原有的佈局關係中插入一個「如出一轍」的SimpleDraweeView,保證SimpleDraweeView在父容器中的位置正好和ImageView徹底重合,Click事件依然還保留原來的ImageView上,RecycleView的緩存問題也能夠避免,這樣,SimpleDraweeView負責加載顯示圖片,ImageView負責交互。
  • 優點:
    • 這是方案三延伸出的一個折中方案,避免了不少難題,實現起來也會很簡單,並且代碼的入侵度很低。
  • 缺陷:
    • 這個方案稍遜於方案三,但可行性極高。假如原項目中對ImageView的使用得當,那麼幾乎就無需再修改項目代碼了。並且也無須擔憂對象過可能是否致使內存的問題,由於真正的內存消耗的大戶都是圖片,ImageView這個對象自己並不大。

比較方案

上面四個方案是我所想到的全部的實現方案,基本上方案三和方案四實現後的效果是最佳的。可是方案三的難度更大,因此綜合來看,方案四的性價比更好。(目前項目使用的就是方案四,效果良好,運行正常)

實踐

肯定了方案,咱們能夠開始實踐了,結合方案四的思路,代碼實現上基本上是沒有什麼難度了。
對於如何整合圖片加載模塊,請務必參考以前的文章《封裝並實現統一的圖片加載架構

Fresco加載模塊的重點代碼實現以下:

FrescoImageLoader.java

// 項目種幾乎全部的圖片加載都調用到了這裏
    @Override
    public void showImage(@NonNull ImageLoaderOptions options) {
        showImgaeDrawee(options);
    }

    private void showImgaeDrawee(ImageLoaderOptions options) {
         // 這個View就是加載圖片的ImageView
        View view=options.getViewContainer();
        SimpleDraweeView drawee=null;
        Class clazz=null;
        GenericDraweeHierarchy hierarchy=null;
        GenericDraweeHierarchyBuilder hierarchyBuilder = GenericDraweeHierarchyBuilder.newInstance(getResources());
        // 因爲本身的項目中有好幾種ImageView,所以須要一一判斷
        if (view instanceof SquareRImageView) {
            clazz= SquareRImageView.class;
            drawee=getDraweeView(view,clazz);
            if (drawee != null) {
                drawee.setAspectRatio(1);
            }
        }else if (view instanceof CircleImageView){
            clazz= CircleImageView.class;
            // 傳入
            drawee=getDraweeView(view,clazz);
            hierarchyBuilder.setFadeDuration(400).setRoundingParams(RoundingParams.asCircle());
        }else if (view instanceof SimpleDraweeView){
            drawee= (SimpleDraweeView) view;
            hierarchy=drawee.getHierarchy();
        }else if(view instanceof ImageView){
            clazz= ImageView.class;
            drawee=getDraweeView(view,clazz);
        }
        else {
            Logger.i("no type !!");
            return;
        }

        if (drawee != null) {
        // 圖片地址
            Uri uri=Uri.parse(options.getUrl());
            if (options.getHolderDrawable()!=-1) {
                hierarchyBuilder.setPlaceholderImage(options.getHolderDrawable());
            }
            if (options.getErrorDrawable()!=-1) {
                hierarchyBuilder.setFailureImage(options.getErrorDrawable());
            }

            if (hierarchy == null) {
                hierarchy= hierarchyBuilder.build();

            }
            drawee.setHierarchy(hierarchy);

            PipelineDraweeControllerBuilder controllerBuilder=Fresco.newDraweeControllerBuilder().setUri(uri).setAutoPlayAnimations(true);

            ImageRequestBuilder imageRequestBuilder= ImageRequestBuilder.newBuilderWithSource(uri);
            if (options.getImageSize() != null) {
                imageRequestBuilder.setResizeOptions(new ResizeOptions(getSize(options.getImageSize().getWidth(),view), getSize(options.getImageSize().getWidth(),view)));
            }
            if (options.isBlurImage()) {
                 // 是否作高斯模糊
                imageRequestBuilder.setPostprocessor(new BlurPostprocessor(view.getContext().getApplicationContext(), 15));
            }
            ImageRequest request =imageRequestBuilder.build();
            controllerBuilder.setImageRequest(request);
            DraweeController controller=controllerBuilder.build();
            drawee.setController(controller);
        }
    }複製代碼

在圖片加載時,首先須要判斷加載圖片的容器是ImageView仍是ImageView的子類,由於這意味着對圖片不一樣的處理,好比CircleImageView意味着是加載一個圓圖,因此咱們須要設置SimpleDraweeView爲圓圖等等。

// 傳入加載圖片的ImageView,返回一個相同位置,相同大小的SimpleDraweeView
        private SimpleDraweeView getDraweeView(View viewContainer,Class<?> classType) {
        if (viewContainer instanceof SimpleDraweeView){
            return (SimpleDraweeView) viewContainer;
        }
        SimpleDraweeView mDraweeView=null;
        if (classType.isInstance(viewContainer)){
            FrameLayout layout=new FrameLayout(viewContainer.getContext());
            if(viewContainer.getParent() instanceof FrameLayout){
                FrameLayout parent= (FrameLayout) viewContainer.getParent();
                FrameLayout.LayoutParams params= (FrameLayout.LayoutParams) viewContainer.getLayoutParams();
                // 這個方法來完成最終的添加
                mDraweeView=exchangeChilde(parent,viewContainer,params);
            }else if(viewContainer.getParent() instanceof RelativeLayout){
                RelativeLayout parent= (RelativeLayout) viewContainer.getParent();
                RelativeLayout.LayoutParams params= (RelativeLayout.LayoutParams) viewContainer.getLayoutParams();

                mDraweeView=exchangeChilde(parent,viewContainer,params);
            }else if(viewContainer.getParent() instanceof LinearLayout){
                // 當ImageView 的Parent時LinearLayout的時候,處理會有一些不一樣
                LinearLayout parent= (LinearLayout) viewContainer.getParent();
                LinearLayout.LayoutParams params= (LinearLayout.LayoutParams) viewContainer.getLayoutParams();
                layout.setLayoutParams(params);
                addToViewGroup(parent,viewContainer,layout); 
                layout.addView(viewContainer);
                mDraweeView=exchangeChilde(layout,viewContainer,params);
            }else{
                //基本上能夠涵蓋上面一個項目中用到的佈局類型了,
                //其餘的類型如Tablayout等等,視實際狀況而定
                ViewParent parent=viewContainer.getParent();
                Logger.i("");
            }
        }else{
            Logger.i("");
        }
        return mDraweeView;
    }

  // 該方將ImageView從原來的Parent種移除,並添加到一個FrameLayout中去
      private void addToViewGroup(ViewGroup parent,View viewOld,View viewNew){
        for (int i = 0; i < parent.getChildCount(); i++) {
            if (parent.getChildAt(i).equals(viewOld)) {
                parent.removeView(viewOld);
                parent.addView(viewNew,i);
                return;
            }
        }
    }複製代碼

這裏須要判斷ImageView的父容器ViewGroup是那些,須要着重區分LinearLayout這個父佈局,由於若是ImageView的父容器是LinearLayout,那麼咱們就沒法在LinearLayout中添加一個大小相同,位置和ImageView重合的SimpleDraweeView來加載圖片了,所以,此時咱們須要把這個ImageView拿出來,把它和SimpleDraweeView一塊兒裝在FrameLayout中,而後在把FrameLayout添加到ImageView原來在LinearLayout中所處的位置。

// 緊挨着ImageView添加SimpleDraweeView到原來的ImageView的位置
    private SimpleDraweeView exchangeChilde(ViewGroup parent, View testImageView, ViewGroup.LayoutParams layoutParams) {
        SimpleDraweeView draweeview =null;
        for (int i = 0; i < parent.getChildCount(); i++) {
            if (testImageView.equals(parent.getChildAt(i))) {
                if (testImageView instanceof ImageView) {
                    ImageView img= (ImageView) testImageView;
                    img.setBackgroundDrawable(null);
                    img.setImageDrawable(null);
                }
                if (i+1 < parent.getChildCount()) {
                    View child=parent.getChildAt(i+1);
                    // 此處理應作更加仔細的判斷
                    if (child instanceof SimpleDraweeView) {
                        return (SimpleDraweeView) child;
                    }
                }
                draweeview=new SimpleDraweeView(testImageView.getContext());
                draweeview.setLayoutParams(layoutParams);
                parent.addView(draweeview,i+1);
                return draweeview;
            }
        }
        return draweeview;
    }複製代碼

以上基本上就是以Fresco來實現圖片加載模塊的核心代碼了,基本能夠覆蓋原有的Glide的功能,而且入侵度低,無需修改原有代碼,隨時可替換。

勘誤

暫無


項目已經上傳了github,點此獲取,求star! 求follow !

相關文章
相關標籤/搜索