Android專案篇(二):開源庫及工具的封裝

weixin_33936401發表於2018-05-29

TicktockMusic 音樂播放器專案相關文章彙總:

在我們的專案中,總會不可避免的用到三方的開源專案。在開源庫的選擇上,我們一般會選擇成熟穩定,不斷更新,作者及時解決 issue 的專案。而且大部分開源專案開放的 api 已經非常方便,使用簡單,容易入手。但是,在我們做專案的過程中,最好再將開源專案進行封裝。本文以圖片載入框架和工具的封裝為例來討論下封裝的好處。

必要性

1、統一入口,邏輯改動方便。
2、若三方庫停止維護或者業務不滿足需求需要更換三方庫時,封裝後方便改動。

圖片載入框架的封裝

Android 專案中常用的圖片載入框架基本上都是 Picasso、Glide、Fresco、android-universal-image-loader,就近年來說,Picasso 和 Glide 由於其便捷性,更為受歡迎。本文就介紹下 Glide 的封裝。

首先,最簡單的封裝如下:


public class ImageLoader { 
    public static void display(Context context, String imageUrl, ImageView imageView) { 
        Glide.with(context).load(imageUrl).into(imageView);  
    } 
}


其實這樣已經可以應對一般的場景了,但是業務相對複雜的話,比如改變圖片的快取策略、僅在wifi下載入圖片、圖片圓角等,這種簡單的封裝就無法滿足了。 接下來我們採用策略模式進行封裝。

1、 建立基礎策略介面,所有的策略均實現此介面


public interface IBaseImageStrategy {

    void display(Context context, ImageConfig imageConfig);

    void clean(Context context, ImageView imageView);
}

2、 圖片載入配置類,此類包含用到的圖片載入引數,採用 Build 模式進行引數的配置,這樣使用更加靈活。


public class ImageConfig {

    private Object url;
    private ImageView imageView;
    ...

    public ImageConfig(Builder builder) {
        this.url = builder.url;
        this.imageView = builder.imageView;
        ...
    }

    ... //getter and setter
   
    public static class Builder {
        private Object url;
        private ImageView imageView;
       
        ...       

        public ImageConfig build() {
            return new ImageConfig(this);
        }
    }
}


3、具體的策略,比如我們採用 Glide ,則建立 GlideImageLoaderStrategy,可以根據 config 的屬性作為引數,來配置 Glide 具體的載入引數。


public class GlideImageLoaderStrategy implements IBaseImageStrategy {

    @Override
    public void display(Context context, ImageConfig imageConfig) {
        RequestOptions options = getOptions(context, imageConfig);
        Object url = getPath(imageConfig);
        if (!imageConfig.isAsBitmap()) {
            RequestBuilder<Drawable> requestBuilder = Glide.with(context)
                    .load(url)
                    .apply(options);
            if (!imageConfig.isRound() && imageConfig.getDuration() != 0) {
                requestBuilder = requestBuilder.transition(new DrawableTransitionOptions()
                        .crossFade(imageConfig.getDuration()));
            }
            requestBuilder.into(imageConfig.getImageView());
        } else {
            RequestBuilder<Bitmap> requestBuilder = Glide.with(context)
                    .asBitmap()
                    .load(url)
                    .apply(options);
            if (!imageConfig.isRound() && imageConfig.getDuration() != 0) {
                requestBuilder = requestBuilder.transition(new BitmapTransitionOptions()
                        .crossFade(imageConfig.getDuration()));
            }
            requestBuilder.into(imageConfig.getTarget());
        }
    }

    /**
     * Glide 配置
     *
     * @param context     context
     * @param imageConfig 配置
     * @return Glide配置
     */
    private RequestOptions getOptions(Context context, ImageConfig imageConfig) {
        RequestOptions options = new RequestOptions()
                .placeholder(imageConfig.getDefaultRes())
                .error(imageConfig.getErrorRes());
        ...
        return options;
    }


    @Override
    public void clean(Context context, ImageView imageView) {
        Glide.with(context).clear(imageView);
    }

4、統一入口,此處採用單例模式。


public class ImageLoader implements IBaseImageStrategy {

    private static ImageLoader INSTANCE;
    private IBaseImageStrategy mImageStrategy;

    private ImageLoader(){
        mImageStrategy = new GlideImageLoaderStrategy();
    }

    public static ImageLoader getInstance() {
        if (INSTANCE == null) {
            synchronized (ImageLoader.class) {
                if (INSTANCE == null) {
                    INSTANCE = new ImageLoader();
                }
            }
        }
        return INSTANCE;
    }

    @Override
    public void display(Context context, ImageConfig imageConfig) {
        mImageStrategy.display(context, imageConfig);
    }

    @Override
    public void clean(Context context, ImageView imageView) {
        mImageStrategy.clean(context, imageView);
    }
}

5、使用,所有的載入均使用統一的 ImageLoader 進行載入。


