Android项目从立项到上线

2017/9/3 posted in  Android

最近在组建项目组从0开始开发项目,从立项到上线,有一些心得,包括项目规范、结构、优化、三方等,与大家分享,一起修仙!

接下来我会以自己写的两个项目为例,结合起来分析。代码已经上传github:

  • Material Design风格项目 点击前往 喜欢就给个star_^
  • UI、Base、网络等封装库项目 点击前往 喜欢也给个star喽

先上图来看看吧:

20171104150981004338352.png
20171104150981004338352.png

项目采用MVP+Retrofit+RxJava+Gson+Glide+Material Design设计

同时也使用了EventBus事件总线、GreenDao数据库、SurfaceView+MediaPlayer视频播放器等主要技术。

ok!项目就简单介绍到这里,接下来开始我们的修仙之路。

20171104150981008811224.png
20171104150981008811224.png

项目整体分析

UI风格

logo

主题风格:

  • Material Design
  • 仿ios
  • 主界面–侧拉
  • 主界面–仿微信 选项卡等

技术方案

整体分析项目,确定整体技术方案,之后的开发中按照技术方案执行,同时编写相关文档。

  • 项目架构
  • 网络框架
  • 图片处理
  • 数据处理/接口格式
  • 三方使用:统计、推送、更新等

可行性分析

邀请项目经理、技术负责人、产品经理、后端开发、测试等共同分析技术方案的可行性,并相应调整技术方案。

工期

根据项目需求和技术方案,给出开发时长;

根据工期要求,可适当调整技术方案。

测试用例

测试人员开始整理、编写测试用例和项目标准文档;

个人觉得开发人员应该参与到测试工作中,这样方便自己对业务逻辑和功能分析全面,也帮助测试人员分析项目功能和技术,便于写出更加全面的测试用例。

开发规范

其实Android、Ios、Server等各端开发人员,都应该有完整的、严格的开发规范,这里我只说一下Android端的开发规范:

以自己项目为例,涉及到以下几项:

  • 文档规范
  • 资源命名规范
  • 代码命名规范
  • 代码注释规范
  • 代码风格规范
  • 服务器数据规范 等

文档

重要有以下几个文档:

  • 需求文档
  • 产品原型图
  • 完整UI图
  • 接口文档
  • 核心技术文档
  • 重点逻辑文档
  • 程序框架图
  • ER图、数据字典、类图等
  • 测试文档

资源文件命名规范

名字全部小写,最好不用数字,全部英文,单词中间下划线隔开

drawable、anim等文件夹下

名称结构为“技术点模块点空间类型_功能名”结构,技术点主要有:selector、translate、alpha、scale等,模块名主要有:login、pay、mine、setting等,控件类型主要有:button、textview、imageview、dialog等,功能名主要有:findpwd、request、back、next等。

20171104150981042494432.png
20171104150981042494432.png

drawable-xhdpi等图片资源

名称为“技术点模块点功能名”结构,技术点主要有:activity、fragment、item、include等,模块点主要有:login、setting、mine、pay等,功能名主要有:head、title、back、sure等。

20171104150981047164097.png
20171104150981047164097.png

layout

布局名称为“技术点模块功能名”结构,技术点主要有:activity、fragment、item、include等,模块主要有:home、lesson、mine、loginregist等,功能名主要有:login、title、setting、pay、bar等。

20171104150981050013389.png
20171104150981050013389.png

values

布局名称为“技术点模块功能名”结构,技术点主要有:activity、fragment、item、include等,模块主要有:home、lesson、mine、loginregist等,功能名主要有:login、title、setting、pay、bar等。

20171104150981053458526.png
20171104150981053458526.png

代码命名规范

包名

包名为“根包名.技术点名.模块名”结构,全部小写

20171104150981056272553.png
20171104150981056272553.png

类名

采用 大驼峰 命名法,单词直接拼接,所有单词首字母大写

类名为“业务模块名 执行操作名 技术点名”结构,业务模块名主要有:home、lession、net、login,pay等,操作名主要有:Get、Set、Request、Login等,技术点名主要有:Activity、Fragment、View、Adapter等。

20171104150981070542458.png
20171104150981070542458.png

普通变量

采用 小驼峰 命名法,第一个单词首字母小写,其他单词首字母大写。

普通变量为“名字简写 类型 功能名”结构,名字简写有:js等,类型主要有:Int、Double、Boolean、String、Char等,功能名有:Login、Number、Content等。

2017110515098119341868.png
2017110515098119341868.png

常亮

所有字母全部大写,中间下划线隔开

常量为“功能名_标识”结构,功能名主要有:LOGIN、REQUEST、PERSONINFO等,标识有SUCCESS、ERROR、URL等。

20171105150981196443079.png
20171105150981196443079.png

方法名

名字能体现出功能即可。不再累赘重述。

数据/接口定义

建议

  • 服务器返回数据采用json格式
  • json数据中无数据,必须返回空数组或空字符串,不可返回null
  • Android端使用gson或fastjson或jackson等三方解析工具解析
  • 不建议使用官方JSONObject解析,容易出错
  • 实体类属性名与json中字段名完全一致
  • json中字段名全部使用英文,不可英文、拼音夹杂
  • 用户表识建议使用Cookie
  • 建议使用POST解析,它对参数数量没有要求,也比较安全
  • 为了传输安全,使用https请求 等
  • 完善接口文档,建议每一版对应一个完整接口文档

