Android开发手册

看完了《阿里巴巴Android开发手册》,虽然部分知识有点过时,但是还是有很多可以借鉴的地方,摘要了部分我认为还是很有用的地方。

一.资源文件命名
需带模块前缀,比如
Activity 的 layout 以 module_activity 开头

二.Android基本组件
1.Activity 间的数据通信,对于数据量比较大的,避免使用 Intent + Parcelable的方式,可以考虑EventBus等替代方案,以免造成 TransactionTooLargeException
曾经碰到过intent+Bitmap跳转发生异常。
2.Activity 间通过隐式 Intent 的跳转,在发出 Intent 之前必须通过 resolveActivity检查,避免找不到合适的调用组件,造成 ActivityNotFoundException 的异常。
3.避免在 Service#onStartCommand()/onBind()方法中执行耗时操作,如果确实有需求,应改用 IntentService 或采用其他异步机制完成。 因为Service也是运行在Main线程中的,不能执行延迟操作
4.避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作,应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做
理由同上
5.避免使用隐式 Intent 广播敏感信息,信息可能被其他注册了对应BroadcastReceiver 的 App 接收。当然在Android8.0之后,官方移除了大部分隐式广播,主要是考虑到很多应用随意注册系统广播,触发广播的系统事件会导致所有应用快速的连续消耗资源。当然,目前大部分使用第三方库比较多比如EventBus,广播反而用得少
6.不要在Activity#onDestroy()内执行释放资源的工作,例如一些工作线程的销毁和停止,因为onDestroy()执行的时机可能较晚。可根据实际需要,在Activity#onPause()/onStop()中结合 isFinishing()的判断来执行。

三.UI与布局
1.布局中不得不使用 ViewGroup 多重嵌套时,不要使用 LinearLayout 嵌套,改用 RelativeLayout,可以有效降低嵌套数。
Android 应用页面上任何一个 View 都需要经过 measure、 layout、 draw 三个步骤才能被正确的渲染。从 xml layout 的顶部节点开始进行 measure,每个子节点都需要向自己的父节点提供自己的尺寸来决定展示的位置,在此过程中可能还会重新measure(由此可能导致 measure 的时间消耗为原来的 2-3 倍)。节点所处位置越深,套嵌带来的 measure 越多,计算就会越费时。这就是为什么扁平的 View 结构会性能更好。
同时,页面拥上的 View 越多,measure、 layout、 draw 所花费的时间就越久。要缩短这个时间,关键是保持 View 的树形结构尽量扁平,而且要移除所有不需要渲染的View。理想情况下,总共的 measure,layout,draw 时间应该被很好的控制在 16ms以内,以保证滑动屏幕时 UI 的流畅。
当然,现在更推荐使用强大的constraintlayout
2.在 Activity 中显示对话框或弹出浮层时,尽量使用DialogFragment,而非Dialog/AlertDialog,这样便于随Activity生命周期管理对话框/弹出浮层的生命周期。
3.禁止在设计布局时多次设置子 view 和父 view 中为同样的背景造成页面过度绘制,推荐将不需要显示的布局进行及时隐藏。
可以使用Space,ViewStub ,merge等便签优化布局
4.尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错。

四.进程、 线程与消息通信
1.在 Application 的业务初始化代码加入进程判断,确保只在自己需要的进程初始化。特别是后台进程减少不必要的业务初始化。
2.新建线程时,必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor或者其他形式自定义的线程池),不允许在应用中自行显式创建线程。
3.线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

