Fresco實踐總結

Fresco實踐總結-高斯模糊、圓形圓角、URL、File、Assets、Resource


題外話:在這之前的一段時間裏,由於種種原因,一直在忙,在這草長鶯飛的三月,開啓我2017的第一篇博文。2017,讓我們共同創造神話。本篇文章轉載自:http://blog.yanzhenjie.com


Code Behavior, one can.t be less.

Fresco是一個Facebook開源的Android圖片加載庫,性能真的讓我無話可說,而且滿足了我對圖片加載的一切幻想,所以我必須爲它寫一篇文章,當然更多的是自己的總結與記錄。 
Fresco開源地址:https://github.com/facebook/fresco 
Fresco文檔地址:https://www.fresco-cn.org

前端時間我寫了一個Android相冊庫Album:https://github.com/yanzhenjie/album,當時我在做測試的時候發現,用Picasso或者Glide時,當列表達到上千條時,滑動起來就會卡,但是換成Fresco後一點都不卡了,而且Fresco做到的幾個內置效果讓我欣喜若狂,所以我把我的使用總結記錄下來:

依賴Fresco

// 一般依賴:
compile 'com.facebook.fresco:fresco:0.14.1'

// 如果需要支持gif,再添加:
compile 'com.facebook.fresco:animated-gif:0.12.0'
  • 1
  • 2
  • 3
  • 4
  • 5

初始化

建議在App啓動就初始化,所以建議寫在Application#onCreate()中,記得在manifest.xml註冊Application

一般初始化:

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Fresco.initialize(this);
    }
    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

高級初始化-配置緩存文件夾