屏幕适配

安卓设备分辨率、屏幕尺寸五花八门,碎片化严重,重点对市面上主流的720 * 1280和1080 * 1920手机进行适配,同时对于其他类型手机也要适配。

关于屏幕适配,之前写过一个Android屏幕完美适配方案,点击前往,这里不再重复表述。

程序架构MVP

20171105150981206687113.png
20171105150981206687113.png

上图介绍:

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总结,大家期待一下。。

package划分

20171105150981211916250.png
20171105150981211916250.png

如上:主体按功能模块划分,同一级的还有一些技术点,如adapter、util、pay等;在功能模块下,按照mvp模式,又分为contract、model、presenter、fragment和activity;而在其他技术点包下面同样也按功能模块划分。

总之,我们划分包时:以功能模块为主,以技术点为辅。

Base、Util、UI类封装

Base类

  • BaseApplication
  • BaseActivity
  • BaseFragment
  • BasePresenter 等

BaseApplication:

主要进行一些例如:三方配置热更新加载文件配置数据库配置等准备工作;同时也许定义全局性变量:如Application的Context网络状态主线程Looper主线程Handler等。

BaseActivity:

封装为抽象类,将各任务抽取成方法,有子类实现:比如findViewById(initView)initDatasetListener等;

对友盟统计的封装:因为友盟统计或别的统计需要在所有Activity的各生命周期方法中调用api,所以应该将其封装到BaseActivity中。

项目为MVP结构,所以设置了View和Presenter的泛型,如:

其中定义了屏幕宽高度等设备信息,也定义了BasePresenter对象、并抽取抽象方法,由子类返回其对应presenter。

20171105150981225588056.png
20171105150981225588056.png

BaseFragment:

BaseFragment的封装如BaseActivity一样,添加View和Presenter的泛型和presenter对象,创建返回Presenter的抽象方法供子类事项;

创建createView(创建跟视图view)、initChildView(子view findViewById)、initData(加载数据)抽象方法

BasePresenter:

20171105150981228211507.png
20171105150981228211507.png

BasePresenter封装如上:内置view的软引用,在Activity或Fragment的onResume中调用presenter的attachView方法,将view实例传给presenter;在Activity或Fragment的onDestroy或onStop方法中调用detachView方法解除与view的绑定;而getView方法则是presenter在model返回数据后调用来操作对应view的展示UI方法。

Utils类

只列举一些常用的工具类:

  • SharedPreferenceUtils
  • ToastUtils
  • StorageUtils
  • FileUtils
  • NetUtils
  • deviceUtils
  • DateUtils
  • LogUtils
  • AppUtils 等

UI类

只列举一些常用的View类

  • 下拉刷新、上拉加载
  • 圆形ImageView
  • 自定义Dialog
  • Banner
  • 自定义ScrollView
  • 自定义RecyclerView
  • 项目相关的自定义View 等

数据库

关于数据库操作,之前一直是自己写:也就是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地址

官网

自己项目中使用了GreenDao,它代码自动生成、存取速度快、支持加密、一个轻量级别的库,用着方便,推荐大家使用GreenDao。

图片处理

之前有自己封装过图片处理框架,核心是使用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即可。

网络框架

上一个项目中,网络框架自己封装:核心使用HttpUrlConnection实现,先封装请求参数相关类RequestVo,其中包含请求方式、url、参数、解析类、是否缓存、缓存时长等参数;缓存是将json字符串加密后与拼接过的url成对存储到File,并且设置有效时间,超过有效时间删除缓存并去网络请求,成功后重新保存。

但现在市面上最流行的是Retrofit+RxJava+Gson,接下来我们大概介绍一下:

添加依赖

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依赖,版本号必须一致

登录Service

interface BaseService {
  @GET("user/login" )
  Observable<User> login(
          @Query("username") String username,
          @Query("password") String password
  );
}

login方法的返回值是Observable类型,就是RxJava中的被观察者。

网络请求

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

其他三方

在自己的开发过程中,还用到了如EventBus、Zxing、Zbar、Volley、Gson、LeakCanary等三方框架;

也用到了如友盟统计、微信、支付宝支付、三方登录、极光推送、tinker热更新等三方sdk;

就不再一一列举,附上一张图,大家有时间多去学习、多去了解。

20171105150981301966614.png
20171105150981301966614.png

混淆、加固、上线

混淆

大家可以参考我的另一篇文章

混淆是上线前挺重要的一个环节。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 // 保留类名和成员名。当然也可以是类中特定方法,代码不贴了,理由同上。

加固

加固一般都使用三方加固:如果apk上百度应用市场,则使用百度加固,其他情况建议使用360加固,当然,加固前一定是混淆过的、并且签过名的apk。

360加固还提供了一些其他服务,可根据项目情况操作(是需要花钱的..)

上线

也就是我们所说的发版,当你的apk测试通过,混淆过、签名过、也加固了,可以发版了。

每个公司上线的市场要求不同,一般主要有应用宝、360、豌豆荚、百度市场、小米市场、华为市场、魅族市场等,当然还有官网了。

发版时需要:apk当然要有、logo(第一次上线)、应用简介(第一次上线)、版本号、更新功能、介绍图、官网地址(第一次上线)等。

ok,到这里,我们的一个阶段算是完成了,接下来还会有更多的业务和bug等着大家,哈哈!希望这篇文章能对大家有一点点帮助。