Android 开发规范

Android开发规范

命名规范

  1. 资源文件需要带模块名前缀(模块化实行暂无需),以小写加下划线方式命名.
  2. layout文件命名方式
Activity 的 layout以module_activity开头
Fragment 的 layout 以 module_fragment 开头
Dialog 的 layout 以 module_dialog 开头
include 的 layout 以 module_include 开头
ListView 的行 layout 以 module_list_item 开头
RecyclerView 的 item layout 以 module_recycle_item 开头
GridView 的 item layout 以 module_grid_item 开头
  1. drawable 资源命名方式
模块名_业务功能描述_控件描述_控件状态限定词
如:module_login_btn_pressed,module_tabs_icon_home_normal
  1. anim 资源命名方式
模块名_逻辑名称_[方向|序号]
module_fade_in , module_fade_out , module_push_down_in
module_loading_grey_001
  1. color 资源使用#AARRGGBB 格式,写入 module_colors.xml 文件中,命名格式采用以下规则:
模块名_逻辑名称_颜色
<color name="module_btn_bg_color">#33b5e5e5</color>
  1. dimen 写入 module_dimens.xml 文件中,采用以下规则:
模块名_描述信息
<dimen name="module_horizontal_line_height">1dp</dimen>
  1. style 资源采用“父 style 名称.当前 style 名称”方式命名,写入module_styles.xml文件中,首字母大写:
<style name="ParentTheme.ThisActivityTheme">
 …
</style>
  1. string资源文件或者文本用到字符需要全部写入module_strings.xml文件中,采用以下规则:
模块名_逻辑名称
moudule_login_tips,module_homepage_notice_desc
  1. Id 资源原则上以驼峰法命名,View 组件的资源 id 建议以 View 的缩写作为
    前缀。常用缩写表如下:
    控件 缩写
LinearLayout ll
RelativeLayout rl
ConstraintLayout cl
ListView lv
ScollView sv
TextView tv
Button btn
ImageView iv
CheckBox cb
RadioButton rb
EditText et

其它控件的缩写推荐使用小写字母并用下划线进行分割,例如:ProgressBar 对应
的缩写为 progress_bar;DatePicker 对应的缩写为 date_picker。

基本组件规范

  1. 讨论Activity 间的数据通信,对于数据量比较大的,避免使用 Intent + Parcelable的方式,可以考虑EventBus等替代方案,以免造成TransactionTooLargeException.
  2. Activity#onSaveInstanceState()方法不是 Activity 生命周期方法,它是用来在 Activity 被意外销毁时保存 UI 状态的,只能用于保存临时性数据,例如UI控件的属性等,不能跟数据的持久化存储混为一谈。持久化存储应该在 Activity#onPause()/onStop()中实行.
  3. Activity 间通过隐式 Intent 的跳转,在发出 Intent 之前必须通过 resolveActivity 检查,避免找不到合适的调用组件,造成 ActivityNotFoundException 的异常。
  4. 避免在 Service#onStartCommand()/onBind()方法中执行耗时操作,如果有需求,应改用 IntentService 或采用其他异步机制完成.
  5. 避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作,应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做.
  6. 避免使用隐式 Intent 广播敏感信息,信息可能被其他注册了对应 BroadcastReceiver 的 App 接收,如果广播仅限于应用内,则可以使用 LocalBroadcastManager#sendBroadcast()实现,避免敏感信息外泄和 Intent 拦截的风险.
  7. 添 加 Fragment 时,确保 FragmentTransaction#commit() 在 Activity#onPostResume()或者 FragmentActivity#onResumeFragments()内调用。不要随意使用 FragmentTransaction#commitAllowingStateLoss()来代替.
  8. 不要在 Activity#onDestroy()内执行释放资源的工作,例如一些工作线程的销毁和停止,因为 onDestroy()执行的时机可能较晚。可根据实际需要,在 Activity#onPause()/onStop() 中结合 isFinishing()的判断来执行.
  9. 如非必须,避免使用嵌套的 Fragment.
  10. 总是使用显式 Intent 启动或者绑定 Service,且不要为服务声明 Intent Filter
    保证应用的安全性。如确实需要使用隐式调用,则可为 Service 提供 Intent Filter 并从 Intent 中排除相应的组件名称,必须搭配使用 Intent#setPackage()方法设置 Intent 的指定包名,消除目标服务的不确定性.
  11. Service 需要以多线程来并发处理多个启动请求,建议使用 IntentService,避免复杂的设置。Service 组件一般运行主线程,避免耗时操作,如果有耗时操作应该在 Worker 线程执行,推荐使用 IntentService 执行后台任务.
  12. 对于只用于应用内的广播,优先使用 LocalBroadcastManager 来进行注册和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率.
  13. 当前 Activity 的 onPause 方法执行结束后才会创建(onCreate)或恢复(onRestart)别的 Activity,所以在 onPause 方法中不适合做耗时较长的工作,这会影响页面之间的跳转效率.(注:所有耗时任务都不应在主线程进行,在页面跳转的时候涉及到流畅性问题,所以需要格外注意).
  14. Activity或者Fragment中动态注册BroadCastReceiver时,registerReceiver() 和 unregisterReceiver()要成对(生命周期对应)出现.Activity 的生命周期不对应,可能出现多次 onResume 造成 receiver 注册多个,但最终只注销一个,其余 receiver 产生内存泄漏。
  15. Android 基础组件如果使用隐式调用,应在 AndroidManifest.xml 中使用 <intent-filter> 或在代码中使用 IntentFilter 增加过滤.

