少年,老夫带你撸一把Android项目框架,你可想学?

时间从来没有等过我们,岁月这把捅猪刀.捅得你满脸都是沧桑.你一定是为工作操碎了心.不知道现在的身处何处,是否有挚爱的人照顾你.过得快乐或委屈?
哦忘了.你是个有故事的人,"你想ta过得比你要好,希望你永远不都会知道".


有时在过度劳累之后,腰腿酸痛精神不振,好像身体被掏空,是不是*透支了?

骚年莫慌,老夫带你撸个框架,进可重振雄风,退可养精蓄锐!

本篇适合什么样的人群看?

  • 做android有一段时间了,撸码就是一把梭。
  • 项目开工,但是目前不知道怎么去拆分业务和技术依赖分层。
  • 刚刚毕业出来,进了一家不大不小的公司一人我因酒醉,两眼是...啊,呸呸呸!一人单挑项目。

你能在本篇文章中收获什么?

  • 自己动手搭一个通用型依赖框架
  • 学习拆分业务层和技术层
  • and 吹吹水 装装哔

目录

  • 前言
  • 踩坑
  • 分析
  • 实现
  • 总结

前言

本篇是从线上的APP中基于一次次的采坑、总结、分享抽离出来的。但由于商业原因具有不可公开性。所以不会提及任何与业务有关联的场景用例。以及不会包含与任何商用项目相干的提及。所有技术仅仅用于技术学习交流。本篇不会是一个纯代码讲解的文章。文本中的故事也是一个虚构的。具体技术实现请您移步致GIthub,地址在篇尾。特此声明。

致谢

感谢 jeasinlee、LeoFangQ、Michael、zaneCC 几位前辈无私的分享才得已产出。

踩坑

什么?要求一个月后上线?老夫就是一把梭。复制粘贴东拼西凑就能出来。
上线一周后。小王啊。经过产品和老板讨论过后,我们下一步要把首页上的XX改一下。后面主流程不这样走了。你接下来...?
fuck!为毛你们之前不想好。这怎么改。代码是一大坨的。动一下可能就会导致到处都会崩溃。

项目中的代码是下面这样的:

你叫劳资怎么改?当初是你说要一个月上线。这个项目的代码以前只有我和神能看懂。现在只有神了。

分析

想想就气人,都是你们这帮孙子逼逼逼,连夜加班一个月赶出来的东西。上线不到一周就要改。这下我可头大了。之前没有考虑到后期可能会出现维护或者扩展。改?还不要了我命。但也没有更好的办法了。只好默默的理代码了。

第二天项目里进来了一个看起来像dalao的气息的人,dalao看完会心一笑,小王啊。这个时候我们宁可重构一把也不可在原有的基础上改,救得了今天救不过明天。这样,我向上级申请延长一些时间,我们一起来重新设计一把架构把。

之前的项目结构:

一把梭项目架构

dalao的项目结构:


dalao的架构

实现

等一下,dalao。你直接给我上个图,我看不懂呀。并且也没有解释清楚怎么实现分离的呢。

dalao咳咳两声喃喃道:首先,我们整体的思路是让现在开发的APP去依赖AndroidBasicLibs,我们尽量做到精简只需要在Gradle依赖它就行了。

 dependencies { compile 'xxx.xxx.xxx:AndroidBasicLibs' }

AndroidBasicLibs作为一个通用型的依赖库,它拥有三只麒麟臂,分别是basekit、common、uihelper。

AndroidBasicLibs

basekit

basekit是一个基于MVP+RXjava的的基础框架,它承载了MVP的设计风格,我们的上层APP只需要继承它的BaseActivity、BaseModel 、IBaseView 、BasePresenter 就可以了。上层要做的仅仅只是往对应的层填充独有的业务。如下图5是Base。 图6是APP中的业务单元。具体用法请参考GIthub,地址在本文末尾。

图5
图6

common

common是一个通用型的工具箱集,它可以对上层所依赖的第三方框架做解耦操作。如何理解这句话呢。假如我们要做图片加载,图片加载框架你肯定不会自己重复造轮子。在没有common层时。项目1.0的依赖的是ImageLoader,2.0的时候发现这个库有BUG需要将ImageLoader替换成主流的glide框架。但因为项目中多处都有使用到ImageLoader的API。无法做到加载图片处只改一行代码,就能让所有的业务都换了新的加载工具。这时common的威力就显而易见。,中间做解耦,上层调用common。common调用第三方依赖。当然这只是一个列子。还有很多地方都可以进行二次封装。比如网络请求、视图动画等我们把这些封装都放在common。下面是图片加载的二次封装实现代码。