五.文件与数据库
1.任何时候不要硬编码文件路径,请使用 Android 文件系统 API 访问。
正例:File file = new File(Environment.getExternalStoragePublicDirectory
反例:File file = new File("/mnt/sdcard/Download/Album", alName);
还有,
正例:findViewById(Window.ID_ANDROID_CONTENT)
反例:findViewById(com.android.internal.R.id.content)
2.SharedPreference 中只能存储简单数据类型(int、 boolean、 String 等),复杂数据类型建议使用文件、数据库等其他方式存储。
3.SharedPreference 提交数据时,尽量使用 Editor#apply(),而,非Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用Editor#commit()
apply是异步,commit是同步
4.多线程操作写入数据库时,需要使用事务,以免出现同步问题。
5.大数据写入数据库时,请使用事务或其他能够提高 I/O 效率的机制,保证执行速度。

六.Bitmap、 Drawable 与动画
1.加载大图片或者一次性加载多张图片,应该在异步线程中进行。图片的加载,涉及到 IO 操作,以及 CPU 密集操作,很可能引起卡顿
2.png 图片使用 tinypng 或者类似工具压缩处理,减少包体。现在比较推荐webp格式的图片
3.使用完毕的图片,应该及时回收,释放宝贵的内存。
bitmap.recycle();
4.在 Activity.onPause()或 Activity.onStop()回调中,关闭当前 activity 正在执行的的动画。
5.使用 ARGB_565 代替 ARGB_888,在不怎么降低视觉效果的前提下,减少内存占用。
说明:
android.graphics.Bitmap.Config 类中关于图片颜色的存储方式定义:
1) ALPHA_8 代表 8 位 Alpha 位图;
2) ARGB_4444 代表 16 位 ARGB 位图;
3) ARGB_8888 代表 32 位 ARGB 位图;
4) RGB_565 代表 8 位 RGB 位图。
位图位数越高,存储的颜色信息越多,图像也就越逼真。大多数场景使用的是ARGB_8888 和 RGB_565,RGB_565 能够在保证图片质量的情况下大大减少内存的开销,是解决 oom 的一种方法。但是一定要注意 RGB_565 是没有透明度的,如果图片本身需要保留透明度,那么就不能使用 RGB_565。
6.尽量减少 Bitmap(BitmapDrawable)的使用,尽量使用纯色(ColorDrawable)、渐变色(GradientDrawable)、 StateSelector(StateListDrawable)等与 Shape 结合的形式构建绘图。
7.当 View Animation 执行结束时,调用 View.clearAnimation()释放相关资源

七.安全
1.将 android:allowbackup 属性设置为 false,防止 adb backup 导出数据。
2.META-INF 目录中不能包含如.apk,.odex,.so 等敏感文件,该文件夹没有经过签名,容易被恶意替换。
3.Receiver/Provider 不能在毫无权限控制的情况下,将 android:export 设置为 true。
4.阻止 webview 通过 file:schema 方式访问本地敏感数据
5.不要广播敏感信息,只能在本应用使用 LocalBroadcast,避免被别的应用收到,或者 setPackage 做限制。
6.不要把敏感信息打印到 log 中。
攻击者可以直接从 Logcat 中读取这些敏感信息。所以在产品的线上版本中关闭调试接口,不要输出敏感信息。
7.对于内部使用的组件,显示设置组件的"android:exported"属性为 false。限制其不同user id 的应用调用当前组件。恶意应用可通过向受害者应用发送空数据、异常或者畸形数据从而使应用产生本地拒绝服务。
8.Android5.0 以后安全性要求 较高的应用 应该使 用 window.setFlag(LayoutParam.FLAG_SECURE) 禁止录屏。
9.开放的 activity/service/receiver 等需要对传入的 intent 做合法性校验
10.Android WebView 组件加载网页发生证书认证错误时,采用默认的处理方法handler.cancel(),停止加载问题页面。如果该方法实现调用了 handler.proceed()来忽略该证书错误,则会受到中间人攻击的威胁,可能导致隐私泄露.