UI 与布局

  1. 使用 ConstraintLayout,尽量不要嵌套,降低布局层级(ViewStub,merge).
  2. 在 Activity 中显示对话框或弹出浮层时,尽量使用 DialogFragment,而非 Dialog/AlertDialog,这样便于随Activity生命周期管理对话框/弹出浮层的生命周期.
  3. 讨论: 文本大小使用单位 dp,View 大小使用单位 dp。对于 TextView,如果在文字大小确定的情况下推荐使用 wrap_content 布局避免出现文字显示不全的适配问题。
  4. 禁止在设计布局时多次为子 View 和父 View 设置同样背景进而造成页面过度绘制,无需显示的布局及时隐藏.
  5. 在需要时刻刷新某一区域的组件时,建议通过以下方式避免引发全局 layout 刷新:
1) 设置固定的 View 大小的宽高,如倒计时组件等;
2) 调用 View 的 layout 方法修改位置,如弹幕组件等;
3) 通过修改 Canvas 位置并且调用 invalidate(int l, int t, int r, int b)等方式限定刷新区域;
4) 通过设置一个是否允许requestLayout的变量,然后重写控件的requestlayout、 onSizeChanged
方法,判断控件的大小没有改变的情况下,当进入 requestLayout 的时候,直接返回而不调用 super 
的 requestLayout 方法。
  1. 不能在 Activity 没有完全显示时显示 PopupWindow 和 Dialog,推荐在 Activity#onAttachedToWindow()/Activity#onWindowFocusChanged() 之后创建对话框.
  2. 尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错.
  3. 不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;这样会把 ListView 的所有 Item 加载到内存中,会消耗巨大的内存和 cpu 资源,推荐使用 NestedScrollView.
  4. 不要在 Android 的 Application 对象中缓存数据。
    讨论:
1. 基础组件之间的数据共享使用 Intent 等机制
2. 使用 SharedPreferences 等数据持久化机制.
3. 使用kotlin object 单例全局对象.
  1. 使用 Toast 时,建议定义一个全局的 Toast 对象,这样可以避免连续显示 Toast 时不能取消上一次 Toast 消息的情况。即使需要连续弹出 Toast,也应避免直接调用 Toast#makeText.
  2. 使用 Adapter 的时候,如果使用 ViewHolder 做缓存,在 getView()方法中无论 convertView 的每个子控件是否需要设置属性(比如某个 TextView 设置的文本为 null,背景色为透明),都需要为其显式设置属性,否则在滑动过程中,因为 adapter item 复用的原因,会出现内容的显示错乱.

