Android APP架构的一些心得
什么是架构
在我看来,软件架构绝对不只是框架的堆砌,看我看来,架构是为了方便软件维护、扩展、安全性、切入性(我也不知道有没有人提出过这个关键字,因为的确很少看见,简单来说我这里说的切入性就是指一个以前没有接触过这个项目的人,能快速加入到这个项目中,对项目进行维护、修改和扩展)。
维护性
一个好的软件(不一定是成功的软件,这里说的好只是程序员认为的代码方面)肯定是能方便维护的,出了问题能快速定位,需要修改时能快速修改,并且在一定程度上不会说一修改就一堆bug,这就是我认为的可维护性,当然后面要说到的切入性其实也算是维护性,不过为什么单独放出来在切入性时我会详细说明。至于怎么样才能使一个软件维护方便,我觉得有以下几点:
1.代码规范
一份代码如果没有遵循任何规范,那么我相信它的可维护性是很差的,就算是你一个人做出来的,估计过了几个月去修改的时候也会冒出一句这TM是什么鬼。
2.框架稳定性
很多时候很多开源框架刚出来的时候,也许功能十分强大,但是毕竟刚出来,没有经过充分的测试,所以还是会或多或少存在一个不稳定因子,所以建议在选择框架时尽量选择成熟稳定的框架,哪怕功能和性能的确比不上刚出来的框架。当然这也不是说完全不用刚出来的框架,毕竟都不用,那么它也永远成熟不了,至于到底用不用和怎么用,本文的框架选择和使用篇也会详细分析说明。
3.封装
本来想说AOP的,但是个人觉得很多专业性的名词也比不上一些通俗的形容,毕竟本文主要的目的是让人理解,不是提出理论。封装这个在Android中是经常使用的,简单来说就是把一些常用的、通用的东西进行一个封装,通过统一入口进行调用,这样出问题就只需要修改一个地方,就能全部修改过来。同时封装也要注意一些常见的坑,比如我曾经就踩过的context坑,当时封装了一个UIUtils(主要是针对UI相关的工具类),因为很多方法都要使用context,所以直接application传进去,保存了,所有方法都不用传context,但是这样却出了一个bug,那就是有些操作用application的context是有问题的。当然这种还比较好处理,但是如果因为封装导致内存泄露,这就难以查找了,比如你传入了一个activity的context,但是activity已经关闭了,但是因为你封装的方法里面还在继续使用这个context,所以activity的内存也是不会释放的,所以封装的时候也一定要注意,不要给自己挖坑。
4.耦合
针对耦合这个东西相信很多文章都说过了,如何解耦合,不过个人感觉解耦合这个东西也要适度,不要因为解决一点耦合,花了大量的代码,浪费了大量的性能,所以解耦合这个东西就一定要把握这个度,过度设计是有问题的设计,这点我是赞同的。同时很多时候封装会导致一些耦合的问题,比如我曾经一个项目中就有个这个一个情况:
因为项目中使用了滑动选取身高的WheelView,因为当时是弹Dialog出来选择的,所以当时想也没想就直接封装了一个Dialog,后面又出来了一个选取年龄的,然后又封装了一个Dialog,以至于到后面封装的Dialog有7,8个了,但是有些界面因为选中后的处理有些不一样,导致Dialog里面的代码混乱,所以后面就直接简单的封装了一个Dialog,传入需要选中的数据集合和选中监听,这样就用了一个Dialog就能处理多种数据的选择,还能根据不同界面分别处理,当然这样也不是万金油,毕竟这样每个界面都需要自己实现监听,所以很多时候有利就有弊,至于具体怎么取舍,就看利是否大于弊。
扩展性
扩展性简单来说就是当程序需要新的功能时,能否对其进行扩展以及扩展的难度来判断,如何提高扩展性,我觉得有以下几点:
1.抽象接口
这点相信大家都经常遇到,比如Android的点击事件,你想要实现什么样的点击效果,自己实现一个点击监听,然后设置给控件就可以了。
2.元素重用
很多时候,很多功能模块可能使用到相同或者类似的元素,如何Android中的一些布局,这些如果抽取出公共部分在进行扩展的时候方便对其快速扩展,当然这个是项目一开始并不能预见的,所以需要在开发中不断的去重构。
3.单一职责
这个跟其实就是面向对象的单一职责,比如前面提到的Dialog,一开始只有选择身高的功能,看起来是单一职责,但是其实相关处理也包含在其中,所以那个Dialog类本身职责已经不单纯,当然如果一直只有这一个,这样做没有任何问题,也能看做单一职责,但是如果有多个选择和处理的时候,就必须对其重构了。
4.替换性
替换性包含里氏代换但是也不仅仅是里氏代换,比如常见的Android布局不同,但是其显示内容大致相同,这样写布局的时候就可以对相同内容的控件指定相同的id,这样就算替换布局,也不用重写ViewHolder,当然对于Adapter也仅仅只需要替换相应的布局就ok。
5.耦合
这个和维护的耦合相同,毕竟很多地方本来就存在交叉,所以就没有必要再说了。
安全性
个人觉得数据安全性并不仅仅是数据安全,还有程序的一些操作安全性(简单来说就是避免程序出现一些非崩溃性异常)。
1.数据安全性
数据安全就包括数据抓取、数据拦截以及数据修改。当然这些并不能完全避免,只能是由我们写出尽量安全的代码,比如关键数据使用HTTPS以及对数据进行md5验证完整性,对于数据修改,可以通过多文件多地址保存文件修改记录,来确定保存的数据是否被修改,毕竟Android只要获取root权限,就能对很多文件进行修改了。
2.操作安全性
简单来说经常遇到的一个问题,比如按钮的点击事件,有可能这个点击事件是请求网络或者打开Activity,这样就会存在事件还未处理完成再次收到事件,只要你一直猛点,肯定可以的,所以这样就需要我们队控件事件进行一些封装,比如打开界面的,可以在点击后禁用按钮,界面打开完成后才启用,请求网络的可以在开始就禁用按钮,请求结果反馈了才启用(不管是请求成功或者失败)。
切入性
切入性就是当另外一个从未接触过此项目的人,能快速进入这个项目进行开发,当然想要切入性好,前面的维护和扩展是必须要满足的,下面我就说说我认为能增加切入性的一些点。
1.文档
开发都不喜欢写文档,这是肯定的,但是每当我们去接手一个项目的时候,发现没有文档估计就要开始骂娘了,所以文档不仅要写,还要写的规范。我认为开发中必须要有的几个文档:代码规范文档(比如包名规范,文件命名规范,id命名规范等等,具体依据项目情况而定)、接口文档。
2.注释
每个类必须要有注释,方法也要有注释,同时也要标注好方法最后修改人是谁,这样出现疑问或者问题别人就知道该去找谁了,当然有些方法是不需要有注释的,比如重写父类的方法,只是如果方法内逻辑很复杂,可以在方法中添加一些对逻辑的说明。当然注释也不是越多越好,具体注释该怎么写,Google最清楚,不能Google百度也行。
3.包名
什么类放什么包简单,但是一但一个包中的类太多,也是非常不方便,所以正确的分包也是非常重要的,目前常见的Android分包包括针对功能分包(不是指程序功能,而是指UI,http,bean这些功能),还有就是模块分包(这就是程序的功能了,比如login,user等),当然具体怎么分包需要团队协商,防止一个包中类太多,而我现在一个程序很大的情况下,采用的分包是先功能分包,再模块分包,比如:
wang.raye.demo
|-activity
| |-user
| |-login
|-fragment
| |--user
| |--login
这样能减少每个包的类数量,当然如果项目本身并不是很大,可以完全不用这种分包模式,毕竟如果只是小项目,这样会使项目变得更加难以阅读。
MVC 还是 MVP
现在针对移动端开发,衍生了很多种架构,如MVC、MVP、MVVM,当然这里着重分析MVC和MVP,毕竟MVVM我也只是了解过一下,没有详细接触,至于什么是MVC和MVP我也不想做过多描述,这类的文章实在太多,这里主要分析一下什么情况下用MVC和MVP。
至于很多人接触过MVP后就觉得MVC就一无是处,我个人觉得这是不对的,不同的项目,不同的业务,用不同的架构,这是我觉得应该做的事,没有一种架构能适合所有项目开发(毕竟现在还没有),所以我们分析MVC和MVP分别在Android以什么样的方式实现。
MVC
MVC是以XML布局为V(视图),Activity或Fragment为C(控制器),数据实体为M(模型),但是因为XML的局限性,所以其实我们还是需要在Activity或Fragment中对视图进行操作,所以这也就是为什么那么多人抵制MVC的原因,因为这也算不上完整的MVC。
优点:开发迅速,结构易理解。
缺点:当一个界面业务逻辑一多,不方便维护。
MVP
MVP是为XML配合Activity或Fragment为V(视图),同时抽象出接口,界面相关业务抽离出来的P(Presenter)同时通过视图接口来更新UI,数据实体为M(模型)。
优点:业务发生变化时易修改,同时能减少修改过程中引发bug,也能将多人协同开发充分调用起来(并不是针对一个人负责一个模块的模式,而是多人协同开发一个模块)。
缺点:开发速度会有所降低
所以对比2种架构,发现MVC适合不需要太多业务逻辑和功能性少的APP,比如数据展示类应用,MVP适合每个界面有复杂逻辑以及大型多人开发的APP。
框架选择及使用
如何选择框架
1.稳定性
如果框架本身就不稳定,那么导致的结果就是程序本身也会漏洞百出,所以选择框架一定要选择经历过考验的稳定的框架。
2.扩展性
随着程序功能的增加,以前的框架可能会出现功能不足的情况,但是因为这点是不可预见的,所以我们选择框架时一定要了解好框架本身的扩展性如何,或者对框架有较深的理解,能够自己扩展框架,当然有些框架解决的问题比较单一,一般也不用担心过多的扩展性,比如Butterknife或PreIOC这类单一性框架,但是有些框架经常需要配合做一些操作,比如图片加载框架,常见的一些就是清理图片缓存、获取图片缓存大小、显示圆角或者圆形图片, 常用的图片加载框架UniversalImageLoader都提供了相关的方法或接口来实现。
3.封装性
封装性是指能否针对框架进行二次封装,以及封装后的耦合度,详细会在使用篇说明。
如何使用
选择好了框架千万不要拿来就用,因为再好的框架也有它局限的地方,当然你也可以简单的在遇到这个框架不能实现的时候,添加另外一种框架,只是这样项目会越来越大,对于APP来说APK也越来越大,65535 的问题也会提前出现,所以为了方便以后有可能出现的切换框架,以及防止初期对框架使用不熟悉而引发出新的bug,在选择好了框架后,一定要对框架进行二次封装,当然有些框架是不需要二次封装的,比如前面说的单一性的框架Butterknife或PreIOC,但是像UniversalImageLoader、OKHttp等框架,必须要进行二次封装,至于封装原则,则是封装后,调用框架对于调用代码来说是透明的,简单来说,就是对于框架调用都通过一个统一的入口进入,并且调用时,不需要传入任何跟框架相关的东西,如果必须要传入接口,可以通过继承框架来实现新的接口传入,这样在真正的使用框架的地方,没有任何关于框架的引用。
封装的好处
之所以要这样封装,最大的好处就是一旦框架不能满足需求时,需要进行框架更换时,只需要换掉框架,同时修改统一入口处的代码,就能快速的替换整个框架。
结尾
以上就是我在Android APP架构上面的一些心得,此文可能并没有教你快速搭建一个框架,只是指明搭建框架时需要注意和搭建框架的一个方向,当然软件是死的人是活的,具体项目具体处理,毕竟别人的东西只能是借鉴和学习其好的思想。