 ImageLoader.getInstance().display(context, new ImageConfig.Builder()
                .url(url)
                .placeholder(R.drawable.ic_placeholder)
                .into(imageView)
                .build());

6、當我們因為一些原因需要切換圖片載入庫,比如之前用的 Picasso(策略類為 PicassoImageLoaderStrategy),由於要載入 gif 圖片,所以切換到 Glide,這樣我們只用新建一個 GlideImageLoaderStrategy,在 ImageLoader 中改變 IBaseImageStrategy 的例項類即可切換。

圖片載入框架封裝總結

綜上,這種封裝模式的好處就是便於根據業務及其他需求進行擴充套件和維護。當然封裝並不是萬能的,比如使用 Glide 可能用到的 Target ,在 Picasso 中並沒有此類,所以用到 Target 的地方我們仍然需要一個個手動的更改。另外,Fresco 由於使用時涉及 xml 檔案等,用法相對特殊,所以使用 Fresco 的話,封裝很難顧及到。本文的封裝主要是提供思路,以一種相對維護性高的方式進行封裝。

工具的封裝

上邊以 Glide 為例介紹了開源專案的封裝。我們在專案過程中,也會用到各種工具,接下來介紹下 SharedPrefrences 的封裝。 SharedPrefrences 經常用來儲存一些簡單的資訊,如各種狀態,部分快取等。由於其特殊性,所以很多人會寫一個工具類來簡單處理下 SharedPrefrences 的使用,但是 SharedPrefrences 作為一種資料儲存的手段,我覺得還是需要重視起來,以便處理後續的需求。接下來就介紹下 SharedPrefrences 的封裝。

1、定義資料倉儲的介面,所有的資料倉儲介面都繼承此介面,並且建立此介面的實現類。

資料倉儲介面:


public interface DataRepo {

    void put(String key, String value);

    void put(String key, int value);

    void put(String key, boolean value);

    void put(String key, long value);

    String getString(String key);

    String getString(String key, String defaultValue);

    int getInt(String key);

    int getInt(String key, int defaultValue);

    long getLong(String key);

    boolean getBoolean(String key);

    boolean getBoolean(String key, boolean defaultValue);

    void remove(String key);

    Map<String, ?> getAll();

    boolean contains(String key);

    void clear();
}

實現類:


public class SharedPreferenceDataRepo implements DataRepo {

    private final SharedPreferences mSharedPreferences;

    public SharedPreferenceDataRepo(Context context, String fileName, int mode) {
        mSharedPreferences = context.getSharedPreferences(fileName, mode);
    }

    @Override
    public void put(String key, String value) {
        mSharedPreferences.edit().putString(key, value).commit();
    }

    ...

    @Override
    public void clear() {
        mSharedPreferences.edit().clear().commit();
    }
}


2、定義具體的倉庫介面,如 CacheRepo,並建立此介面的實現類。

倉庫介面:


public interface CacheRepo extends DataRepo{

    void setCache(String key, String value);

    String getCache(String key);

}


實現類:


public class CacheRepoImpl extends SharedPreferenceDataRepo implements CacheRepo {

    private static final String FILE_NAME = "cache_sp";

    public CacheRepoImpl(Context context) {
        super(context, FILE_NAME, Context.MODE_PRIVATE);
    }

    @Override
    public void setCache(String key, String value) {
        put(key, value);
    }

    @Override
    public String getCache(String key) {
        return getString(key);
    }
}

3、統一入口,這裡類似一個工廠方法,根據需要來獲取不同的資料倉儲。


public class DataManager {

    private final CacheRepo mCacheRepo;
    private final ConfigRepo mConfigRepo;
    private volatile static DataManager INSTANCE;

    private DataManager(Context context) {
        mCacheRepo = new CacheRepoImpl(context);
        mConfigRepo = new ConfigRepoImpl(context);
    }

    public static DataManager getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (DataManager.class) {
                if (INSTANCE == null) {
                    INSTANCE = new DataManager(context);
                }
            }
        }
        return INSTANCE;
    }

    public CacheRepo getCacheRepo() {
        return mCacheRepo;
    }

    public ConfigRepo getConfigRepo() {
        return mConfigRepo;
    }
}


使用,統一通過 DataManager 進行載入:


DataManager.getInstance(mContext).getCacheRepo().getCache("key");

SharedPrefrences 封裝總結

SharedPrefrences 的封裝和 Glide 類似,其實也都是為了提高可維護性。由於 SharedPrefrences 可以作為資料儲存的一種方式,所以為了方便的改變儲存方式,我覺得有必要封裝一下的。舉個例子,假如我們專案一開始利用 SharedPrefrences 快取使用者的一些資訊,但是隨著業務逐漸複雜起來,需要快取的使用者資訊逐漸增多,或者需要快取多個使用者的資訊,並且支援一些快取資料的增刪改查,這再繼續使用 SharedPrefrences 就不太合適了,採用資料庫比較合適。利用簡單封裝的工具類修改的話,可能每個使用到的地方都需要手動的更改使用方法,但是通過上述的封裝,我們可以很方便的轉化為資料庫的儲存方式,這樣維護起來就比較方便了。

總結

對於封裝,也要考慮開源專案的本身因素,假如侵入性比較強或者作者更新及時、專案比較複雜之類的,也可以根據人力考慮是否封裝及如何封裝。
本文以 Glide 和 SharedPrefrences 為例分別介紹了開源專案和工具的封裝,封裝的好處是為了提高程式碼的可維護性和擴充套件性等,以便應付不斷變化的需求及其他因素。所有的程式碼都在 TicktockMusic 中,歡迎討論。

相關文章