进程、线程与消息通信

  1. 不要通过 Intent 在 Android 基础组件之间传递大数据(binder transaction 缓存为 1MB),可能导致 OOM,TransactionTooLargeException等.
  2. 在 Application 的业务初始化代码加入进程判断,确保只在自己需要的进程初始化.
  3. 新建线程时,必须通过线程池提供(AsyncTask,ThreadPoolExecutor,Rxjava,kotlin 协程,或其他形式自定义线程池,不能直接使用Executors),不允许在应用中自行显式创建线程.
  4. 新建线程时,定义能识别自己业务的线程名称,便于性能优化和问题排查.
  5. ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时线程能被释放.
  6. 禁止在多进程之间用 SharedPreferences 共享数据,虽然可以(MODE_MULTI_PROCESS),但官方已不推荐。

文件与数据库

  1. 不要硬编码文件路径,使用 Android 文件系统 API 访问
android.os.Environment#getExternalStorageDirectory()
android.os.Environment#getExternalStoragePublicDirectory()
android.content.Context#getFilesDir()
android.content.Context#getCacheDir
  1. 当使用外部存储时,必须检查外部存储的可用性
// 读/写检查
public boolean isExternalStorageWritable() {
 String state = Environment.getExternalStorageState();
 if (Environment.MEDIA_MOUNTED.equals(state)) {
 return true;
 }
 return false;
}
// 只读检查
public boolean isExternalStorageReadable() {
 String state = Environment.getExternalStorageState();
 if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
 return true;
 }
 return false;
}
  1. 应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用 FileProvider.
  2. SharedPreference 中只能存储简单数据类型(int、boolean、String 等),复杂数据类型建议使用文件、数据库等其他方式存储.
  3. SharedPreference 提交数据时,尽量使用 Editor#apply() ,而非 Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用 Editor#commit(),commit 会直接读写磁盘,频繁操作性能不好.
  4. 数据库 Cursor 必须确保使用完后关闭,以免内存泄漏.
  5. 多线程操作写入数据库时,需要使用事务,以免出现同步问题.
  6. 大数据写入数据库时,使用事务或其他能够提高 I/O 效率的机制,保证执行速度.
  7. 执行 SQL 语句时,应使用 SQLiteDatabase#insert()、update()、delete(),不要使用 SQLiteDatabase#execSQL(),以免 SQL 注入风险.

Bitmap、Drawable 与动画

  1. 在 ListView,ViewPager,RecyclerView,GirdView 等组件中使用图片时,应做好图片的缓存,避免始终持有图片导致内存溢出,也避免重复创建图片,引起性能问题。建议 使 用 Fresco( https://github.com/facebook/fresco )、 Glide ( https://github.com/bumptech/glide )等图片库.
  2. png 图片使用 TinyPNG 或者类似工具压缩处理,减少包体积.
  3. 应根据实际展示需要,压缩图片,而不是直接显示原图.(Glide)
  4. 使用完毕的图片,应该及时回收,释放宝贵的内存.(Glide)
  5. 在 #onPause()或 #onStop()回调中,关闭当前正在执行的的动画.
  6. 在动画或者其他异步任务结束时,应该考虑回调时刻的环境是否还支持业务处理。例如 Activity 的 onStop()函数已经执行,且在该函数中主动释放了资源,此时回调中如果不做判断就会空指针崩溃(Kotlin ?. !!.)
  7. 使用 inBitmap 重复利用内存空间,避免重复开辟新内存(Glide 待确认)
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int
reqHeight, ImageCache cache) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
 ...
    BitmapFactory.decodeFile(filename, options);
 ...
 // 如果在 Honeycomb 或更新版本系统中运行,尝试使用 inBitmap
    if (Utils.hasHoneycomb()) {
        addInBitmapOptions(options, cache);
    }
 ...
    return BitmapFactory.decodeFile(filename, options);
}