八.网友开发补充
1.使用 0px 代替 0dp,这样就可以在获取时避免系统进行换算,提升代码的执行效率。
2.尽量采用 switch case 来判断,如果不能实现则再考虑用 if else,因为在多条件下使用 switch case 语句判断会更加简洁。
3.在能满足需求的情况下,尽量用 invisible 来代替 gone,因为 gone 会触发当前整个 View 树进行重新测量和绘制。
4.ScrollView 和 NestedScrollView 都能实现需求的前提下,优先选用 NestedScrollView,是因为 NestedScrollView 和 RecyclerView 支持相互嵌套,而 ScrollView 是不支持嵌套滚动的。
5.应用图标应该放在 mipmap 目录下,其他图片资源应当放到 drawable 目录下。
(mipmap 也叫 mip 贴图,是 OpenGL 中的纹理用于减少锯齿的解决方案。所以在 Android 中应该也是相同的作用。由于设备的屏幕密度很有可能会介于 xxhdpi 和 xxxhdpi 之间,这时候如果用 drawable 的资源就有可能产生锯齿。)
6.api 和 implementation,在能满足使用的情况下,优先选用 implementation,因为这样可以减少一些编译时间。\n\n(子Moudle中使用,implementation相当于"private",api相当于"public")
7.金额计算统一使用BigDecimal处理,以免发生精度丢失。
8.我们在定义后台返回的 Bean 类时,不应当将一些我们没有使用到的字段添加到代码中,因为这样会消耗性能,因为 Gson 是通过反射将后台字段赋值到 Java 字段中。
9.如果后台给定的字段名不符合代码命名的时候,例如当遇到 student_name 这种命名时,我们应当使用 Gson 框架中的 @SerializedName 注解进行重命名。
10.请求的接口参数和返回字段必须要写上注释,除此之外还应该备注对应的后台接口文档地址,以便我们后续能够更好地进行维护和迭代。
11.接口请求成功的提示可以不显示,但请求失败的提示需要显示给到用户,否则会加大排查问题的难度,也极有可能会把问题掩盖掉,从而导致问题遗留到线上去。
12.非公开的成员变量必须以小 m 开头,静态变量则用小 s 开头
13.常量则需要用大写,并且用下划线代替驼峰
14.不允许包名中携带英文大写,包名要按照模块或者作用来划分
15.某个类实现的接口过多时,很难区分某个方法是实现的哪个接口,此时可以使用“@link” 注释,帮助我们快速定位接口类在项目中所在的位置。
16.不要直接使用“Exception”捕捉异常,而应该列出具体的异常类型,然后使用“CrashReport”或其他工具进行统计分析。
17.应当将 Intent/SharedPreferences 中的 key 常量保存到一个管理类中。
18.应当将 接口的Code保存到一个管理类中。
19.如果跳转的 Activity 需要传递参数,应该在目标的 Activity 中定义静态的 start 又或者 newIntent 方法。
20.如果创建的 Fragment 需要传递参数,应该在目标的 Fragment 中定义静态的 newInstance 方法。
21.另外如果一个界面需要传递的参数过多(5 个以上),建议用一个对象对这些参数进行封装,然后实现 Serializable 或者 Parcelable 接口进行传递。 22.链式写法不能只用一行代码,而是应当遵守一句 API 换一行的策略。
23.集成一些第三方框架或者 SDK,必须注明框架的作用及出处,以便出现问题时能够快速核查和反馈。
24.使用第三方库必须要依赖指定的版本号,而不能使用 latest-version 或者 + 来指定依赖库最新的版本号。
25.尽量避免 Copy 第三方库的技术代码到项目中,特别是在放置到项目业务模块中,因为这样会增加项目的复杂度,从而降低可维护性。
26.能用框架就用成熟框架,尽量不要自己编写或者修改框架,如果有需要,要对这块进行严格测试。
27.模块混淆配置:请不要使用 proguardFiles 语句,而是应该使用 consumerProguardFiles 语句,因为 consumerProguardFiles 语句会将混淆规则和资源代码一同打包到 aar 包中,这样做的好处在于:在项目编译时会将 aar 包中的混淆规则合并到主模块中。

android {

    defaultConfig {
        // 模块混淆配置
        consumerProguardFiles 'proguard-xxx.pro'
    }
}

28.资源前缀限制:我们应该在模块中加入此限制,这样我们在模块中添加资源时,编译器如果发现资源名称前缀不符合规范,则会出现代码警告。这样做的好处在于,以某一名称作为前缀,可以有效避免在编译时引发的一些资源合并冲突。

android {
    // 资源前缀限制
    resourcePrefix \"xxx_\"
}

29.框架版本管理:我们应该统一抽取框架的版本到 config.gradle 文件中。
30.类注释规范:author 是创建者(必填项)、time 是创建时间(必填项)、desc 是类的描述(必填项)

  1. String ID 命名规范
    请以 模块 + 功能 来命名
    Color ID 命名规范
    请以 模块 + 作用 + color 来命名
    Style 命名规范
    如果只是主题相关的样式,以 Theme 命名结尾,控件样式则以 Style 命名结尾
    32.不能根据设计图给定的宽高把 TextView 或者 Button 的宽高定死,而是通过 wrap_content 和 padding 的方式来调整 View 的宽高
    33.应该在布局文件根布局中定义 tools:context 属性,以便在布局文件中快速定位到对应的类。
    此外,tools 属性还有各种各样的用途
    34.不要使用switch(view.getId()),Gradle5.0之后,R.id.viewId 不再是 final常量