最近在组建项目组从0开始开发项目,从立项到上线,有一些心得,包括项目规范、结构、优化、三方等,与大家分享,一起修仙!
接下来我会以自己写的两个项目为例,结合起来分析。代码已经上传github:
先上图来看看吧:
项目采用MVP+Retrofit+RxJava+Gson+Glide+Material Design设计
同时也使用了EventBus事件总线、GreenDao数据库、SurfaceView+MediaPlayer视频播放器等主要技术。
ok!项目就简单介绍到这里,接下来开始我们的修仙之路。
1、项目整体分析
a、UI风格
logo
主题风格:
- Material Design
- 仿ios
- 主界面–侧拉
- 主界面–仿微信 选项卡等
b、技术方案
整体分析项目,确定整体技术方案,之后的开发中按照技术方案执行,同时编写相关文档。
- 项目架构
- 网络框架
- 图片处理
- 数据处理/接口格式
- 三方使用:统计、推送、更新等
c、可行性分析
邀请项目经理、技术负责人、产品经理、后端开发、测试等共同分析技术方案的可行性,并相应调整技术方案。
d、工期
根据项目需求和技术方案,给出开发时长;
根据工期要求,可适当调整技术方案。
e、测试用例
测试人员开始整理、编写测试用例和项目标准文档;
个人觉得开发人员应该参与到测试工作中,这样方便自己对业务逻辑和功能分析全面,也帮助测试人员分析项目功能和技术,便于写出更加全面的测试用例。
2、开发规范
其实Android、Ios、Server等各端开发人员,都应该有完整的、严格的开发规范,这里我只说一下Android端的开发规范:
以自己项目为例,涉及到以下几项:
- 文档规范
- 资源命名规范
- 代码命名规范
- 代码注释规范
- 代码风格规范
- 服务器数据规范 等
a、文档
重要有以下几个文档:
- 需求文档
- 产品原型图
- 完整UI图
- 接口文档
- 核心技术文档
- 重点逻辑文档
- 程序框架图
- ER图、数据字典、类图等
- 测试文档
b、资源文件命名规范
名字全部小写,最好不用数字,全部英文,单词中间下划线隔开
~、drawable、anim等文件夹下
名称结构为“技术点模块点空间类型_功能名”结构,技术点主要有:selector、translate、alpha、scale等,模块名主要有:login、pay、mine、setting等,控件类型主要有:button、textview、imageview、dialog等,功能名主要有:findpwd、request、back、next等。
~、drawable-xhdpi等图片资源
名称为“技术点模块点功能名”结构,技术点主要有:activity、fragment、item、include等,模块点主要有:login、setting、mine、pay等,功能名主要有:head、title、back、sure等。
~、layout
布局名称为“技术点模块功能名”结构,技术点主要有:activity、fragment、item、include等,模块主要有:home、lesson、mine、loginregist等,功能名主要有:login、title、setting、pay、bar等。
~、values
布局名称为“技术点模块功能名”结构,技术点主要有:activity、fragment、item、include等,模块主要有:home、lesson、mine、loginregist等,功能名主要有:login、title、setting、pay、bar等。
c、代码命名规范
~、包名
包名为“根包名.技术点名.模块名”结构,全部小写
~、类名
采用 大驼峰 命名法,单词直接拼接,所有单词首字母大写
类名为“业务模块名 执行操作名 技术点名”结构,业务模块名主要有:home、lession、net、login,pay等,操作名主要有:Get、Set、Request、Login等,技术点名主要有:Activity、Fragment、View、Adapter等。
~、普通变量
采用 小驼峰 命名法,第一个单词首字母小写,其他单词首字母大写。
普通变量为“名字简写 类型 功能名”结构,名字简写有:js等,类型主要有:Int、Double、Boolean、String、Char等,功能名有:Login、Number、Content等。
~、常亮
所有字母全部大写,中间下划线隔开
常量为“功能名_标识”结构,功能名主要有:LOGIN、REQUEST、PERSONINFO等,标识有SUCCESS、ERROR、URL等。
~、方法名
名字能体现出功能即可。不再累赘重述。
3、数据/接口定义
建议
- 服务器返回数据采用json格式
- json数据中无数据,必须返回空数组或空字符串,不可返回null
- Android端使用gson或fastjson或jackson等三方解析工具解析
- 不建议使用官方JSONObject解析,容易出错
- 实体类属性名与json中字段名完全一致
- json中字段名全部使用英文,不可英文、拼音夹杂
- 用户表识建议使用Cookie
- 建议使用POST解析,它对参数数量没有要求,也比较安全
- 为了传输安全,使用https请求 等
- 完善接口文档,建议每一版对应一个完整接口文档
4、屏幕适配
安卓设备分辨率、屏幕尺寸五花八门,碎片化严重,重点对市面上主流的720*1280和1080*1920手机进行适配,同时对于其他类型手机也要适配。
关于屏幕适配,之前写过一个Android屏幕完美适配方案,点击前往,这里不再重复表述。
5、程序架构MVP
上图介绍:
Contract:契约类,一个功能模块中View接口、Model接口和请求数据回调统一在对应模块的Contract中定义,便于管理。
ViewInterface: view层接口,定义了view中的UI操作
ModelInterface: model层接口,定义了model负责的数据操作方法,如请求接口,操作数据库等
CallbackInterface: model层操作数据完成后的回调
BasePersenter: Persenter父类,主要是对相关view的获取,销毁等操作
View: view层实现类,主要就是Activity或Fragment,负责UI展示和事件响应
Model: model层实现类,就是依据业务,请求对应接口或数据库,并将结果返给回调CallBack
Persenter: persenter层类,负责业务逻辑处理,view将响应传给persenter,persenter负责调用model,并将结果返回给view供其展示
MVP:
MVP模式相当于在MVC模式中又加了一个Presenter用于处理模型和逻辑,将View和Model完全独立开,在android开发中的体现就是activity仅用于显示界面和交互,activity不参与模型结构和逻辑。
使用MVP模式会使得代码多出一些接口但是使得代码逻辑更加清晰,尤其是在处理复杂界面和逻辑时,我们可以对同一个activity将每一个业务都抽离成一个Presenter,这样代码既清晰逻辑明确又方便我们扩展。当然如果我们的业务逻辑本身就比较简单的话使用MVP模式就显得,没那么必要。所以我们不需要为了用它而用它,具体的还是要要业务需要
现在比较流行MVVM架构,后续我会将MVVM总结,大家期待一下。。
6、package划分
如上:主体按功能模块划分,同一级的还有一些技术点,如adapter、util、pay等;在功能模块下,按照mvp模式,又分为contract、model、presenter、fragment和activity;而在其他技术点包下面同样也按功能模块划分。
总之,我们划分包时:以功能模块为主,以技术点为辅。
希望能对大家有用
7、Base、Util、UI类封装
A、Base类
- BaseApplication
- BaseActivity
- BaseFragment
BasePresenter 等
a、BaseApplication:
主要进行一些例如:三方配置、热更新加载、文件配置、数据库配置等准备工作;同时也许定义全局性变量:如Application的Context、网络状态、主线程Looper、主线程Handler等。
b、BaseActivity:
封装为抽象类,将各任务抽取成方法,有子类实现:比如findViewById(initView)、initData、setListener等;
对友盟统计的封装:因为友盟统计或别的统计需要在所有Activity的各生命周期方法中调用api,所以应该将其封装到BaseActivity中。
项目为MVP结构,所以设置了View和Presenter的泛型,如:
其中定义了屏幕宽高度等设备信息,也定义了BasePresenter对象、并抽取抽象方法,由子类返回其对应presenter。
c、BaseFragment:
BaseFragment的封装如BaseActivity一样,添加View和Presenter的泛型和presenter对象,创建返回Presenter的抽象方法供子类事项;
创建createView(创建跟视图view)、initChildView(子view findViewById)、initData(加载数据)抽象方法
d、BasePresenter:
BasePresenter封装如上:内置view的软引用,在Activity或Fragment的onResume中调用presenter的attachView方法,将view实例传给presenter;在Activity或Fragment的onDestroy或onStop方法中调用detachView方法解除与view的绑定;而getView方法则是presenter在model返回数据后调用来操作对应view的展示UI方法。
B、Utils类
只列举一些常用的工具类:
- SharedPreferenceUtils
- ToastUtils
- StorageUtils
- FileUtils
- NetUtils
- deviceUtils
- DateUtils
- LogUtils
- AppUtils 等
C、UI类
只列举一些常用的View类
- 下拉刷新、上拉加载
- 圆形ImageView
- 自定义Dialog
- Banner
- 自定义ScrollView
- 自定义RecyclerView
- 项目相关的自定义View 等
8、数据库
关于数据库操作,之前一直是自己写:也就是SQLiteOpenHelper结合相关SQL操作工具类来实现数据库操作。
但随着业务逻辑的增加和复杂,需要进行大量的数据库操作时,编写大量的代码,既费时间、还会避免不了地出bug;
所以我们只介绍几款流行的数据库框架:
- GreenDao
- OrmLite
- LitePal
- Realm
GreenDao:
特点:1.存取速度快; 2.支持数据库加密; 3.轻量级; 4.激活实体; 5.支持缓存; 6.代码自动生成
地址:https://github.com/greenrobot/greenDAO
OrmLite:
优点: 1.轻量级;2.使用简单,易上手;3.封装完善;4.文档全面。缺点:1.基于反射,效率较低(本人还没有觉得效率低);2.缺少中文翻译文档
jar包地址:http://ormlite.com/releases/
LitePal:
LitePal 框架是郭大神开源的数据库框架,他的博客也比较详细的介绍了其用法。
地址:https://github.com/LitePalFramework/LitePal
Realm:
1.易用:Ream 不是在SQLite基础上的ORM,它有自己的数据查询引擎。并且十分容易使用。
2.快速:由于它是完全重新开始开发的数据库实现,所以它比任何的ORM速度都快很多,甚至比SLite速度都要快。
3.跨平台:Realm 支持 iOS & OS X (Objective‑C & Swift) & Android。我们可以在这些平台上共享Realm数据库文件,并且上层逻辑可以不用任何改动的情况下实现移植。
4.高级:Ream支持加密,格式化查询,易于移植,支持JSON,流式api,数据变更通知等高级特性
5.可视化
git地址:https://github.com/realm/realm-java
官网:https://realm.io/docs/java/latest/#getting-started
自己项目中使用了GreenDao,它代码自动生成、存取速度快、支持加密、一个轻量级别的库,用着方便,推荐大家使用GreenDao。
9、图片处理
之前有自己封装过图片处理框架,核心是使用HttpUrlConnection实现加载,仿LruCache(近期最少使用排序)算法实现图片缓存。
但我们用的最多的还是ImageLoader、Glide、Picasso和Fresco四大主流框架,接下来主要比较一下四个框架的各自特点:
ImageLoader:
优点:
- 多线程下载,线程管理。
- 多级缓存架构设计和策略,内存缓存,磁盘缓存,缓存有效性处理。
- 图片压缩,特效处理,动画处理。
- 复杂网络情况下下载图片策略,例如弱网络等。
- 内存管理,lru 算法、对象引用、GC回收等优化。
缺点:
- 时间久,官方不再维护,出现bug需要自己修复。
Glide:
优点:
- 更易用,因为Glide的with方法不光接受Context,还接受Activity 和 Fragment,Context会自动的从他们获取。同时将Activity/Fragment作为with()参数的好处是:图片加载会和Activity/Fragment的生命周期保持一致,比如Paused状态在暂停加载,在Resumed的时候又自动重新加载。所以我建议传参的时候传递Activity 和 Fragment给Glide,而不是Context。
- Glide可以加载GIF动态图。
- Glide缓存的是跟ImageView尺寸相同的。Glide的这种方式优点是加载显示非常快。
- 默认使用HttpUrlConnection下载图片,可以配置为OkHttp或者Volley下载,也可以自定义下载方式。
- 默认使用手机内置存储进行磁盘缓存,可以配置为外部存储,可以配置缓存大小,图片池大小。
- 默认使用两个线程池来分别执行读取缓存和下载任务,都可以自定义。
缺点:
- Glide加载的图片质量要差于Picasso,这是因为Glide默认的Bitmap格式是RGB_565,比ARGB_8888格式的内存开销要小一半。
Picasso:
特点:
- 在adapter中需要取消已经不在视野范围的ImageView图片资源的加载,否则会导致图片错位,Picasso已经解决了这个问题。
- 使用复杂的图片压缩转换来尽可能的减少内存消耗
- 自带内存和硬盘二级缓存功能
Fresco:
优点:
最大的优势便在于5.0以下(最低2.3) bitmap的加载,在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem),而且图片不显示时,占用的内存会自动被释放,这会使APP更加流畅,减少因图片内存占用而引发的OOM。5.0以后的系统默认存储在Ashmem区了
图片的渐进式呈现,图片先呈现大致的轮廓,然后随着图片下载的继续,逐渐成仙清晰的图片,这对于慢网络对说,用户体验更好。
支持加载Git动态图和Webp格式的图片。
缺点:
- 框架体积比较大,3M左右,会增加APK的大小。
总结:在项目开发中,要适当的选择图片框架,ImageLoader太老已过时,且官方不再维护,所以不再考虑使用ImageLoader;Picasso能做的,Glide都能做到,就是Glide的图片质量会稍差一些;而Fresco又体积偏大,但渐进式呈现,用户体验好。综上的话,一般项目建议使用Glide即可。
10、网络框架
上一个项目中,网络框架自己封装:核心使用HttpUrlConnection实现,先封装请求参数相关类RequestVo,其中包含请求方式、url、参数、解析类、是否缓存、缓存时长等参数;缓存是将json字符串加密后与拼接过的url成对存储到File,并且设置有效时间,超过有效时间删除缓存并去网络请求,成功后重新保存。
但现在市面上最流行的是Retrofit+RxJava+Gson,接下来我们大概介绍一下:
a、添加依赖
compile 'io.reactivex:rxjava:x.y.z'
compile 'io.reactivex:rxandroid:1.0.1'
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
要注意:以上添加了Retrofit、RxJava和Gson依赖,版本号必须一致
b、登录Service
interface BaseService {
@GET("user/login" )
Observable<User> login(
@Query("username") String username,
@Query("password") String password
);
}
login方法的返回值是Observable类型,就是RxJava中的被观察者。
c、网络请求
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create() .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//新的配置
.baseUrl(BASE_URL)
.build();
BaseService service = retrofit.create(BaseService.class);
service.login(phone, password) //获取Observable对象
.subscribeOn(Schedulers.newThread())//请求在新的线程中执行
.observeOn(Schedulers.io()) //请求完成后在io线程中执行
.doOnNext(new Action1<UserInfo>() {
@Override
public void call(User user) {
saveUserInfo(user);//保存用户信息到本地
}
})
.observeOn(AndroidSchedulers.mainThread())//最后在主线程中执行
.subscribe(new Subscriber<User>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
//请求失败
}
@Override
public void onNext(User user) {
//请求成功
}
});
RxJava + Retrofit 形式的时候,Retrofit 把请求封装进 Observable ,在请求结束后调用 onNext() 或在请求失败后调用 onError()。
可以看到,调用了service的login方法后得到Observable对象,在新的线程中执行网络请求,请求成功后切换到io线程执行保存用户信息的动作,最后再切换到主线程执行请求失败onError()、请求成功onNext()。整体的逻辑十分清晰都在一条链中,就算还有别的要求还可以往里面添加,丝毫不影响代码的简洁。
注意:Retrofit在创建的时候添了一下代码
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
想了解更多关于RxJava,浏览http://gank.io/post/560e15be2dca930e00da1083#toc_1
11、其他三方
在自己的开发过程中,还用到了如EventBus、Zxing、Zbar、Volley、Gson、LeakCanary等三方框架;
也用到了如友盟统计、微信、支付宝支付、三方登录、极光推送、tinker热更新等三方sdk;
就不再一一列举,附上一张图,大家有时间多去学习、多去了解。
12、混淆、加固、上线
混淆
大家可以参考我的另一篇文章http://blog.csdn.net/jiashuai94/article/details/77991077
混淆是上线前挺重要的一个环节。Android使用的ProGuard,可以起到压缩,混淆,预检,优化的作用。
坚持以下几项原则:
- 使用三方依赖,在混淆文件中添加官方提供的混淆代码,官方没有就google;
- 实体类不混淆,因为实体类涉及到与服务端的交互,各种gson的交互如此等等,是要保留的;
- 与js互调的类不混淆;
与反射有关的类不混淆 等。
具体语法:
-optimizationpasses 5 // 代码混淆的压缩比例,值在0-7之间
-dontusemixedcaseclassnames // 混淆后类名都为小写
-dontskipnonpubliclibraryclasses // 指定不去忽略非公共的库的类
-dontskipnonpubliclibraryclassmembers // 指定不去忽略非公共的库的类的成员
-dontpreverify // 不做预校验的操作
-verbose
-printmapping proguardMapping.txt // 生成原类名和混淆后的类名的映射文件
-optimizations !code/simplification/cast,!field/*,!class/merging/* // 指定混淆时采用的算法
-keepattributes *Annotation*,InnerClasses // 不混淆Annotation
-keepattributes Signature // 不混淆泛型
-keepattributes SourceFile,LineNumberTable // 抛出异常时保留代码行号
-keep class XXXX // 保留类名不变,也就是类名不混淆,而类中的成员名不保证。当然也可以是继承XXX类的所有类名不混淆,具体代码不贴了,重在理解。
-keepclasseswithmembers class XXXX // 保留类名和成员名。当然也可以是类中特定方法,代码不贴了,理由同上。