private static void addInBitmapOptions(BitmapFactory.Options options,
 ImageCache cache) {
    // inBitmap 只处理可变的位图,所以强制返回可变的位图
    options.inMutable = true;
    if (cache != null) {
        Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
        if (inBitmap != null) {
            options.inBitmap = inBitmap;
        }
    }
}
  1. 使用 RGB_565 代替 RGB_888,在不怎么降低视觉效果的前提下,减少内存占用.(Glide).
  2. 尽量减少 Bitmap(BitmapDrawable)的使用,尽量使用纯色(ColorDrawable)、渐变色(GradientDrawable)、StateSelector(StateListDrawable)等与 Shape 结合的形式构建绘图.
  3. 谨慎使用 gif 图片,注意限制每个页面允许同时播放的 gif 图片,以及单个 gif 图片的大小.
  4. 非首次加载必须的大图片资源不要直接打包到 apk,可以考虑通过文件仓库远程下载,减小包体积.
  5. 在有强依赖 onAnimationEnd 回调的交互时,如动画播放完毕才能操作页面 , onAnimationEnd 可能会因各种异常没被回调 (参考:https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-called-onanimationstart-works-fine ),建议加上超时保护或通过 postDelay 替代 onAnimationEnd.
  6. View Animation 执行结束时,调用 View.clearAnimation()释放相关资源.

安全

  1. 将 android:allowbackup 属性必须设置为 false,阻止应用数据被导出.
<application
 android:allowBackup="false">
  1. 如果使用自定义 HostnameVerifier 实现类,必须在 verify()方法中校验服务器主机名的合法性,否则可能受到中间人攻击.
  2. 如果使用自定义 X509TrustManager 实现类,必须在 checkServerTrusted() 方法中校验服务端证书的合法性,否则可能受到中间人攻击.
  3. 在 SDK 支持的情况下,Android 应用必须使用 V2 签名,这将对 APK 文件的修改做更多的保护(默认已开启,不能手动去关闭)
  4. 所有的 Android 基本组件(Activity、Service、BroadcastReceiver、阿里巴巴 Android 开发手册ContentProvider 等)都不应在没有严格权限控制的情况下,将 android:exported 设置为 true.
  5. WebView 应设置
WebView#getSettings()#setAllowFileAccess(false)、WebView#getSettings()#setAllowFileAccessFromFileURLs(false)、WebView#getSettings()#setAllowUniversalAccessFromFileURLs(false)

,阻止 filescheme URL 的访问.

  1. 不要把敏感信息打印到 log 中.
  2. 确保应用发布版本的 android:debuggable 属性设置为 false.
  3. 本地加密秘钥不能硬编码在代码中,更不能使用 SharedPreferences 等本地持久化机制存储。应选择 Android 自身的秘钥库(KeyStore)机制或者其他安全性更高的安全解决方案保存。
  4. 使用 Android 的 AES/DES/DESede 加密算法时,不要使用 ECB 加密模式,应使用 CBC 或 CFB 加密模式.
  5. Android APP 在 HTTPS 通信中,验证策略需要改成严格模式
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
  1. zip 中不要包含 ../../file 这样的路径,可能被篡改目录结构,造成攻击。
//对重要的 Zip 压缩包文件进行数字签名校验,校验通过才进行解压
String entryName = entry.getName();
if (entryName.contains("..")){
    throw new Exception("unsecurity zipfile!");
}
  1. MD5 和 SHA-1、SHA-256 等常用算法是 Hash 算法,有一定的安全性,但不能代替加密算法。敏感信息的存储和传输,需要使用专业的加密机制.

其他

  1. 不能使用 System.out.println 打印 log.
  2. Log 的 tag 不能是" ".

参考:

  1. 《阿里巴巴Android开发手册》v1.0.1更新,优化部分内容和示例代码
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容

  • 初衷: 开发 Android 更加高效、高质量地进行 App 开发,呈现给用户体验好、性能优、稳定性佳、安全性高的...
    枫羽望空阅读 3,026评论 0 64
  • Android开发规范 一、命名规范 JAVA类和变量命名 1.命名使用英文单词拼接,驼峰命名法,不可使用拼音 2...
    前锋_261e阅读 870评论 0 0
  • Intent 是一个消息传递对象,您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件...
    牧童遥指2000阅读 5,046评论 0 12
  • 0.Android手机操作系统的四层架构? Applications , Application Framewor...
    lucas777阅读 7,802评论 0 16
  • 摘要 1 前言 2 AS 规范 3 命名规范 4 代码样式规范 5 资源文件规范 6 版本统一规范 7 第三方库规...
    浪够_阅读 1,059评论 0 3