// 高級初始化:
Fresco.initialize(this, ImagePipelineConfig.newBuilder(App.this)
    .setMainDiskCacheConfig(
        DiskCacheConfig.newBuilder(this)
            .setBaseDirectoryPath(new File("SD卡路徑")) // 注意Android運行時權限。
            .build()
    )
    .build()
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面的高級初始化當然不止這麼一點點,這裏舉出一個敏感的例子,就是配置緩存SD卡路徑,這裏涉及到Android6.0運行時權限,我也給一個解決方案,我使用的權限管理庫是AndPermissionhttps://github.com/yanzhenjie/AndPermission):

首先在Application中判斷是否有SD卡權限,如果有則初始化到SD卡,如果沒有則採用默認配置:

public class App extends Application {

    private static App app;

    @Override
    public void onCreate() {
        super.onCreate();

        app = this;

        // 如果有SD卡權限則直接初始化到SD卡。
        if (AndPermission.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE))
            initFresco();
        else { // 沒有權限,暫時使用默認配置。
            Fresco.initialize(this);
        }

    }

    /**
     * 高級初始話Fresco。
     */
    public void initFresco() {
        // 高級初始化:
        Fresco.initialize(this, ImagePipelineConfig.newBuilder(App.this)
                .setMainDiskCacheConfig(
                        DiskCacheConfig.newBuilder(this)
                                .setBaseDirectoryPath(new File("SD卡的路徑..."))
                                .build()
                )
                .build()
        );
    }

    public static App get() {
        return app;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

然後在SplashActivity申請SD卡權限,已被下次進入App時初始化Fresco時擁有SD卡權限:

public class SplashActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 申請權限。
        AndPermission.with(this)
                .requestCode(100)
                .permission(Manifest.permission.READ_CALENDAR)
                .send();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, 
        @NonNull String[] permissions, @NonNull int[] grantResults) {
        AndPermission.onRequestPermissionsResult(requestCode, permissions, grantResults, listener);
    }

    /**
     * 權限監聽。
     */
    private PermissionListener listener = new PermissionListener() {
        @Override
        public void onSucceed(int requestCode, List<String> grantPermissions) {
            if(requestCode == 100)
                // 啓動app:
                startActivity(new Intent(SplashActivity.this, MainActivity.class));
        }

        @Override
        public void onFailed(int requestCode, List<String> deniedPermissions) {
            if(requestCode == 100)
                // 用戶不授權,則退出app:
                finish();
        }
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

高級初始化-配置網絡層爲OkHttp

Fresco默認使用HttpURLConnection作爲網絡層,當然也可以配置OkHttp作爲它的網絡層,配置OkHttp爲它的網絡層需要依賴下面的庫:

compile "com.facebook.fresco:imagepipeline-okhttp3:0.12.0+"
  • 1

然後在Application中初始化的時候注意:

/**
 * 初始話Fresco。
 */
public void initFresco() {
    // 你的OkHttpClient根據你的設計來,建議是單例:
    OkHttpClient okHttpClient = new OkHttpClient();
    Fresco.initialize(this, OkHttpImagePipelineConfigFactory.newBuilder(App.this, okHttpClient)
        .setMainDiskCacheConfig(
            DiskCacheConfig.newBuilder(this)
                .setBaseDirectoryPath(new File("SD卡的路徑..."))
                .build()
        )
        .build()
    );
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

這裏只需要注意原來的ImagePipelineConfig換成了OkHttpImagePipelineConfigFactory,並且需要一個OkHttpClient的對象。

加載網絡圖片、url、assets、res、本地File圖片

先給出支持的URI格式列表(列表來自fresco-cn.org):

Type Scheme Sample
http遠程圖片 http://或者https:// HttpURLConnection或者OkHttp
本地文件 file:// FileInputStream
Content provider content:// ContentResolver
res目錄下的資源 res:// Resources.openRawResource
asset目錄下的資源 asset:// AssetManager
Uri中指定圖片數據 data:mime/type;base64, 數據類型必須符合rfc2397規定 (僅支持 UTF-8)

SimpleDraweeView

實際開發中,如果沒有特殊需求,我們一般使用SimpleDraweeView來佔位,傳統的圖片加載框架一般是使用ImageView,例如:

<ImageView
.../>
  • 1
  • 2

在使用Fresco時我們一般使用SimpleDraweeView,它也是繼承ImageView的:

<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/iv_head_background"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dp_200"/>
  • 1
  • 2
  • 3
  • 4

把它當成我們平常使用的ImageView即可,不過我們要注意Fresco不支持wrap_content(具體原因看這裏),需要使用match_parent或者顯示指定view寬高,有些同學看到這個就很頭疼了,但是這真的不是什麼問題,下面給出解決方案:

一、由服務器返回URL時返回圖片的寬高信息 
平常我們服務器這樣返回圖片url的:

{
    "name":"嚴振杰",
    "head":"http://www.yanzhenjie.com/images/main/yzj_head.png"
}
  • 1
  • 2
  • 3
  • 4

使用Fresco我們可以把一個圖片url當成一個對象包裹起來:

{
    "name":"嚴振杰",
    "head": 
        {
            "url":"http://www.yanzhenjie.com/images/main/yzj_head.png",
            "width":"500",
            "height":"500"
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

或者在URL後面跟一個寬高的參數:

{
    "name":"嚴振杰",
    "head":"http://www.yanzhenjie.com/images/main/yzj_head.png?width=500&height=500"
}
  • 1
  • 2
  • 3
  • 4

二、根據設計師的給的尺寸,預先設置圖片寬高 
設計師設計UI的時候肯定會用一個屏幕作爲標準,比如iOS的750*1340,或者Android的720*1280,我們可以根據設計圖和手機實際寬高計算出View在手機中應有的寬高,見下面的代碼。

最後:我們在解析出來寬高後,我們可以動態的設置SimpleDraweeView的寬高:

/**
 * 設置view大小。
 *
 * @param view  View。
 * @param width 指定寬。
 * @param width 指定高。
 */
public static void requestLayout(View view, int width, int height) {
    ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
    if (layoutParams == null) {
        layoutParams = new ViewGroup.LayoutParams(width, height);
        view.setLayoutParams(layoutParams);
    } else {
        view.getLayoutParams().width = width;
        view.getLayoutParams().height = height;
        view.requestLayout();
    }
}

/**
 * 根據設計圖寬高,計算出View在該屏幕上的實際寬高。
 *
 * @param width  設計圖中View寬。
 * @param height 設計圖中View高。
 */
public static void calcRealSizeByDesign(View view, int width, int height) {
    int realWidth, realHeight;
    realWidth = 設備屏幕寬度 * width / 設計圖屏幕寬度;
    realHeight = measure[0] * height / width;
    requestLayout(view, realWidth, realHeight);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

基礎配置和注意的地方講完了,那麼下面就是重頭戲了,如何加載圖片。

一、加載http/https遠程圖片

/**
 * 顯示http或者https遠程圖片。
 *
 * @param draweeView imageView。
 * @param url        連接地址。
 */
public static void showUrl(SimpleDraweeView draweeView, String url) {
    try {
        draweeView.setImageURI(Uri.parse(url));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

二、顯示本地圖片

這裏就有個坑了,先看一下下面我寫了兩個方法,一個需要傳入View的實際寬高,一個不需要。上面已經說了,SimpleDraweeView需要在xml中、java中指定它的寬高,或者是使用match_parent

這裏需要注意,1. 如果view指定的寬高不是match_parent則直接調用第二個不需要傳入寬高的發那個發,如果爲SimpleDraweeView寫的寬高是match_parent時,加載圖片需要告訴Fresco你的View在屏幕上的實際寬高是多少,否則是不能加載出來的。

比如,你的SimpleDraweeView是全屏的,那麼你就填入屏幕的寬高,如果不是全屏,就利用上面講的方法測量出View的實際寬高後傳入。

/**
 * 顯示一個本地圖片。
 *
 * @param draweeView imageView。
 * @param path       路徑。
 * @param width      實際寬。
 * @param height     實際高度。
 */
public static void showFile(SimpleDraweeView draweeView, String path, int width, int height) {
    try {
        Uri uri = Uri.parse("file://" + path);
        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
                .setResizeOptions(new ResizeOptions(width, height))
                .build();
        AbstractDraweeController controller = Fresco.newDraweeControllerBuilder()
                .setOldController(draweeView.getController())
                .setImageRequest(request)
                .build();
        draweeView.setController(controller);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 顯示本地圖片。
 *
 * @param draweeView imageView。
 * @param path       路徑。
 */
public static void showFile(SimpleDraweeView draweeView, String path) {
    try {
        Uri uri = Uri.parse("file://" + path);
        draweeView.setImageURI(uri);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

三、顯示res中圖片

這裏要注意,我們在爲res中的圖片生成Uri的時候:

Uri uri = Uri.parse("res://包名(任何字符串或者留空)/" + R.drawable.ic_launcher);
  • 1

所以我們一般留空,因此我們的代碼看起來是下面的樣子:

/**
 * 顯示一個Res中的圖片。
 *
 * @param draweeView ImageView。
 * @param resId      資源ID。
 */
public static void showRes(SimpleDraweeView draweeView, @DrawableRes int resId) {
    try {
        // 你沒看錯,這裏是三個///。
        draweeView.setImageURI(Uri.parse("res:///" + resId));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

四、顯示ContentProvider圖片

/**
 * 顯示content provider圖片。
 *
 * @param draweeView image view。
 * @param path       路徑。
 */
public static void showContentProvider(SimpleDraweeView draweeView, String path) {
    try {
        draweeView.setImageURI(Uri.parse("content://" + path));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

五、顯示assets中的圖片

/**
 * 顯示Assets中的圖片。
 *
 * @param draweeView ImageView.
 * @param path       路徑。
 */
public static void showAsset(SimpleDraweeView draweeView, String path) {
    try {
        draweeView.setImageURI(Uri.parse("asset://" + path));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

你以爲到這裏就完了嗎?並沒有,繼續看。

一些默認屬性的設置

<com.facebook.drawee.view.SimpleDraweeView
  android:layout_width="20dp"
  android:layout_height="20dp"
  fresco:fadeDuration="300" // 淡出時間,毫秒。
  fresco:actualImageScaleType="focusCrop" // 等同於android:scaleType。
  fresco:placeholderImage="@color/wait_color" // 加載中…時顯示的圖。
  fresco:placeholderImageScaleType="fitCenter" // 加載中…顯示圖的縮放模式。
  fresco:failureImage="@drawable/error" // 加載失敗時顯示的圖。
  fresco:failureImageScaleType="centerInside" // 加載失敗時顯示圖的縮放模式。
  fresco:retryImage="@drawable/retrying" // 重試時顯示圖。
  fresco:retryImageScaleType="centerCrop" // 重試時顯示圖的縮放模式。
  fresco:progressBarImage="@drawable/progress_bar" // 進度條顯示圖。
  fresco:progressBarImageScaleType="centerInside" // 進度條時顯示圖的縮放模式。
  fresco:progressBarAutoRotateInterval="1000" // 進度條旋轉時間間隔。
  fresco:backgroundImage="@color/blue" // 背景圖,不會被View遮擋。

  fresco:roundAsCircle="false" // 是否是圓形圖片。
  fresco:roundedCornerRadius="1dp" // 四角圓角度數,如果是圓形圖片,這個屬性被忽略。
  fresco:roundTopLeft="true" // 左上角是否圓角。
  fresco:roundTopRight="false" // 右上角是否圓角。
  fresco:roundBottomLeft="false" // 左下角是否圓角。
  fresco:roundBottomRight="true" // 左下角是否圓角。
  fresco:roundingBorderWidth="2dp" // 描邊的寬度。
  fresco:roundingBorderColor="@color/border_color" 描邊的顏色。
/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

xml中可以配置,在Java代碼中也是可以配置的,我這裏列出一部分API:

SimpleDraweeView simpleDraweeView = new SimpleDraweeView(context);
simpleDraweeView.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));

GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(context.getResources())
    .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
    .setPlaceholderImage(R.drawable.fresco_failed)
    .setPlaceholderImageScaleType(ScalingUtils.ScaleType.FIT_XY)
    .setFailureImage(R.drawable.fresco_failed)
    .setPressedStateOverlay(ResCompat.getDrawable(R.drawable.transparent_half_1))
    .setFailureImageScaleType(ScalingUtils.ScaleType.FIT_XY)
    .build();
simpleDraweeView.setHierarchy(hierarchy);

// load image from ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

一些特殊效果

第一個,先來一個圓形圖片帶白色的邊:

圓形帶有描邊

綜合上面的屬性這裏就不多解釋了:

<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/iv_user_fund_user_head"
    android:layout_width="80dp"
    android:layout_height="80dp"
    fresco:roundAsCircle="true" // 圓形圖片。
    fresco:roundingBorderColor="@color/white" // 白色描邊。
    fresco:roundingBorderWidth="2dp"/> // 描邊寬度。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第二個,Fresco高斯模糊:

高斯模糊

/**
 * 以高斯模糊顯示。
 *
 * @param draweeView View。
 * @param url        url.
 * @param iterations 迭代次數,越大越魔化。
 * @param blurRadius 模糊圖半徑,必須大於0,越大越模糊。
 */
public static void showUrlBlur(SimpleDraweeView draweeView, String url, int iterations, int blurRadius) {
    try {
        Uri uri = Uri.parse(url);
        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
                .setPostprocessor(new IterativeBoxBlurPostProcessor(iterations, blurRadius))
                .build();
        AbstractDraweeController controller = Fresco.newDraweeControllerBuilder()
                .setOldController(draweeView.getController())
                .setImageRequest(request)
                .build();
        draweeView.setController(controller);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

效果就這樣子,其它的關於進度條子類的自己去試試吧,我就不演示佔篇幅了。

拿到緩存的Bitmap

有幾次在羣裏討論關於Fresco的問題,很多同學吐槽Fresco沒有直接拿到Bitmap的方法,其實直接拿到Bitmap相當於從SD卡讀取一個文件,如果圖片過大,就會耗時,造成App卡(假)死,所以我們要採用異步的方式:

/**
 * 加載圖片成bitmap。
 *
 * @param imageUrl 圖片地址。
 */
public static void loadToBitmap(String imageUrl, BaseBitmapDataSubscriber mDataSubscriber) {
    ImageRequest imageRequest = ImageRequestBuilder
        .newBuilderWithSource(Uri.parse(imageUrl))
        .setProgressiveRenderingEnabled(true)
        .build();

    ImagePipeline imagePipeline = Fresco.getImagePipeline();
    DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage
        (imageRequest, App.get());
    dataSource.subscribe(mDataSubscriber, CallerThreadExecutor.getInstance());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

用起來也很簡單:

loadToBitmap(imageUrl, new BaseBitmapDataSubscriber() {
    @Override
    public void onNewResultImpl(@Nullable Bitmap bitmap) {
        // 讀取成功。
    }

    @Override
    public void onFailureImpl(DataSource dataSource) {
        // 讀取失敗。
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

       

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章