美文网首页
Android 开发规范

Android 开发规范

作者: CH_DHY | 来源:发表于2019-01-24 10:07 被阅读12次

    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更新,优化部分内容和示例代码

    相关文章

      网友评论

          本文标题:Android 开发规范

          本文链接:https://www.haomeiwen.com/subject/gnfyjqtx.html