Android项目篇(二):开源库及工具的封装

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 中,欢迎讨论。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,598评论 25 707
  • 有的时候 NSTimer 的调度任务比较复杂,需要在调度方法中执行异步操作,并且在异步操作中需要回调原方法中的回调...
    张嘉夫阅读 833评论 1 51
  • 草莓,我喜欢 那是甜蜜,甜美的象征 记得那年与媳妇一块种了草莓 在新建的平房的东面 草莓的叶子是绿色的 小巧着身子...
    碧海青天2017阅读 241评论 0 0
  • 本来平静的生活被一场意外改变,至今为止,我还是希望它没有发生。 我重生了,占据了我女儿藻奈美的身体,我庆幸,可是我...
    紫渚阅读 2,441评论 5 2
  • 冬至是我国农历中的一个非常重要的节气,也是一个传统节日,至今仍有不少地方有过冬至节的习俗。冬至是北半球中白天最短、...
    席闪闪阅读 343评论 0 0