Android App开发入门与实战
人民邮电出版社
北京
图书在版编目(CIP)数据
Android App开发入门与实战 / 熊欣著. --北京:人民邮电出版社,2020.8
ISBN 978-7-115-54250-2
Ⅰ.①A… Ⅱ.①熊… Ⅲ.①移动终端-应用程序-程序设计 Ⅳ.①TN929.53
中国版本图书馆CIP数据核字(2020)第116848号
◆著 熊欣
责任编辑 赵轩
责任印制 王郁 马振武
◆ 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
三河市中晟雅豪印务有限公司印刷
◆ 开本:787×1092 1/16
印张:21 2020年8月第1版
字数:536千字 2020年8月河北第1次印刷
定价:69.00元
读者服务热线:(010)81055493 印装质量热线:(010)81055316
反盗版热线:(010)81055315
广告经营许可证:京东市监广登字20170147号
本书基于作者14年的开发实战经验,详细介绍了13套Android 开发规范、3套热门App框架、12个常用功能模块、14套App解决方案,以及高效开发工具。最后,本书还以实训方式,展现了 Github客户端开发的前前后后,作为全书知识的总结。本书不仅实例丰富,还提供完整源码,适合刚入门Android开发人员以及技术管理人员阅读,同时也可作为高等院校相关专业的教学、自学用书。
Android是一款基于Linux的开源操作系统,主要应用于移动端设备。2003年10月,安迪·鲁宾(Andy Rubin)等人创建了Android公司;2005年8月,Google收购了Android公司;2008年10月,第一款Android智能手机发布。
至今为止,Android手机已经走过了十多个年头。这些年我们见过各式各样的操作系统,如iOS、Symbian、Windows Mobile、BlackBerry等,但是Android操作系统却脱颖而出,凭借着其开源性和开放性成为使用人数最多、拥有的使用设备数最多的移动端操作系统,因此这些年也迎来了一阵Android App开发的狂潮。
Android不仅局限于应用和游戏的开发,还将应用到AI人工智能、智慧家居、虚拟现实技术、安全等热门领域。在未来的10年里,Android将以更加人性化、智能化,以及安全和便捷的体验展示在大众面前。Android本身也由于开源的特性,吸引了众多手机厂商的加入,随着国内外各大手机厂商的壮大,引发了Android App开发的热潮。
作为一名程序员,如果你对移动端开发和开源有兴趣,那么你应该对Android App的开发有一定的了解。本书的目的就是带领读者进入Android App开发的世界,让读者全面、系统地掌握App开发的技能,最终能胜任Android开发的职位,并且开发出自己的App。
本书特色
1.内容全面
本书包括Android开发过程中所涉及的开发规范、框架、模块、解决方案、App实战开发等部分,同时还包括团队管理方面的内容,可以说覆盖了Android开发所涉及的各个方面。每个部分均再次细化并进行详细说明,同时附带大量图片加以介绍。
2.实例多
本书在介绍了相关知识点后,都会配备相应的实例,书中会展示关键部分代码,并且配有详细注释,完整实例源码存放在随书源码中。
3.实战性强
本书不仅从技术角度详细介绍Android开发的知识点,更重要的是指引读者手动实现这些功能,如实现框架和模块。实现过程中会通过需求分析、技术分析、代码实现、总结等步骤一步一步地指导实现过程,让读者深入了解实现原理。
4.解决方案多
本书为Android实战开发过程中可能会遇到的问题提供了丰富的解决方案。这些解决方案均是根据实际开发中常见的问题所提出的,经过了长时间的检验,可靠性、实用性强。
5.结构安排合理
本书4篇的安排体现了App开发过程的系统性和完整性,并且依据循序渐进的原则,让读者从浅到深、由点及面,更好地理解整个Android开发体系。同时,每篇内容自身也可成独立体系,以满足不同阅读需求的读者。
本书体系结构
第1篇 规范篇(第1~5章)
本篇介绍了Android开发过程中所需制定的各种规范。规范不仅是一种约束,还是一种解决方案,更是提升开发效率的手段。
第2篇 开发篇(第6~10章)
本篇介绍了Android开发中常用的设计模式、框架和模块。这是Android开发的重点所在,除了介绍原理外,还会介绍如何动手一步步地实现自己的框架和模块。
第3篇 扩展篇(第11~14章)
本篇介绍了提升Android开发效率的相关知识点,包括App开发中的各种常用解决方案、App优化、App测试和常用工具。
第4篇 实战篇(第15~16章)
本篇通过实际开发一个App对之前介绍的知识内容进行总结,通过实战App开发,相信能更好地帮助读者了解Android App的整个开发过程。
本书读者对象
·Android初中级开发人员,有Android开发基础,想进一步提高开发水平,想对Android开发有更全面和深入的了解。
·Android团队开发管理人员,想进一步规范团队开发管理,以及提升团队开发质量和效率。
·Android开发爱好者,想短时间内快速开发出App。
本书代码开发环境
·Android Studio:3.5.1
·Android Gradle Plugin Version:3.5.1
·Gradle Version:5.4.1
·compileSdkVersion : 29
·buildToolsVersion : 29.0.2
·targetSdkVersion : 29
·minSdkVersion : 19
·OS:Windows
·开发语言:Java
本书源代码下载
为了方便读者学习,本书提供了配套的全部源码。
读者可以发送邮件到作者androidbook@126.com获取,邮件主题为本书书名。
读者也可以通过邮箱提出意见或建议。
因为Android包目录的命名会直接影响到整个App工程后期的开发效率和扩展性,所以在创建项目的初期,包目录的命名非常重要。
Android工程本身对包目录命名没有要求,可以将代码文件直接放置在默认目录下,但这样做会导致很多无关文件的堆积,不利于查找及后期维护,所以一般不建议采用这种方式。
Android包目录命名的常用方式有两种:PBL(Package By Layer)和PBF(Package By Feature)。
PBL是按层次划分的,实际上就是按照职能划分,如在根目录里面命名activity、fragment、view、service、db、net、util、bean、base等包名。
例如,activity的职能是管理所有的Activity类,只要这个类是继承自Activity的,都放到这个目录下。以此类推,fragment目录存放所有继承自Fragment的类,view放置自定义的View,net放置网络相关的类,bean放置Bean对象等。
早期的Android App开发常采用图1.1所示的包结构。
接下来我们来分析一下PBL这种命名结构的优缺点。
1)PBL优点
·项目结构简洁明了,上手快。
·适合开发人员不多、项目功能简单、后期变动不大的项目。
2)PBL缺点
(1)低内聚
同一个目录下会有各种功能模块。例如activity目录,其中放置了登录、设置等功能模块;这几个模块本身并没有很强的关联性,却被放置在了一起,导致聚合性降低。
(2)高耦合
这里讲的高耦合是指目录之间的关联性,例如activity目录内的类可能引用到了fragment或者view目录里面的类,导致目录之间的耦合性较高。
(3)影响开发效率
开发一个功能模块,往往需要在不同的包目录之间切换。例如登录模块,需要在activity目录下开发LoginActivity类,而LoginActivity类往往包含了fragment目录下的内容,这时又需要去fragment目录下找到对应的Fragment类来开发,目录之间频繁切换会影响开发效率。同样,修改、调试一个功能也需要进行这样的操作,如果后期项目功能和代码增多,会大大降低开发效率。
PBF是按照功能划分包目录的。以功能模块名称作为目录名,所有与这个功能模块相关的开发都在这个目录内。
以Google I/O 2019 Android App为例,如图1.2所示。
可以看到,除了与业务无关的模块,如utils这样的通用模块,每一个包目录都对应一个功能模块。
Google的项目采用PBF进行包目录命名,我们来分析一下PBF有哪些优点。
1)高内聚
所有功能都在一个包下完成,以map模块为例,如图1.3所示。
可以看到,map目录下包含了fragment、adapter、viewmodel等。这里需要说明的是,功能模块里面的util一般都是和这个功能模块强相关的,如果是功能模块包目录外的util目录名,一般放置的是与项目相关的util类和能作用于整个或者多个功能模块的util类。
其他包目录以此类推,例如ui包目录下面放置了Base Activity,adapter包目录下面放置了BaseAdapter等。
2)低耦合
包目录之间没有很强的关联性,此模块的功能只需要在对应的包目录下面即可进行开发,除了基础类外,一般不需要引入其他包的类。
3)开发效率高
增删改查都只需要在对应的包目录下面操作即可,便于团队开发管理,提升问题排查效率,也方便后续开发人员接手。
4)便于后期组件化转化
如果项目是按照PBF来进行包目录划分的,后期进行组件化改造的时候就会非常方便。可以直接把功能模块独立出来作为一个组件,同时划分好代码边界,对外保留好模块间的访问接口。例如上面的map功能模块,可以将其独立出来作为一个library工程。
以Java语言为例,来说明一下如何制定代码的命名规范。
命名规则约定事项如下。
·代码命名不以下画线和美元符号开头。
·【】表示可选。
采用大驼峰命名法。
(1)命名规则
【功能】 + 【类型】。
(2)举例
Activity类,命名以Activity为后缀,如:LoginActivity。
Fragment类,命名以Fragment为后缀,如:ShareFragment。
Service类,命名以Service为后缀,如:DownloadService。
Dialog类,命名以Dialog为后缀,如:ShareDialog。
Adapter类,命名以Adapter为后缀,如:ProductAdapter。
BroadcastReceiver类,命名以Receiver为后缀,如:PushReceiver。ContentProvider类,命名以Provider为后缀,如:FileProvider。
业务处理类,命名以Manager为后缀,如:UserManager。
解析类,命名以Parser为后缀,如:NewsParser。
工具类,命名以Util为后缀,如:EncryptUtil。
模型类,命名以Bean为后缀,如:GiftBean。
接口实现类,命名以Impl为后缀,如:DeviceImpl。
自定义共享基础类,命名以Base开头,如:BaseActivity。
测试类,命名以它要测试的类的名称开始,以Test结束,如:DeviceImplTest。
(3)抽象类和接口类
抽象类命名后缀为Abstract,如:abstract DeviceAbstract。
接口类命名后缀为Contract,如:interface DeviceContract。
采用小驼峰命名法。
(1)命名规则
动词或动名词。如:run()、addDevice()。
(2)举例
初始化方法,命名以init开头,如:initView。
按钮点击方法,命名以to开头,如:toLogin。
设置方法,命名以set开头,如:setData。
具有返回值的获取方法,命名以get开头,如:getData。
通过异步加载数据的方法,命名以load开头,如:loadData。
布尔型的判断方法,命名以is、has或check开头,如:isEmpty、checkNull。
对数据进行处理,命名以handle开头,如:handleUserInfo。
弹出提示框,命名以show开头,如:showAgreement。
更新数据,命名以update开头,如:updateUserInfo。
保存数据,命名以save开头,如:saveUserInfo。
重置数据,命名以reset开头,如:resetUserInfo。
删除数据,命名以delete开头,如:deleteUserInfo。
查询数据,命名以query开头,如:queryUserInfo。
移除数据,命名以remove开头,如:removeUserInfo。
采用小驼峰命名法,变量命名应该简短且有规则。所有变量都要显示地赋值,如int number =0。布尔变量应该包含Is,如IsFirstLogin。
按照不同的变量类型,变量的命名规则有所不同,如下。
(1)类变量(成员变量)
非公有的变量前面要加上小写m;静态变量前面要加上小写s;其他变量以小写字母开头,前面不再加任何前缀,例如Bean类中的属性变量,为了生成的get和set方法名美观可读,有一些IDE已经支持生成get和set方法名时自动去除前缀。
常量、静态变量全大写,采用下画线命名法。
1. public class Demo { 2. public static final int SOME_CONSTANT = 2020; 3. public int publicField = 1; 4. private static Demo sSingleton; 5. int mPackagePrivate; 6. private int mPrivate = 0; 7. protected int mProtected = 10; 8. }
(2)局部变量
变量为一个单词,以小写字母开头。如:GiftBean bean。
(3)参数
参数为一个或多个单词的组合,以小写字母开头。如:fun(int position)、fun(String userName)。
(4)临时变量
临时变量通常被取名为i、j、k、m和n,它们一般用于整型;c、d、e一般用于字符型。如:for (int i = 0; i < len ; i++),for (String c : stringList)。
(5)泛型变量
泛型变量一般用单个大写字母来表示,如果这个泛型是某个类的子类,那么这个大写字母一般取的是父类所代表的这个类含义的首字母。如:interface BasePresenter<V extends BaseView,M extends BaseModel> ,其中V代表View,M代表Model。
(6)控件变量
Android中把很多UI控件作为成员变量,为了和Java的成员变量区分开,UI控件类型的成员变量在遵循前面成员变量命名规范的前提下,后面统一再加上控件名称。如:private TextView mDescriptionTextView。
有些命名规则是在后面加上控件的缩写,缩写不如全名看起来美观,而且不易于理解。
全部小写,采用下画线命名法,使用名词或名词词组。所有Activity或Fragment等的布局名必须与其类名相对应。
(1)命名规则
【类型名】+【模块名】。
(2)举例
MainActivity.java对应activity_main.xml,规则是类名单词倒置,中间用“_”连接,并且单词全部改为小写,如下。
activity_main:Main模块的Activity。
fragment_login:Login模块的Fragment。
dialog_update:Update模块的Dialog。
关于include,由于include的布局一般不属于某个专门的模块,所以用include_代表类型。如果是在某个模块内拆分出来的布局,需要加上这个模块的名称,如下。
include_tips:提供tips布局。
include_im_function:IM模块的功能布局。
关于ListView、RecyclerView或GridView的item的布局命名,用item_代表类型,如下。
item_user_member:User模块下普通会员的item。
item_user_vip:User模块下VIP会员的item。
另外,item如果是表示header或footer的,可以加上_header或_footer后缀,如下。
item_user_header:User模块下下拉刷新的header。
item_user_footer:User模块下下拉刷新的footer。
总之,布局文件的命名需要能直接反映该布局文件的作用范围和功能。
全部小写,采用下画线命名法。
(1)命名规则
【控件缩写】+【模块名】+【功能名】。
(2)举例
1. <!-- 这是登录模块的密码输入框 --> 2. <TextView 3. android:id="@+id/et_login_password" 4. /> 5. 6. <!-- 这是登录按钮 --> 7. <Button 8. android:id="@+id/btn_login_submit" 9. />
有时候为了简洁,layout的id定义得不那么复杂,例如上面的et_login_password可能会写成password。这样有一个问题是,如果项目中存在多个相同的命名,那么查找起来会有些不方便,如图1.4所示的同名id。
在单击main_content这个id名后,系统会弹出对话框提示选择跳转到哪一个layout下面的id。这样会增加开发时间,尤其是在对代码的熟悉程度不够的情况下。而且如果是在SDK中这样去命名,很有可能会导致引用SDK的项目出现资源重名并产生冲突。
全部小写,采用下画线命名法。
(1)命名规则
【模块名】+【动画类型】+【动画方向】。
如果不限定模块名,表示这个动画是全局通用的。
(2)举例
scale_in.xml:缩小;slide_out.xml:扩大。
fade_in.xml:淡入;fade_out.xml:淡出。
push_down_in.xml:从下方推入;push_down_out.xml:从下方推出。
left_in.xml:从左边进入;left_out.xml:从左边退出。
welcome_zoom_in.xml:欢迎界面放大。
全部小写,采用下画线命名法。
(1)命名规则
【控件缩写】+【模块名】+【功能名】+【状态限定】。
状态限定的内容包括small、big、normal、focus、red、white等,且可以叠加。
(2)举例
btn_main_back.png:Main模块的返回按钮的图片。
btn_main_back_small.png:Main模块的返回按钮的小图片。
btn_main_back_small_pressed.png:Main模块的返回按钮被选中时的小图片。
btn_red.png:通用红色按钮图片。
bg_setting.png:Setting模块通用背景图片。
ic_user_head_small.png:User模块头像(小)。
selector_login_input.png:Login模块输入文本框的selector。
1)strings
(1)命名规则
【模块名】+【控件名】+【功能名】。
(2)举例
1. <string name="loading">加载中</string> 2. <string name="button_ok">确定</string> 3. <string name="dialog_title">对话框</string> 4. <string name="main_titlebar_more">更多</string> 5. <string name="setting_title">设置页面</string> 6. <string name="search_edittext_hint">输入关键字</string> 7. <string name="login_findpassword">找回密码</string>
2)colors
(1)命名规则
【模块名或theme名】+【功能名】+【颜色编码】。
(2)举例
1. <!-- Basic colors --> 2. <color name="white">#FFFFFF</color>3. <color name="black">#000000</color> 4. <color name="red">#FF0000</color> 5. <color name="blue">#0000FF</color> 6. <color name="green">#00FF00</color> 7. <!-- Splash page --> 8. <color name="splash_yellow">#EEEE00</color> 9. <color name="splash_pink">#FFB5C5</color> 10. <!-- Feedback page --> 11. <color name="feedback_submit_black">#000000</color>
3)dimens
(1)命名规则
【模块名】+【控件名】+【描述】。
(2)举例
1. <!-- Common dimensions --> 2. <dimen name="margin_normal">16dp</dimen>3. <dimen name="margin_small">8dp</dimen> 4. <dimen name="margin_large">32dp</dimen> 5. <!-- Navigation --> 6. <dimen name="nav_drawer_width">@dimen/match_parent</dimen>7. <dimen name="nav_account_image_size">32dp</dimen> 8. <dimen name="nav_header_logo_size">36dp</dimen>
4)styles
采用大驼峰命名法。
(1)命名规则
【模块名.】+【功能名】。
(2)举例
1. <style name="ContentStyle"> 2. <item name="android:layout_weight">1</item>3. <item name="android:layout_width">0dp</item> 4. </style> 5. <style name="Login.ContentStyle"> 6. <item name="android:layout_weight">1</item>7. <item name="android:layout_width">0dp</item> 8. <item name="android:textSize">14sp</item> 9. <item name="android:gravity">center</item> 10. </style>
常用控件缩写表请查阅随书源码。
Android开发的IDE有很多种,Eclipse就是曾经的“霸主”,但是在2015年6月,Google官方宣布Android Studio作为Android开发的IDE,之后Eclipse就逐渐淡出了。目前Android开发的IDE主要是Android Studio,但是也有其他的一些IDE可以用来开发Android,如IntelliJ IDEA。
还有就是如果涉及混合开发,那么可选的IDE就更多了,例如可以使用VSCode进行Flutter的开发。
Android Studio实际上也是源自IntelliJ IDEA的,只是在IntelliJ IDEA社区版上剔除了其他功能,只留下专用于Android开发的插件。
这里以Android Studio为例,先介绍下基本的配置规范。
·保持IDE版本最新,保持开发人员的IDE版本一致。
·IDE统一编码格式为UTF-8,如图1.5所示。
·编码完成后记得按Ctrl + Alt + L快捷键格式化代码。
·安装一些常用的Android Studio插件,如GsonFormat等。
在开发App的过程中,每个团队都会制定自己的代码规范,也可以叫作代码格式。但是在实际开发过程中我们发现,要么由于开发周期短,开发人员常常没有按照规范进行开发,而是按照自己的编程习惯来开发,要么是新入职的员工,或者从其他项目组借调过来的开发人员,不熟悉当前项目的开发规范,引入了自己常用的代码开发格式。因此有必要采用一些强制手段来执行我们设定的代码规范。
这里引入CheckStyle来执行代码规范。
CheckStyle有两种使用方式,一种是安装插件,还有一种是通过gradle配置。
1)安装插件
在Plugins里面搜索CheckStyle-IDEA,如图1.6所示。
安装完成后选择Google Checks,作为项目默认的配置文件,如图1.7所示。
以整个项目为范围,运行CheckStyle,结果如图1.8所示。
2)CheckStyle文件
关于CheckStyle的详细介绍,可以访问CheckStyle官网。
Android Studio默认提供了Google和Sun的CheckStyle文件,我们可以在设置中找到它们。可以选择其中的一个作为自己项目的CheckStyle。
3)实例
打开一个Java文件,右键单击以运行Check Current File,结果如图1.9所示。
根据提示进行部分修改后,结果如图1.10所示。
4)自定义CheckStyle
每个公司,甚至每个团队都应该有自己的CheckStyle文件。可参考Google或其他公司的CheckStyle文件,自行修改成为适合自己团队使用的代码规范文件。
随书源码中有一个自定义的CheckStyle文件供读者查阅。
有两种方式可以通过配置gradle脚本使用CheckStyle,如下。
·可以直接运行命令执行CheckStyle;
·可以在运行Build任务的时候,同时触发CheckStyle任务,用来检测代码规范性。如果不符合代码规范,停止运行,并且提供HTML文件查看错误详情,直到代码完全符合我们定义的代码规范才能继续运行。
建议使用第二种方式,通过强制性的脚本检测以保证每个团队成员提交的代码都是符合代码规范的。接下来介绍这种方式是如何实现的。
首先,新建一个checkstyle.gradle文件,内容如下。
1. allprojects { 2. project -> 3. // 代码规范检查 4. apply plugin: 'checkstyle' 5. checkstyle { 6. configFile rootProject.file('config/quality/checkstyle/checkstyle.xml') 7. toolVersion '8.18' 8. ignoreFailures false 9. showViolations true 10. } 11. task('checkstyle', type: Checkstyle){ 12. source 'src/main/j ava' 13. include '**/*.j ava' 14. exclude '**/gen/**' 15. classpath = files() 16. } 17. tasks.whenTaskAdded { task -> 18. boolean runCheckStyleOnLocalDev = "${enable_checkstyle}" 19. .toBoolean() 20. boolean runCheckStyleTask = task.name == 'prepareReleaseDependencies' || (runCheckStyleOnLocalDev && task.name == 'preBuild') 21. if (runCheckStyleTask){ //prepareReleaseDependencies, preBuild 22. println("checkstyle run task.name :" + task.name) 23. task.dependsOn 'checkstyle' 24. } 25. } 26. }
其中config/quality/checkstyle/checkstyle.xml是项目中CheckStyle文件的路径。
然后在项目的build.gradle文件中加上如下代码。
1. apply from: './checkstyle.gradle'
第一种方式是直接运行CheckStyle的Gradle脚本,在terminal里面输入“gradlew checkstyle”,即可运行CheckStyle任务。
第二种方式是在运行Build任务的时候自动检测CheckStyle。按Ctrl+B快捷键运行Build任务,同时触发 checkstyle.gradle文件中的任务。
如果有错误,日志中会提供一个查看报告结果的路径,可以打开这个HTML文件格式的报表,查看详细的检查结果,然后对比将代码修改成符合我们定义的代码规范的代码。
需要注意的是,如果错误太多,输出的消息中不一定会包含检查结果路径。如果没输出,可以到各个工程module的build\reports\checkstyle路径下进行查看。
图1.11所示为CheckStyle检测结果报表的内容。
单击链接可以看到详细的描述,如图1.12所示。
CheckStyle官网提供了检查标准,截至目前一共有155条规范。在编写CheckStyle的时候可以按照这些规范来编写,单击链接可查看详细的属性介绍。
图1.13所示为FileLength规范。
在编码完成后,我们要养成将文件格式化的习惯,保证文件符合我们CheckStyle定义的代码规范。
选择CheckStyle文件,如图1.14所示。
然后在代码文件中按Ctrl + Alt + L快捷键即可将代码按照我们定义的代码规范格式化。
这里需要注意的是,并不是所有定义的代码规范都能够通过按Ctrl + Alt + L快捷键来得以实施,例如定义了每一行的字符长度不能超过140个字符,若某行的字符长度超过140个字符,那么格式化也不能将该行自动换行。
为了方便自己和他人阅读代码,请做好注释。代码不仅是用来运行的,还是用来给人阅读的。
如果没有注释,那么会给代码阅读以及问题排查带来不便,例如其他开发人员接手某块代码的开发工作时,如果有详细的注释,那么接手的开发人员能很快地了解这块代码的业务逻辑以及解决思路。同样,项目的一部分功能也会经常交给新员工来解决,新员工往往需要花费大量时间阅读代码,如果有详细的注释,新员工阅读和理解代码的效率就会提升;否则新员工有可能会经常请教老员工,在一定程度上占用老员工的开发时间,影响彼此的工作效率。
所以说代码注释也是需要特别关注的一项内容。
在文件头注释中添加版权声明。
1)示例
1. /** 2. * Copyright (c) 2020 Your Company. All rights reserved. 3. */
2)设置
在Android Studio中依次单击File → Settings → Editor → File and Code Templates →Includes → File Header,如图1.15所示,设置File Header的内容。
还可以创建注释模板文件,如图1.16所示的Company Declare文件。
在Class中添加这个文件,如图1.17所示。
在新建Java文件的时候,这个文件头注释也会自动生成,如图1.18所示。
1)示例
1. /** 2. * 对类的描述3. * @author: 张三 4. * @e-mail: xxx@xx 5. * @time: ${date}${time} 6. * @version: 1.0 7. */
2)设置
在File Header中添加即可。
每一个成员方法(包括自定义成员方法、覆盖方法、属性方法)的方法头都必须做方法头注释。
在方法前一行输入/**并按Enter键,或者在Android Studio中设置Fix doc comment(Settings → Keymap → Fix doc comment)快捷键,Android Studio会自动生成注释模板,我们只需要补全参数即可。
1. /** 2. * 对方法的说明 3. * @param id 查询用ID 4. * @return User实体类 5. */
对于注释,还可以设定通用注释模板,然后通过快捷输入的方式让IDE自动生成设定的注释内容。这样可以在代码的任何地方快捷生成注释,提升编码效率。如图1.19所示。
在Android Studio中依次单击File → Settings → Editor → Live Templates,新建一个名为MyComment的Group,然后新建一个名为cmt的缩写,再输入通用注释模板内容。这样在代码里面输入cmt然后按Enter键,就会自动生成通用注释模板内容。
1)对代码块注释
1. /***************** 说明 ******************/
2)对单行注释
1. // 说明
3)对多行注释
1. /* 2. * 说明1 3. * 说明2 4. */
下面几种情况下的常量和变量,都需要添加注释,优先采用在代码右侧添加//符号的方法来注释,若注释太长则在代码上方添加注释。
·接口中定义的所有常量。
·公有类的公有常量。
·枚举类定义的所有枚举常量。
·实体类的所有属性变量。
1. public static final int TYPE_DOG = 1; // 狗2. public static final int TYPE_CAT = 2; // 猫 3. public static final int TYPE_PIG = 3; // 猪4. 5. private int id; // id 6. private String name; // 名称 7. private String sex; // 性别
在1.1.3小节介绍的资源文件中,如果需要添加注释,使用如下格式。
1. <!-- Toast信息 -->
TODO代表需要实现,但目前还未实现的功能说明。FIXME代表功能代码有问题,需要修复的说明。
在Android Studio左下角的TODO里面可以看到当前项目所有的TODO和FIXME注释,如图1.20所示。
在介绍了代码注释规范后,在开发过程中我们会在文件中按规范添加代码注释。
如果打算更进一步地将整个项目的代码注释规范地整理出来,形成一份文档交给用户,应该怎么办呢?这里我们引入JavaDoc。
JavaDoc是Sun公司提供的一种技术,即从程序源代码中抽取类、方法、成员等注释,形成一个和源代码配套的API帮助文档。
JavaDoc标签有很多,符合JavaDoc标签规范的注释能够在生成的文档中显示出来。此处以@param为例,介绍JavaDoc标签的用法。
@param的用法是在其后接参数名和描述。
1. /** 2. * startActivityForResult 3. * 4. * @param clazz 类名.class,获取类型类 5. * @param requestCode 请求码 6. */ 7. protected void readyGoForResult(Class<?> clazz, int requestCode) { 8. Intent intent = getGoIntent(clazz); 9. startActivityForResult(intent, requestCode); 10. }
在生成的JavaDoc中的效果如图1.21所示。
图1.22展示了如何配置JavaDoc。在Android Studio中依次单击File → Settings → Editor →Code Style → Java → JavaDoc。
JavaDoc的输出也很简单,在Android Studio中通过单击Tools → Generate JavaDoc……即可导出JavaDoc文档。
导出JavaDoc文档的时候有些常见的问题需要注意,例如提示:编码GBK的不可映射字符。
这是在utf-8项目中经常会遇到的问题,其实也就是字符编码问题。可以在VM的setting中进行图1.23所示的设置。
除了上述导出JavaDoc的方法,还可以通过Gradle脚本配置来自动生成javadoc文件。在module的build.gradle中进行配置:
1. task j avadoc(type: Javadoc) { 2. source = android.sourceSets.main.j ava.srcDirs 3. classpath += project.files(android.getBootClasspath().j oin(File.pathSeparator)) 4. } 5. 6. task j avadocJar(type: Jar, dependsOn: j avadoc) { 7. classifier = 'j avadoc' 8. from j avadoc.destinationDir 9. } 10. 11. artifacts { 12. archives j avadocJar 13. }
接下来在Android Studio右侧的gradle这个tab里,找到这个module中的javadocJar脚本,双击运行,在该module的build目录下会自动创建javadoc文件,如图1.24所示。
.gitignore的作用是忽略指定文件的上传,否则在提交代码的时候,项目中的所有文件都会提示可以上传,但是有一些文件(如本地编译文件)实际上不需要上传到服务器中;还有一些文件里面有用户的账号密码,涉及安全信息,也不应该上传。因此我们通过配置.gitignore文件,来决定哪些可以上传,哪些不用上传。
Github上有一个gitignore项目,专门提供了一系列的针对不同语言的gitignore模板。
编写.gitignore文件需要注意以下几点。
·#表示此行不生效,一般用来写注释。
·*表示通配符,如*.dex表示所有的dex文件。
·目录路径以 / 结尾,如build/表示所有build目录下的文件。
·特定文件直接写明文件名,如freeline.py。
随书源码中我们也提供了一个Android通用的.gitignore模板以供参考,实际开发过程中可以根据项目的情况进行修改。
Java源码编译后成为字节码,保存在class文件中,而出于跨平台考虑,class文件会保存类名、方法名、属性名等信息。而这些信息是很容易被反编译成Java源码的。
ProGuard是一种字节码优化工具,通过它可以删除无用的代码,并且将代码中的类名、方法名、属性名替换成简短并难以理解的名称,从而达到代码压缩、混淆、优化的目的。经过ProGuard混淆后的App往往体积会缩小(一般可以缩小25%左右),并且经过混淆后会给反编译增大难度,在一定程度上防止了App的逆向。
先介绍下Android Studio中ProGuard的配置。
·minifyEnabled:混淆开关。
·proguard-android.txt:SDK中ProGuard的默认配置规则。
·proguard-rules.pro:自定义ProGuard的配置规则。
编写的ProGuard内容不仅可以存在于proguard-rules.pro文件中,还可以自定义一个.pro文件(如my-proguard.pro),然后在proguard-rules.pro中通过–include引入:
1. -include my-proguard.pro
另外需要注意的是,并不是所有文件都能被混淆,以下几种类型的类不能使用混淆。
·反射用到的类。
·Jni中调用的类。
·AndroidManifest中配置的类,如Activity、Fragment及Framework中的类。
·其他情况会受到混淆影响的类。例如定义一个TAG常量,我们可能会通过这种方式来获取类名:Demo.class.getSimpleName(); TAG提供给输出日志使用,混淆后,日志中看不到原本的类名,所以此处的TAG一般直接使用常量“Demo”。
随书源码中有ProGuard的详细语法介绍。
如果ProGuard配置得不好的话可能会带来难以预料的问题,常见的如导致App崩溃等,而且增大了Bug的排查难度。
例如测试环境一般不使用ProGuard,但是在正式环境中会启用ProGuard,就会出现测试环境运行正常,但是正式环境异常甚至崩溃的情况。这种情况可以从ProGuard启用混淆的角度来进行排查。
因此整理一份混淆配置模板是一项必要的工作,随书源码中整理了一套Android ProGuard模板,这是在实际开发过程中总结出来的,可以直接参考使用,非常方便。
App的数据来源就是API接口,所以API接口对App的重要性不言而喻。设计API接口首要考虑的就是安全机制。
本书将从以下3个方面来考虑怎么设计一个安全的API接口。
防篡改就是防止请求的URL参数值发送至服务器的时候被改动。
普通的API接口格式是xxx.html?key1=xx?key2=xx?key3=xx。我们采用sign签名的方式保证数据传输的正确性。
App一般会在公司的后台申请一个appKey和一个appSecret,这两个是一一对应的。appKey会作为一个参数写在URL中,然后再发送至服务器。appSecret则用于参与生成sign的计算。sign算法需要足够复杂,最好有一套自己的签名算法,而不是外界公开的签名算法。一般采用安全散列算法实现,如SHA1。
sign也要作为一个参数添加到URL中,和appKey一并发送至服务器,如xxx.html?appK ey=xx?sign=xx?key1=xx?key2=xx?key3=xx。服务器收到请求后,会通过appKey查找对应的appSecret,然后通过同样的散列算法,得到一个sign,最后比较一下两个sign是否相等。如果不相等则数据遭到篡改,废弃这条请求。
另外,关于appSecret有以下两种使用方式。
·appSecret直接写在客户端代码中,这样即可直接获取调用。
·appSecret还可以通过一个专门的接口getSign从后台获取。这种情况首先需要用户登
录,登录成功后,服务器返回一个accessToken参数,然后调用appSecret接口时需要带上这个accessToken参数。
解决了数据被篡改的问题之后,还有一个问题就是,如果一条正常的请求数据被其他人获取到了,从而进行第二次甚至多次请求应该怎么办呢?
我们这里可以使用nonce + timestamp的解决方案。
1)nonce
nonce是一个随机数,由客户端生成,每次请求时将随机数作为一个参数发送给服务器。服务器会在数据库里查询是否有这个nonce,如果没有则是一条新的请求,进行正常处理即可;如果能查到已经存在这个nonce,则废弃这条请求。
nonce可以通过UUID.randomUUID().toString()来生成。
有个问题是,这个nonce在数据库中随着请求量的增大,产生的数据量也会越来越大。为了解决这个问题,我们可以采用timestamp时间戳的方式。
2)timestamp
时间戳是服务器给URL请求设定的一个有限时间范围起点。例如服务器认为客户端发送过来的timestamp与服务器当前的时间戳之差在10分钟之内,则认为这条请求是有效的。超过了10分钟则废弃这条请求。如果是10分钟内的请求,需要在数据库中查询nonce是否有记录,如果有记录,则废弃这条请求;如果没有记录,则记录这个nonce,并且将超过10分钟的nonce全部删除。
关于这个timestamp获取的问题,同样需要从服务器获取,不然客户端怎么知道服务器的起点计算时间呢?上面说到有个获取appSecret的接口,其实我们可以在这个接口中一并将timestamp获取到。
需要注意的是,客户端需要在每次发送URL请求的时候,计算一下timestamp的值。例如获取到的timestamp=1564588800,那么下次请求的timestamp的值是1564588800 + diffTime。服务器收到timestamp后会跟服务器当前的时间戳做对比,看是否大于10分钟。
一条正常的URL格式请求如下:
demo.html?nonce=xx?timestamp=xx?appKey=xx?sign=xx?key1=xx?key2=xx?key3=xx
所以一个正常的API请求应该是这样的流程:用户登录成功后,每次进行一个API请求,都需要调用一次getSign接口,用于获取appSecret和timestamp。但是肯定不是每次请求都要获取一次这个接口的数据。我们可以在首次请求后,将这些数据保存起来,后续API请求可以直接使用,除非appSecret或者timestamp为空(例如App退出登录后清空appSecret和timestamp)。
HTTPS是用SSL + HTTP构建的可用于网络传输以及身份认证的网络协议。HTTP使用明文通信,传输的内容可以通过抓包工具截取,HTTPS自动对数据进行加密压缩,防止监听,防止被抓包以看到明文,防止中间人截取。
苹果从iOS9就开始默认使用HTTPS,Android 9.0也开始强制使用HTTPS了,默认阻塞HTTP请求。如果需要在Android 9.0中兼容HTTP,则需要进行额外的特定配置。
每一组API接口需要对应一个大版本号,大版本号一般是跟App的大版本对应的。例如App第一版本命名为v1,App第二版本经过改版后,接口返回的内容一般也会有变化,这里命名为v2。
以Restful API风格为例,如/api/v{x}/,一般在API接口的前面位置加上v{x}这个值。
x分以下两种情况。
·整型表示大版本号,如v1、v2。
·浮点型表示小版本号,是对大版本定义的业务接口的补充,如v1.1。
举个例子,如下。
/api/v1/userinfo:表示v1这个大版本的App,有一个userinfo业务类型的接口。
/api/v2/userinfo:表示v2这个大版本的App,有一个userinfo业务类型的接口。
/api/v2.1/userinfo:表示在v2这个大版本中,对userinfo这个业务接口进行了一些细微调整。
请求参数由公共请求参数和业务请求参数组合而成。
1)公共请求参数
公共请求参数如表2.1所示。
其中version表示客户端版本号,此处将其传给服务器,由服务器根据客户端版本号进行一定的业务逻辑判断。
2)业务请求参数
以登录为例,表2.2展示了登录API接口的业务请求参数。
一个完整的URL如下(以传统的URL格式为例):
/api/v1/getUserInfo.do?nonce=3ec934e8-81b9-492e-933d-a5dc41eb15bd×ta mp=1567118072&sign=c1741210c06f3827d1d2f3bfd1f1fc2878377307&accessToken=Lv zFEeQSuU9B4DrnoPO9D4CL3ZhKCetZ%2FRckCWQlgb9qmNLmgCKxkymoC4pEs5LF w1lSAGZXKvHe%0AvpFgXKmAAQ%3D%3D&appKey=3b6af23db66c69b7131a8186f90f e663&name=jack&password=123456
使用JSON格式(而不是XML)返回API请求的结果。JSON格式简洁,传输数据量小,而且能展示复杂的数据结构。
1. { 2. "body":{ 3. }, 4. "code":0, 5. "msg":"" 6. }
·code:API接口执行状态,例如0表示成功,-100表示网络超时,-200表示鉴权失败等
·msg:非成功状态下需要说明的信息,一般与code状态码一一对应定义。
·body:返回的具体数据,通常是JSON格式。
客户端需要对所有code的值进行逐一处理。
一般来说,一个API接口投入使用后,除非这个接口确定废弃不再使用,不然一般情况下不能对这个接口进行修改。例如修改了API的请求参数和返回值,会对使用此API接口的App带来不可预估的影响,最严重的影响就是崩溃。
如果接口需要变更,那需要保证API接口的变更能够向下兼容,就是API的变更不影响原来使用API接口的客户端。如果不能够保证向下兼容,那么只能建立一个新的API接口。
接口变更有以下两种情况:能够向下兼容;不能向下兼容,需要建立一个新的API接口。以下是一个原始的JSON格式的返回值:
1. { 2. "id": 1, 3. "name": "j ack", 4. "address": "usa" 5. }
原始URL: /api/v1/userinfo
1)向下兼容
·URL的请求参数新增字段,例如:
/api/v1/userinfo?currentTime=2019/12/12
新增一个请求参数currentTime,不影响当前API接口的使用。
·URL的返回数据结构新增字段,例如:
1. { 2. "id": 1, 3. "name": "j ack", 4. "address": "usa" 5. "age": 18 6. }
新增一个age字段,旧版本的App只需要id、name、address 3个字段,多出来的age字段对旧版本来说没有用处,也不会被额外处理。而新版本的App使用到这个接口时,可以对这4个字段进行解析,将新增加的age字段利用起来。
2)建立一个新的API接口
如果返回的数据格式字段值类型发生了变化,例如age原先是数字类型,值为18,现在改为字符串类型,值为“18”,这样旧版本的App解析的时候可能就会出错,影响使用。
1. { 2. "id": 1, 3. "name": "j ack", 4. "address": "usa" 5. "age": "18" 6. }
或者直接将age这个字段的名称改变了,变成year;或者直接将age这个字段给删除了。这些情况对当前使用这个API接口的App一定会产生影响,所以如果出现这种情况需要增加一个新的接口,而不是在原有的接口上修改。
传统格式API: /getUserInfo.do?id=10000&netType=wifi
API由具体的业务地址(getUserInfo.do)和请求参数(id=10000&netType=wifi)拼装而成。请求类型一般就get和post两种,而且有时候在实际项目中这两种类型的请求并没有区分得很详细,两者都可以使用。
Restful API: /api/v1/userinfo
Restful API通过URI(如api/vi/userinfo)来表示资源,通过GET、POST、PUT、DELETE等方法来表示操作行为。
·GET:获取资源。
·POST:新建资源。
·PUT:更新资源。
·DELETE:删除资源。
URI一般使用名词命名,例如GET/userinfo,表示获取全部用户的信息;GET/userinfo/100,表示获取id为100的用户信息。
·SDK发布时需配套有完整且详细的使用说明文档,包括混淆配置说明。
·SDK需要详细记录每个版本的变更内容。
·SDK如果对外开放,需要有一个专门的网站,同时附上SDK的说明文档、demo、变更历史等。
·SDK的minSdkVersion要尽量小,最好不要超过使用SDK的项目的minSdkVersion。
·尽量不要引用第三方库,要尽量使用Android系统自带的功能,然后在其基础上进行封装。如果一定要用到第三方库,可以使用provided依赖,并告知调用方主动依赖这个第三方库。
·如果打出来的包是AAR格式的,需要注意res下面的资源文件名称,以避免和调用方的res文件名称冲突,所以一般SDK里面的res文件名称需加上特定的修饰符,例如公司 + 项目名称。
·SDK需要有较强的容错性,要增大力度对SDK内部的异常进行捕获。
·SDK对外提供的接口,需要对其传入的参数的合法性和有效性进行检测。
·SDK内部对于关键路径要有详细的Log记录,便于后期排查问题。
SDK需要做到代码结构层次分明,功能清晰。
一个典型的SDK一般可以分为以下3层。
接入层的功能就是对外提供接口,供给调用方使用。一般对外提供的接口我们会定义一个接口文件,里面的方法都是可以对外提供的接口。所以接入层一般都是定义的接口文件,另外还有一个统一管理所有业务功能模块的类,是外界跟SDK交互的统一入口。它还负责统一配置和进行初始化工作,例如初始化业务模块的Manager文件。
业务层的作用就是实现具体的业务逻辑。对于业务层的设计,我们会按照业务功能划分为不同的模块,每一个模块通过对应的Manager文件进行管理,并且Manager文件会具体实现接入层定义的接口方法。
基础层里面包括各种功能模块,例如SDK自行封装的网络请求模块,还有数据库模块、日志模块、Crash模块等。总而言之就是对业务层提供支持。
图2.1所示是SDK各层调用流程图。
一个完整的SDK项目实例目录结构如图2.2所示。
其中api代表接入层,business代表业务层,common代表基础层。
加壳也就是加固,或者叫作加密,App打包成APK后,通过加壳技术给App上一层保护,用来预防App被破解、反编译、二次打包等。
免费的加固应用有乐固、阿里聚安全、360加固保等;收费的有梆梆安全、爱加密,以及一些免费加固应用的收费版本。
以下是相关机构发布的安全报告:
“65%的移动App至少存在1个高危漏洞,平均每1个App就有7.32个漏洞; 88%的金融类App存在内存敏感数据泄露问题;每10个娱乐类App就有9个至少包含一个高危漏洞。——《FreeBuf:2017年度移动App安全漏洞与数据泄露现状报告》”
因此加壳是App开发中的重要一环,没了壳等于将危险直接暴露在外。建议如果有条件的话还是选择收费版本的加壳软件,因为收费版本加密的强度会高些,而且还有合同保障。
Android的四大组件Activity、Service、ContentProvider、BroadcastReceiver,有一个android:exported属性。如果是false,那么只能在同一个应用程序组件间或带有相同用户id的应用程序间才能启动或绑定该服务;如果是true,则该组件可以被任意应用启动或执行,这样就会有组件被恶意调用的风险。
1. <activity 2. android:name="com.androidwind.safe.DemoActivity" 3. android:exported="false" 4. android:label="@string/app_name" > 5. </activity>
如果组件没有包含过滤器intent-filter,那么android:exported属性的值默认是false;如果组件包含了至少一个intent-filter,那么android:exported属性的值默认是true。
如果必须暴露这些组件,那么需要添加自定义的permission权限来进行访问控制。
1. <activity 2. android:name="com.androidwind.safe.DemoActivity" 3. android:exported="true" 4. android:label="@string/app_name" 5. android:permission="com.androidwind.permission.demoPermission" > 6. </activity>
外部应用如果想直接打开DemoActivity,需要在AndroidManifest.xml中进行配置:
<uses-permission android:name="com.androidwind.permission.demoPermission" />
因为WebView在低系统版本中存在远程代码执行漏洞,如JavascriptInterface,中间人可以利用此漏洞执行任意代码,所以App的targetSdkVersion需要大于17,也就是Android版本至少要达到4.2。
另外需要将Webview自动保存密码的功能关闭:
webView.getSettings().setSavePassword(false);
有的时候为了方便跟踪用户操作,App通常会把日志保存在SD卡上,在适当的时候将用户日志上传到服务器,然后开发人员可以查看用户日志信息,分析相关的问题。但是这样做有一个很大的风险就是日志里面往往包含了App的一些敏感信息,如URL地址、参数、类名,以及用户的使用记录,包括名称、id、聊天记录等。虽然App可以做一些操作来减少风险,如定期删除日志等,但是毕竟这些信息还是外露了,可能会被别有用心的人利用。
因此,对日志输出的要求如下。
·不存储在外部空间中,如手机存储空间。
·测试环境下可以使用logcat输出日志信息。
·正式环境下屏蔽所有的日志输出,包括logcat和手机外部存储。
·不使用System.out输出日志。
另外,项目中日志输出需要使用同一个日志管理类,不应该存在多个输出日志的类。
所有网络请求必须使用HTTPS。而且在Android P系统中,默认使用加密连接,所有未加密的连接会受限:
“Google表示,为保证用户数据和设备的安全,针对下一代 Android 系统(Android P)的应用程序,将要求默认使用加密连接,这意味着 Android P 将禁止 App 使用所有未加密的连接,因此搭载Android P系统的安卓设备无论是接收或者发送流量,未来都不能明码传输,需要使用下一代传输层安全协议(Transport Layer Security),而 Android Nougat 和 Oreo 则不受影响。”
如果使用HTTP连接,那么会返回如下错误信息。
·HTTP在使用HttpUrlConnection时遇到的异常:java.io.IOException:Cleartext HTTP traffic to *** not permitted。
·HTTP在使用OkHttp时遇到的异常:java.net.UnknownServiceException: CLEARTEXT communication *** notpermitted by network security policy。
参考第2章2.1.1小节中的“API接口安全设计规范”。
通常简单的做法是将密钥等敏感信息保存在Java代码中,例如直接写在静态变量里。但是这样很容易被编译破解,即使代码有混淆。
我们可以考虑将一些敏感信息,如密码、密钥等,通过cpp代码保存在so文件中。这样会增加敏感信息被破解的难度。
需要说明的是,如果使用so文件,那么so文件也需要加壳。
github上有一个可以自动生成加密so库的插件cipher.so,这样通过在gradle里配置需要加密的数据,即可加密保存到so库,并且自动生成对应的Java接口。
1. cipher.so { 2. keys { 3. 数据库 { 4. value = '你好数据库!' 5. } 6. hello { 7. value = 'Hello From Cipher.so' 8. } 9. } 10. // signature = '1234567890' 11. encryptSeed = 'HelloSecretKey' 12. }
CodeReview也叫代码评审、代码复查,是一种通过阅读代码来检测编码是否符合代码规范的活动。
代码质量从低到高有这几种级别:可编译;可运行;可测试;可读;可维护;可重用。
自动化测试过的代码只能达到第三层次,也就是可测试阶段,而通过CodeReview的代码可以上升到更高的层次。
CodeReview可以达到如下目的。
·提升开发人员的代码编写质量。
·优化现有版本。
·开发人员间代码和业务的互相熟悉。
·促进团队知识共享。
·代码能够运行吗?它有没有实现预期的功能,逻辑是否正确等。
·所有的代码是否简单易懂?
·代码符合你所遵循的编程规范吗?这通常包括大括号的位置、变量名和函数名、行的长度、缩进、格式和注释。
·是否存在多余的或是重复的代码?
·代码是否尽可能地模块化了?
·是否有可以被替换的全局变量?
·是否有被注释掉的代码?
·循环是否设置了长度和正确的终止条件?
·是否有可以被库函数替代的代码?
·是否有可以删除的日志或调试代码?
·所有的输入数据是否都进行了检查(检测正确的类型、长度、格式和范围)并且编码?
·在哪里使用了第三方工具,返回的错误是否被捕获?
·输出的值是否进行了检查并且编码?
·无效的参数值是否能够处理?
·是否有注释,并且描述了代码的意图?
·所有的函数都有注释吗?
·对非常规行为和边界情况的处理是否有描述?
·第三方库的使用和函数是否有文档?
·数据结构和计量单位是否进行了解释?
·是否有未完成的代码?如果是的话,是不是应该移除,或者用合适的标记进行标记,如TODO?
·代码是否可以测试?例如,不要添加太多的或是隐藏的依赖关系,不能够初始化对象,测试框架可以使用的方法等。
·是否存在测试代码,它们是否可以被理解?例如,至少达到你满意的代码覆盖(code coverage)。
·单元测试是否真正地测试了代码是否可以完成预期的功能?
·是否检查了数组的“越界”错误?
·是否有可以被已经存在的API所替代的测试代码?
·代码审查单还可以利用现有的代码检测工具来实现,如findbugs等。
每个团队要有一个CodeReview清单(CodeReview-checklist),代码审查清单也需要不断地迭代更新和优化。
关于CodeReview清单,另外补充一点:每个团队都需要有自己的技术选型规范,例如网络请求用什么框架,图片加载用什么框架,Adapter用什么方案等。因为每一个功能可能有多种实现方式,为了保证统一性,不在一个项目里面对同一种功能采用多种实现方式(例如使用Glide,又引入了Picasso),所以我们在CodeReview的时候也需要对这一块进行检查。
·face 2 face,面对面阅读代码。
·抓重点,如设计、架构、可读、健壮。
·所有人参与CodeReview。
·简单点:技术经理可以从这周提交的代码中找出典型的代码片段,拿出来进行讲解和讨论。完善点:每个开发者互相进行CodeReview,找出有问题的代码片段,挑选出来供大家讨论。灵活点:遇到问题就跟开发人员面对面或者IM沟通。
·CodeReview时间不宜过长,半个小时左右为宜。
每个项目团队每周CodeReview后需要在文档管理系统上总结CodeReview的内容,按照CodeReview-checklist进行汇报。文档管理系统参考14.2.2小节介绍。