//用接口统一约束Api
public interface ILoader {
    void init(Context context);

    void loadNet(ImageView target, String url, Options options);

    void loadResource(ImageView target, int resId, Options options);

    void loadAssets(ImageView target, String assetName, Options options);

    void loadFile(ImageView target, File file, Options options);

    void clearMemoryCache(Context context);

    void clearDiskCache(Context context);

    class Options {

        public static final int RES_NONE = -1;
        public int loadingResId = RES_NONE;//加载中的资源id
        public int loadErrorResId = RES_NONE;//加载失败的资源id

        public static Options defaultOptions() {
            return new Options(JConfig.IL_LOADING_RES, JConfig.IL_ERROR_RES);
        }

        public Options(int loadingResId, int loadErrorResId) {
            this.loadingResId = loadingResId;
            this.loadErrorResId = loadErrorResId;
        }
    }
}

//具体的实现
public class GlideLoader implements ILoader {
    @Override
    public void init(Context context) {

    }

    @Override
    public void loadNet(ImageView target, String url, Options options) {
        load(getRequestManager(target.getContext()).load(url), target, options);
    }

    @Override
    public void loadResource(ImageView target, int resId, Options options) {
        load(getRequestManager(target.getContext()).load(resId), target, options);
    }

    @Override
    public void loadAssets(ImageView target, String assetName, Options options) {
        load(getRequestManager(target.getContext()).load("file:///android_asset/" + assetName), target, options);
    }

    @Override
    public void loadFile(ImageView target, File file, Options options) {
        load(getRequestManager(target.getContext()).load(file), target, options);
    }

    @Override
    public void clearMemoryCache(Context context) {
        Glide.get(context).clearMemory();
    }

    @Override
    public void clearDiskCache(Context context) {
        Glide.get(context).clearDiskCache();
    }

    private RequestManager getRequestManager(Context context) {
        return Glide.with(context);
    }

    private void load(DrawableTypeRequest request, ImageView target, Options options) {
        if (options == null) options = Options.defaultOptions();

        if (options.loadingResId != Options.RES_NONE) {
            request.placeholder(options.loadingResId);
        }
        if (options.loadErrorResId != Options.RES_NONE) {
            request.error(options.loadErrorResId);
        }
        request.crossFade().into(target);
    }
}

//暴露给上层加载用的Factory
public class LoaderFactory {
    private static ILoader loader;

    public static ILoader getLoader() {
        if (loader == null) {
            synchronized (LoaderFactory.class) {
                if (loader == null) {
                    loader = new GlideLoader();
                }
            }
        }
        return loader;
    }
}

uihelper

uihelper是一个自定义View的依赖。所有不含业务型的View都应该放在这个module。

APP

APP就是上层的项目了,注意在androidStudio的module中除了可以传递依赖以外,还可以有自己独立的依赖,所以我们可以在APP中对业务类型的依赖添加在此处。如:后端SDK、友盟统计、微博分享等,只属于该项目中才会出现的就应当放在此处。

来我们再来看一眼构架图,是不是理清楚多了。

对比:

一把梭项目架构:

优点:

  • 快速开发
  • 无脑堆业务

缺点:

  • 维护成本高
  • 可阅读性差
  • 扩展性低

AndroidBasicLibs构架:

优点:

  • 可扩展
  • 可重用
  • 可维护
  • 更快的速度开发
  • 底层与业务抽离

缺点:

  • 前期搭建费时间
  • 依赖结构的层级略多

总结

项目的架构设计绝不是一成不变的套路,应该根据业务的类型去做模块拆解。并且实际上个人认为很多时候开发第一个版本的项目即便你考虑去搭建一套框架去写。但会因为进度和协同合作方面等因素导致理想状态不太一样。在线上稳定住后,就应该考虑去重构你的项目。见过一位同事的编码风格是边写业务边重构之前的代码。会导致与你协同开发的同事。翻阅过后就懵逼了。每次都要重读一遍。这不是一个好办法。最好与团队中成员达成一致的意见后开整。

本来这篇我并不想发表的,怕是自己学艺不精误导了新人。加上工作上的变动,近期略显仓促。希望各位看官多多给点评。觉得有收获就点个喜欢就是对我最大的鼓励。

最后本框架的地址是:https://github.com/wwah/AndroidBasicLibs


如何下次找到我?

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

推荐阅读更多精彩内容