移动Android开发-规范文档

移动Android规范文档

背景:为了统一成都xxx有限公司移动部门的开发规范,特出此文,勿盲目借鉴.

本手册以开发者为中心视角分为 Android 资源文件命名与使用,Android 基本组件,UI与布局,进程、线程与消息通信, 文件与数据库,BitmapDrawable与动画,安全,其他等九大部分,根据约束力强弱, 规约依次分为强制、推荐、参考三大类:

  • 强制】必须遵守,违反本约定或将会引起严重的后果;
  • 推荐】尽量遵守,长期遵守有助于系统稳定性和合作效率的提升;
  • 参考】充分理解,技术意识的引导,是个人学习、团队沟通、项目合作的方 向。

目标是

  • 防患未然,提升质量意识,降低故障率和维护成本;
  • 标准统一,提升协作效率;
  • 追求卓越的工匠精神,打磨精品代码。

注释

强制】类注释

每个类必须严格意义上按照功能注释使用
推荐规则:

/**
*Describe:朝拜时间闹钟界面
*
*Created by zhigang wei
*on 2018/4/18
*
*Company :cpx
*/

强制】方法注释

不是每个一方法都需要注释,但是核心方法必须要注释

  /**
     * 设置数据
     *
     * @param object 设置的数据源
     */
    public void setData(Object... object) {

    }

推荐】属性注释

关键属性需要注释,推荐使用第一种

/*倒计时-同时也是判断是否是第一次进入*/
private var clockS: Long = 0x111L

或者

    /**
     * 倒计时-同时也是判断是否是第一次进入
     */
    private var clockS: Long = 0x111L

推荐】功能结构注释

如果一个功能类过于复杂,前期在满足开发任务的前提下需要记录后续优化的注释,后续需及时优化这块逻辑

 /*start------------------倒计时---------------------------*/
	xxx
	xxx
	xxx
  /*end------------------倒计时---------------------------*/

强制README文档注释

每一个独立的module都需要有各自的README文档,介绍项目架构,逻辑处理,核心算法,每一次版本更新到具体内容等

具体格式查看标准示例

Android 资源文件命名与使用

1. 【强制】资源文件需带模块前缀解耦。

在这里插入图片描述

2. 【强制layout 文件的命名方式。
  • Activitylayoutactivity_ 开头
  • Fragmentlayoutfragment_ 开头
  • Dialoglayoutdialog_ 开头
  • includelayoutinclude_ 开头
  • 自定义viewlayoutview_开头
  • ListView 的行 layoutlist_item 开头
  • RecyclerViewitem layoutrecycle_item开头 ;headrecycle_item_head_开头,footerrecycle_item_footer_开头
  • GridView 的行 layoutgrid_item 开头
3.【推荐】Id 资源原则上以驼峰法命名,View 组件的资源 id 需要以 View 的缩写作为 前缀。常用缩写表如下
控件 缩写
LinearLayout ll
RelativeLayout rl
ConstraintLayout cl
ScollView sv
RecyclerView rv
Button btn
ImageView iv
CheckBox cb
RadioButton rb
EditText et
TextView tv

类推其它控件的缩写推荐使用小写字母并用下划线进行分割

但是kotlin使用不需要findview所以也可以采用驼峰命名,个人推荐还是下划线来区分xml

4.【强制】 color 资源使用#AARRGGBB 格式,写入baselib 中colors.xml 文件中
<color name="c_7F000000">#7F000000</color>
5.[强制] 资源文件
  • 自定义点击selector_,自定义点击文字selector_tv,自定义图形shape_统一放入都drawable

  • 点九图都统一放入drawable-xxhdpi

  • 图片分别放入mipmap对应的资源中

  • 其他大文件资源统一放入assets对应类型的文件夹中:例如
    在这里插入图片描述

Android 基本组件

Android 基本组件指 ActivityFragmentServiceBroadcastReceiverContentProvider 等等。

推荐】2018/11/6 补1.constraint-layout

项目中需要定义xml布局文件,根部局推荐使用ConstraintLayout
推荐原因请查看链接

1. 【强制Activity 间的数据通信,对于数据量比较大的,避免使用 Intent + Parcelable的方式,可以考虑 RxBus 等替代方案,以免造成 TransactionTooLargeException
2. 【推荐Activity#onSaveInstanceState()方法不是 Activity 生命周期方法,也不保证 一定会被调用。它是用来在 Activity 被意外销毁时保存 UI 状态的,只能用于保存临 时性数据,例如 UI 控件的属性等,不能跟数据的持久化存储混为一谈。持久化存储 应该在 Activity#onPause()/onStop()中实行。
3.【强制Activity 间通过隐式 Intent的跳转,在发出 Intent之前必须通过 resolveActivity 检查,避免找不到合适的调用组件,造成 ActivityNotFoundException 的异常。

正例:

 public void viewUrl(String url, String mimeType)
    {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.parse(url), mimeType);
        if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ ONLY) != null) {
            try {
                startActivity(intent);
            } catch (ActivityNotFoundException e) {
                if (Config.LOGD) {
                    Log.d(LOGTAG, "activity not found for " + mimeType + " over " + Uri.parse(url).getScheme(), e);
                }
            }
        }
    }

反例:

Intent intent = new Intent(); 
intent.setAction("com.great.activity_intent.Intent_Demo1_Result3");
4.【强制】避免在Service#onStartCommand()/onBind()方法中执行耗时操作,如果确 实有需求,应改用IntentService或采用其他异步机制完成。
5.【强制】避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作, 应该创建IntentService完成,而不应该在 BroadcastReceiver 内创建子线程去做。

说明:
由于该方法是在主线程执行,如果执行耗时操作会导致 UI不流畅。可以使用 IntentService 、 创 建 HandlerThread 或 者 调 用Context#registerReceiver (BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他 Wroker 线程执行onReceive方法。BroadcastReceiver#onReceive()方法耗时超过 10 秒钟,可能会被系统杀死

6.【强制】避免使用隐式Intent广播敏感信息,信息可能被其他注册了对应 BroadcastReceiverApp接收。

说明:
通过 Context#sendBroadcast()发送的隐式广播会被所有感兴趣的 receiver接收,恶意应用注册监听该广播的receiver可能会获取到Intent中传递的敏感信息,并进行 其他危险操作。如果发送的广播为使Context#sendOrderedBroadcast()方法发送
的有序广播,优先级较高的恶意 receiver 可能直接丢弃该广播,造成服务不可用,或者向广播结果塞入恶意数据。如果广播仅限于应用内,则可以使用 LocalBroadcastManager#sendBroadcast()实现,避免敏感信息外泄和Intent拦截的风险

正例:

Intent intent = new Intent("my-sensitive-event"); 
intent.putExtra("event", "this is a test event");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
 

反例:


Intent intent = new Intent(); 
v1.setAction("com.sample.action.server_running"); 
v1.putExtra("local_ip", v0.h);
context.sendBroadcast(v1);
7. 【推荐 】 添 加 Fragment时 , 确 保 FragmentTransaction#commit()Activity#onPostResume()或者 FragmentActivity#onResumeFragments()内调用。 不要随意使用 FragmentTransaction#commitAllowingStateLoss()来代替,任何 commitAllowingStateLoss()的使用必须经过 code review,确保无负面影响。

说明:
Activity 可能因为各种原因被销毁,Android 支持页面被销毁前通过 Activity#onSaveInstanceState() 保 存 自 己 的 状 态 。 但 如 果
FragmentTransaction.commit()发生在 Activity状态保存之后,就会导致 Activity 重 建、恢复状态时无法还原页面状态,从而可能出错。为了避免给用户造成不好的体验,系统会抛出 IllegalStateExceptionStateLoss 异常。推荐的做法是在Activity 的onPostResume()onResumeFragments()FragmentActivity 里 执 行 FragmentTransaction.commit(),如有必要也可在 onCreate()里执行。不要随意改用FragmentTransaction.commitAllowingStateLoss()或者直接使用 try-catch 避免 crash,这不是问题的根本解决之道,当且仅当你确认Activity重建、恢复状态时,本次 commit 丢失不会造成影响时才可这么做。

8. 【推荐】不要在 Activity#onDestroy()内执行释放资源的工作,例如一些工作线程的 销毁和停止,因为 onDestroy()执行的时机可能较晚。可根据实际需要,在 Activity#onPause()/onStop()中结合isFinishing()的判断来执行。
9. 【推荐】如非必须,避免使用嵌套的 Fragment。可用自定义view替代

Fragment其他注意问题-踩坑纪录点击查看

说明:
嵌套 Fragment 是在 Android API 17 添加到 SDK 以及 Support 库中的功能,
Fragment 嵌套使用会有一些坑,容易出现 bug,比较常见的问题有如下几种:

  1. onActivityResult()方法的处理错乱,内嵌的 Fragment 可能收不到该方法的回调,需要由宿主 Fragment 进行转发处理;
  2. 突变动画效果;
  3. 被继承的 setRetainInstance(),导致在 Fragment 重建时多次触发不必要的逻辑。
10【推荐】总是使用显式Intent启动或者绑定Service,且不要为服务声明IntentFilter, 保证应用的安全性。如果确实需要使用隐式调用,则可为 Service 提供 Intent Filter 并从 Intent 中排除相应的组件名称,但必须搭配使用 Intent#setPackage()方法设置 Intent 的指定包名,这样可以充分消除目标服务的不确定性。
11.【推荐】Service 需要以多线程来并发处理多个启动请求,建议使用 IntentService, 可避免各种复杂的设置。
12.【推荐】对于只用于应用内的广播,优先使用LocalBroadcastManager 来进行注册 和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率

正例:
对于使用 Context#sendBroadcast()等方法发送全局广播的代码进行提示。如果该广播仅用于应用内,则可以使用 LocalBroadcastManager 来避免广播泄漏以及广播被拦截等安全问题,同时相对全局广播本地广播的更高效。

13【推荐】当前 Activity 的 onPause 方法执行结束后才会执行下一个 Activity 的 onCreate 方法,所以在 onPause 方法中不适合做耗时较长的工作,这会影响到页面之间的跳 转效率。
14.【强制】不要在 Android 的 Application 对象中缓存数据。基础组件之间的数据共享 请使用 Intent 等机制,也可使用 SharedPreferences 等数据持久化机制,大量数据可以采用数据库持久化方案。
15.【推荐】使用 Toast 时,建议定义一个全局的 Toast 对象,这样可以避免连续显示 Toast 时不能取消上一次 Toast 消息的情况(如果你有连续弹出 Toast 的情况,避免 使用 Toast.makeText)。
16.【强制】使用 Adapter 的时候,如果你使用了 ViewHolder 做缓存,在 getView()的 方法中无论这项 convertView 的每个子控件是否需要设置属性(比如某个 TextView 设置的文本可能为 null,某个按钮的背景色为透明,某控件的颜色为透明等),都需 要为其显式设置属性(Textview 的文本为空也需要设置 setText(""),背景透明也需要 设置),否则在滑动的过程中,因为 adapter item 复用的原因,会出现内容的显示错 乱。
17【强制】Activity 或者 Fragment 中动态注册 BroadCastReceiver 时,registerReceiver() 和 unregisterReceiver()要成对出现。
18 【强制】Fragment和viewpager共用需要采用FragmentStatePagerAdapter动态加载Fragment的方案

UI 与布局

1. 【强制】布局中不得不使用 ViewGroup 多重嵌套时,不要使用 LinearLayout 嵌套, 改用 ConstraintLayout或者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 的流畅。要找到那些多余的 View(增加渲染延迟的 view),可以用 Android Studio Monitor里的 Hierarachy Viewer 工具,可视化的查看所有的 view。

多重嵌套导致 measure 以及 layout 等步骤耗时过多。 扩展参考:

2. 【推荐】在 Activity 中显示对话框或弹出浮层时,尽量使用 DialogFragment,而非 Dialog/AlertDialog,这样便于随 Activity 生命周期管理对话框/弹出浮层的生命周期。
3. 【推荐】源文件统一采用 UTF-8 的形式进行编码。
4. 【强制】禁止在非 ui 线程进行 view 相关操作。
5. 【推荐】文本大小使用单位 sp,view 大小使用单位 dp。对于 Textview,如果在文 字大小确定的情况下推荐使用 wrap_content 布局避免出现文字显示不全的适配问 题。
6. 【强制】禁止在设计布局时多次设置子 view 和父 view 中为同样的背景造成页面过 度绘制,推荐将不需要显示的布局进行及时隐藏。
7. 【推荐】灵活使用布局,推荐 Merge、ViewStub 来优化布局,尽可能多的减少 UI 布局层级,推荐使用 ConstraintLayout,FrameLayout,LinearLayout、RelativeLayout 次之。
8. 【推荐】在需要时刻刷新某一区域的组件时,建议通过以下方式避免引发全局 layout 刷新:
  1. 设置固定的 view 大小的高宽,如倒计时组件等;
  2. 调用 view 的 layout 方式修改位置,如弹幕组件等; 3)
  3. 通过设置一个是否允许 requestLayout 的变量,然后重写控件的 requestlayout、
9. 【推荐】不能在 Activity 没有完全显示时显示 PopupWindow 和 Dialog。
10.【推荐】尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错。

说明:

正例:
图片数量较少的 AnimationDrawable 还是可以接受的。Android 的帧动画可以使用 AnimationDrawable 实现,但是如果你的帧动画中如果
包含过多帧图片,一次性加载所有帧图片所导致的内存消耗会使低端机发生 OOM 异常。帧动画所使用的图片要注意降低内存消耗,当图片比较大时,容易出现 OOM。

11.【强制】不能使用 ScrollView 包裹ListView/GridView/ExpandableListVIew;因为这 样会把 ListView 的所有 Item 都加载到内存中,要消耗巨大的内存和 cpu 去绘制图 面。

正例:
ScrollView 中嵌套 List 或 RecyclerView 的做法官方明确禁止。除了开发过程中遇到的各种视觉和交互问题,这种做法对性能也有较大损耗。ListView 等 UI 组件自身有垂直滚动功能,也没有必要在嵌套一层 ScrollView。目前为了较好的 UI 体验,更贴近 Material Design 的设计,推荐使用 NestedScrollView。

进程、线程与消息通信

1. 【强制】不要通过 Intent 在 Android 基础组件之间传递大数据(binder transaction 缓存为 1MB),可能导致 OOM。
2. 【强制】在 Application 的业务初始化代码加入进程判断,确保只在自己需要的进程 初始化。特别是后台进程减少不必要的业务初始化。
3.【强制】多线程/进程 统一使用RxJava处理

RxJava的一些学习资源:

4. 【强制】子线程中不能更新界面,更新界面必须在主线程中进行,网络操作不能在 主线程中调用。
5. 【强制】不要在非 UI 线程中初始化 ViewStub,否则会返回 null。
6.【推荐】禁止在多进程之间用 SharedPreferences 共享数据,虽然可以 (MODE_MULTI_PROCESS),但官方已不推荐。
7.【推荐】谨慎使用 Android 的多进程,多进程虽然能够降低主进程的内存压力,但会 遇到如下问题:
  • 不能实现完全退出所有 Activity 的功能;
  • 首次进入新启动进程的页面时会有延时的现象(有可能黑屏、白屏几秒,是白
    屏还是黑屏和新 Activity 的主题有关
  • 应用多进程时,Application实例化多次,需要考虑各个模块是否需要所在所有进程中初始化
  • 多进程间通过 SharedPreferences 共享数据时不稳定。

文件与数据库

1. 【强制】任何时候不要硬编码文件路径,请使用 Android 文件系统 API 访问。

说明:
Android 应用提供内部和外部存储,分别用于存放应用自身数据以及应用产生的用 户数据。可以通过相关 API 接口获取对应的目录,进行文件操作。

android.os.Environment#getExternalStorageDirectory() 
android.os.Environment#getExternalStoragePublicDirectory() 
android.content.Context#getFilesDir() 
android.content.Context#getCacheDir

正例:

  public File getDir(String alName)
    {
        File file = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), alName); if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created"); }
        return file;
    }

反例:

    public File getDir(String alName)
    {
// 任何时候都不要硬编码文件路径,这不仅存在安全隐患,也让 app 更容易出现适配问题 File file = new File("/mnt/sdcard/Download/Album", alName);
        if (!file.mkdirs()) {
            Log.e(LOG_TAG, "Directory not created"); }
        return file;
    }
2. 【强制】当使用外部存储时,必须检查外部存储的可用性。

正例:

 // 读/写检查
    fun isExternalStorageWritable(): Boolean {
        val state = Environment.getExternalStorageState()
        return if (Environment.MEDIA_MOUNTED == state) {
            true
        } else false
    }

    // 只读检查
    fun isExternalStorageReadable(): Boolean {
        val state = Environment.getExternalStorageState()
        return if (Environment.MEDIA_MOUNTED == state || Environment.MEDIA_MOUNTED_READ_ONLY == state) {
            true
        } else false
    }

3. 【强制】应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用 FileProvider。

正例:

<!-- AndroidManifest.xml -->

 <manifest>...
        <application>...
            <provider
                android:name="android.support.v4.content.FileProvider"
                android:authorities="com.example.fileprovider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/provider_paths"/>
            </provider>
            ...
        </application>
    </manifest>
    <!-- res/xml/provider_paths.xml -->
    <paths>
        <files-path
            name="myimages"
            path="album/"/>
    </paths>
    void getAlbumImage(String imagePath) {
    File image = new File(imagePath);
    Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Uri imageUri = FileProvider.getUriForFile(
    this, "com.example.provider", image);
    getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    - 36 -
    startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE); }

反例:


    void getAlbumImage(String imagePath)
    {
        File image = new File(imagePath);
        Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 不要使用 file://的 URI 分享文件给别的应用,包括但不限于 Intent
        getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(image));
        startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE);
    }
4. 【推荐】SharedPreference 中只能存储简单数据类型(int、boolean、String 等), 复杂数据类型建议使用文件、数据库等其他方式存储。

正例

    public void updateSettings()
    {
        SharedPreferences mySharedPreferences = getSharedPreferences ("settings", Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = mySharedPreferences . edit (); editor.putString("id", "foo");
        editor.putString("nick", "bar");
//不要把复杂数据类型转成 String 存储
        editor.apply(); 
    }
5. 【推荐】SharedPreference 提交数据时,尽量使用 Editor#apply(),而非 Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使 用 Editor#commit()。

说明:

SharedPreference 相关修改使用 apply 方法进行提交会先写入内存,然后异步写入 磁盘,commit 方法是直接写入磁盘。如果频繁操作的话 apply 的性能会优于 commit, apply 会将最后修改内容写入磁盘。但是如果希望立刻获取存储操作的结果,并据此做相应的其他操作,应当使用 commit。

正例:


    public void updateSettingsAsync()
    {
        SharedPreferences mySharedPreferences = getSharedPreferences ("settings", Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = mySharedPreferences . edit ();
        editor.putString("id", "foo");
        editor.apply();
    }
    public void updateSettings()
    {
        SharedPreferences mySharedPreferences = getSharedPreferences ("settings", Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = mySharedPreferences . edit (); editor.putString("id", "foo");
        if (!editor.commit()) {
            Log.e(LOG_TAG, "Failed to commit setting changes"); }
    }

反例:

editor.putLong("key_name", "long value");
editor.commit();

6. 【强制】数据库 Cursor 必须确保使用完后关闭,以免内存泄漏。开源框架推荐使用GreenDao3.0以后当版本
7. 【强制】多线程操作写入数据库时,需要使用事务,以免出现同步问题。

说明:
Android 的通过 SQLiteOpenHelper 获取数据库 SQLiteDatabase 实例,Helper 中会 自动缓存已经打开的 SQLiteDatabase 实例,单个 App 中应使用 SQLiteOpenHelper的单例模式确保数据库连接唯一。由于 SQLite 自身是数据库级锁,单个数据库操作是保证线程安全的(不能同时写入),transaction 时一次原子操作,因此处于事务中的操作是线程安全的。若同时打开多个数据库连接,并通过多线程写入数据库,会导致数据库异常,提示数据库已被锁住。

正例:

 public void insertUserPhoto(SQLiteDatabase db, String userId, String content)
    {
        ContentValues cv = new ContentValues();
        cv.put("userId", userId);
        cv.put("content", content);
        db.beginTransaction(); try {
        db.insert(TUserPhoto, null, cv); // 其他操作
        db.setTransactionSuccessful(); } catch (Exception e) {
        db.endTransaction(); }
    }

反例:

public void insertUserPhoto(SQLiteDatabase db, String userId, String content) { ContentValues cv = new ContentValues();
cv.put("userId", userId);
cv.put("content", content);
db.insert(TUserPhoto, null, cv); }
8. 【推荐】大数据写入数据库时,请使用事务或其他能够提高 I/O 效率的机制,保证执行速度。
9.【强制】如果 ContentProvider 管理的数据存储在 SQL 数据库中,应该避免将不受 信任的外部数据直接拼接在原始 SQL 语句中,可使用一个用于将 ? 作为可替换参 数的选择子句以及一个单独的选择参数数组,会避免 SQL 注入。

正例:

// 使用一个可替换参数
String mSelectionClause = "var = ?"; 
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;

反例:

// 拼接用户输入内容和列名
String mSelectionClause = "var = " + mUserInput;

Bitmap、Drawable 与动画

1. 【强制】加载大图片或者一次性加载多张图片,应该在异步线程中进行。图片的加 载,涉及到 IO 操作,以及 CPU 密集操作,很可能引起卡顿。
2. 【强制】在 ListView,ViewPager,RecyclerView,GirdView 等组件中使用图片时, 应做好图片的缓存,避免始终持有图片导致内存泄露,也避免重复创建图片,引起 性 能 问 题 。 建 议 使 用 Glide (https://github.com/bumptech/glide)等图片库。
3. 【强制】png 图片使用 tinypng 或者类似工具压缩处理,减少包体积。
4. 【推荐】应根据实际展示需要,压缩图片,而不是直接显示原图。手机屏幕比较小,直接显示原图,并不会增加视觉上的收益,但是却会耗费大量宝贵的内存。

正例:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
// 首先通过 inJustDecodeBounds=true 获得图片的尺寸
final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options);
// 然后根据图片分辨率以及我们实际需要展示的大小,计算压缩率 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 设置压缩率,并解码
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}

反例:

不经压缩显示原图。

5. 【强制】使用完毕的图片,应该及时回收,释放宝贵的内存

正例:

  Bitmap bitmap = null;
    loadBitmapAsync(new OnResult(result)
    {
        bitmap = result; });
    ...使用该 bitmap...
// 使用结束,在 2.3.3 及以下需要调用 recycle()函数,在 2.3.3 以上 GC 会自动管理,除非你明
    确不需要再用。
    if (Build.VERSION.SDK_INT <= 10)
    {
        bitmap.recycle();
    }
    bitmap = null;
6. 【推荐】针对不同的屏幕密度,提供对应的图片资源,使内存占用和显示效果达到 合理的平衡。如果为了节省包体积,可以在不影响 UI 效果的前提下,省略低密度图 片。
7. 【强制】在 Activity.onPause()或 Activity.onStop()回调中,关闭当前 activity 正在执 行的的动画。
8. 【推荐】在动画或者其他异步任务结束时,应该考虑回调时刻的环境是否还支持业 务处理。例如 Activity 的 onStop()函数已经执行,且在该函数中主动释放了资源, 此时回调中如果不做判断就会空指针崩溃。
 public class MyActivity extends Activity
    {
        private ImageView mImageView; private Animation mAnimation;
        /** 首次创建 activity 时调用 */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState); setContentView(R.layout.main);
            mImageView = (ImageView) findViewById (R.id.ImageView01);
            mAnimation = AnimationUtils.loadAnimation(this, R.anim.anim); mAnimation.setAnimationListener(new AnimationListener () {
            @Override
            public void onAnimationEnd(Animation arg0) {
                //判断一下资源是否被释放了 if (mImageView != null) {
                mImageView.clearAnimation();
            }
        });
            mImageView.startAnimation(mAnimation);
        }
    }

反例:

动画结束回调中,直接使用资源不加判断,导致异常。

9. 【推荐】使用 inBitmap 重复利用内存空间,避免重复开辟新内存
 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; }
    } 
    }
10.【推荐】使用 ARGB_565 代替 ARGB_888,在不怎么降低视觉效果的前提下,减少 内存占用。

说明:
android.graphics.Bitmap.Config 类中关于图片颜色的存储方式定义:

  • ALPHA_8 代表 8 位 Alpha 位图;
  • ARGB_4444 代表 16 位 ARGB 位图;
  • ARGB_8888 代表 32 位 ARGB 位图;
  • RGB_565 代表 8 位 RGB 位图。

位图位数越高,存储的颜色信息越多,图像也就越逼真。大多数场景使用的是 ARGB_8888 和 RGB_565,RGB_565 能够在保证图片质量的情况下大大减少内存的开销,是解决 oom 的一种方法。

但是一定要注意 RGB_565 是没有透明度的,如果图片本身需要保留透明度,那么就不能使用 RGB_565。

正例:

Config config = drawableSave.getOpacity() != PixelFormat.OPAQUE ? 
Config.ARGB_8888 : Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(w, h, config);

反例:


Bitmap newb = Bitmap.createBitmap(width, height, Config.ARGB_8888);
11【推荐】尽量减少Bitmap(BitmapDrawable)的使用,尽量使用纯色(ColorDrawable)、 渐变色(GradientDrawable)、StateSelector(StateListDrawable)等与 Shape 结 合的形式构建绘图。
12.【推荐】谨慎使用 gif 图片,注意限制每个页面允许同时播放的 gif 图片,以及单个 gif 图片的大小。
13.【参考】大图片资源不要直接打包到 apk,可以考虑通过文件仓库远程下载,减小包 体积。
14.【推荐】根据设备性能,选择性开启复杂动画,以实现一个整体较优的性能和体验;
15.【推荐】在有强依赖 onAnimationEnd 回调的交互时,如动画播放完毕才能操作页面,onAnimationEnd 可能会因各种异常没被回调参考连接, 建 议 加 上 超 时 保 护 或 通 过 postDelay 替 代onAnimationEnd。

正例:


    View v = findViewById(R.id.xxxViewID);
    final FadeUpAnimation anim = new FadeUpAnimation(v); anim.setInterpolator(new AccelerateInterpolator()); anim.setDuration(1000);
    anim.setFillAfter(true);
    new Handler().postDelayed(new Runnable()
    {
        public void run() {
            if (v != null) {
                v.clearAnimation(); }
        }
    }, anim.getDuration());
    v.startAnimation(anim);
16.【推荐】当 View Animation 执行结束时,调用 View.clearAnimation()释放相关资源。

正例:

    View v = findViewById(R.id.xxxViewID);
    final FadeUpAnimation anim = new FadeUpAnimation(v); anim.setInterpolator(new AccelerateInterpolator());
    anim.setDuration(1000);
    anim.setFillAfter(true); anim.setAnimationListener(new AnimationListener()
    {
        @Override
        public void onAnimationEnd(Animation arg0) {
        }
    });
    v.startAnimation(anim);
//判断一下资源是否被释放了 if (v != null) {
    v.clearAnimation();
    }

安全

1. 【强制】使用 PendingIntent 时,禁止使用空 intent,同时禁止使用隐式 Intent

说明:

  • 使用 PendingIntent 时,使用了空 Intent,会导致恶意用户劫持修改 Intent 的内 容。禁止使用一个空 Intent 去构造 PendingIntent,构造 PendingIntent 的 Intent一定要设置 ComponentName 或者 action。
  • PendingIntent 可以让其他 APP 中的代码像是运行自己 APP 中。PendingIntent 的 intent 接收方在使用该 intent 时与发送方有相同的权限。在使用 PendingIntent时,PendingIntent 中包装的 intent 如果是隐式的 Intent,容易遭到劫持,导致信息泄露。

正例:

 Intent intent = new Intent(this, SomeActivity.
    class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_ UPDATE_CURRENT);
    try
    {
        pendingIntent.send();
    } catch (PendingIntent.CanceledException e)
    {
        e.printStackTrace();
    }

反例 1:

 Bundle addAccountOptions = new Bundle();
    mPendingIntent = PendingTntent.getBroadcast(this, 0, new Intent, 0);
    addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
    addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
    AccountManager.get(this).addAccount( accountType,null,null, addAccountOptions, null,mCallback,null);

反例 2:
mPendingIntent 是通过 new Intent()构造原始 Intent 的,所以为“双无”Intent,这个 PendingIntent 最终被通过 AccountManager.addAccount 方法传递给了恶意 APP 接口

    Intent intent = new Intent("com.test.test.pushservice.action.METHOD");
    intent.addFlags(32);
    intent.putExtra("app",
    PendingIntent.getBroadcast(this, 0, intent, 0));

如上代码 PendingIntent.getBroadcast,PendingItent 中包含的 Intent 为隐式 intent,因此当 PendingIntent 触发执行时,发送的 intent 很可能被嗅探或者劫持,导致 intent内容泄漏。

2. 【强制】禁止使用常量初始化矢量参数构建 IvParameterSpec,建议 IV 通过随机方 式产生。

说明:

使用固定初始化向量,结果密码文本可预测性会高得多,容易受到字典式攻击。iv的作用主要是用于产生密文的第一个 block,以使最终生成的密文产生差异(明文相 同的情况下),使密码攻击变得更为困难,除此之外 iv 并无其它用途。因此 iv 通过随机方式产生是一种十分简便、有效的途径。

正例:


        byte[] rand = new byte[16];
        SecureRandom r = new SecureRandom(); r.nextBytes(rand);
        IvParameterSpec iv = new IvParameterSpec(rand);

反例:

 IvParameterSpec iv_ = new IvParameterSpec("1234567890".getBytes());
 System.out.println(iv_.getIV());

3.【强制】META-INF 目录中不能包含如.apk,.odex,.so 等敏感文件,该文件夹没有经 过签名,容易被恶意替换。
4. 【强制】Receiver/Provider 不能在毫无权限控制的情况下,将 android:export 设置 为 true。
5. 【参考】数据存储在 Sqlite 或者轻量级存储需要对数据进行加密,取出来的时候进 行解密。
6. 【强制】阻止 webview 通过 file:schema 方式访问本地敏感数据。
7.【强制】不要广播敏感信息,只能在本应用使用 LocalBroadcast,避免被别的应用收到,或者 setPackage 做限制。
8.【强制】不要把敏感信息打印到 log 中。测试完成以后即使的删除log信息
9.【强制】应用发布前确保 android:debuggable 属性设置为 false。
10.【强制】使用 Intent Scheme URL 需要做过滤。

说明:
如果浏览器支持 Intent Scheme Uri 语法,如果过滤不当,那么恶意用户可能通过浏
览器 js 代码进行一些恶意行为,比如盗取 cookie 等。如果使用了 Intent.parseUri 函数,获取的 intent 必须严格过滤,intent 至少包含
addCategory(“android.intent.category.BROWSABLE”) ,setComponent(null) ,setSelector(null)3 个策略。

正例:

  // 将 intent scheme URL 转换为 intent 对象
        Intent intent = Intent.parseUri(uri);
// 禁止没有 BROWSABLE category 的情况下启动 activity intent.addCategory("android.intent.category.BROWSABLE"); intent.setComponent(null);
        intent.setSelector(null);
// 使用 intent 启动 activity context.startActivityIfNeeded(intent, -1)

反例:

   Intent intent = Intent.parseUri(uri.toString().trim().substring(15), 0);
        intent.addCategory("android.intent.category.BROWSABLE");
        context.startActivity(intent);

11.【强制】不要使用 loopback 来通信敏感信息。
12.【推荐】对于不需要使用 File 协议的应用,禁用 File 协议,显式设置 webView. getSettings().setAllowFileAccess(false),对于需要使用 File 协议的应用,禁止 File 协议调用 JavaScript,显式设置 webView.getSettings().setJavaScriptEnabled(false)。
13【强制】AndroidAPP在HTTPS通信中,验证策略需要改成严格模式。说明:Android APP 在 HTTPS 通信中,使用 ALLOW_ALL_HOSTNAME_VERIFIER,表示允许和 所有的 HOST 建立 SSL 通信,这会存在中间人攻击的风险,最终导致敏感信息可能 会被劫持,以及其他形式的攻击。
14.【推荐】zip 中不建议允许…/…/file 这样的路径,可能被篡改目录结构,造成攻击。 说 明:当 zip 压缩包中允许存在"…/“的字符串,攻击者可以利用多个”…/"在解压时改变 zip 文件存放的位置,当文件已经存在是就会进行覆盖,如果覆盖掉的文件是 so、 dex 或者 odex 文件,就有可能造成严重的安全问题。

正例:
对路径进行判断,存在"…"时抛出异常。

    //对重要的 Zip 压缩包文件进行数字签名校验,校验通过才进行解压 String entryName = entry.getName();
        if (entryName.contains("..")) {
            throw new Exception("unsecurity zipfile!");
        }

反例:


        BufferedOutputStream dest = null;
        try {
            ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream
                    ("/Users/yunmogong/Documents/test/test.zip")));
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                int count;
                byte data[] = new byte[BUFFER];
                String entryName = entry.getName();

            }
            FileOutputStream fos = new FileOutputStream(entryName); //System.out.println("Extracting:" + entry);
            dest = new BufferedOutputStream(fos, BUFFER);
            while ((count = zis.read(data, 0, BUFFER)) != -1) {
                dest.write(data, 0, count);
            }
            dest.flush();
        } catch (
                IOException e)

        {
            e.printStackTrace();
        } finally

        {
            try {
                dest.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

就会造成目录的遍历问题,一旦遭到中间人攻击替换下载的文件,将会导致某些恶意文件被执行。

15.【强制】开放的 activity/service/receiver 等需要对传入的 intent 做合法性校验。
16.【推荐】加密算法:使用不安全的 Hash 算法(MD5/SHA-1)加密信息,存在被破解的风险,建议使用 SHA-256 等安全性更高的 Hash 算法。
17.【推荐】Android WebView 组件加载网页发生证书认证错误时,采用默认的处理方法handler.cancel(),停止加载问题页面。

说明:
Android WebView 组件加载网页发生证书认证错误时,会调用 WebViewClient 类的 onReceivedSslError 方法,如果该方法实现调用了 handler.proceed()来忽略该证书,错误,则会受到中间人攻击的威胁,可能导致隐私泄露.

反例:

 mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.addJavascriptInterface(new JsBridge(mContext), JS_OBJECT);
        mWebView.loadUrl("http://www.example.org/tests/addjsif/");
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                handler.proceed(); // 忽略 SSL 证书错误 }
            });
        }

18【推荐】直接传递命令字或者间接处理有敏感信息或操作时,避免使用 socket 实现, 使用能够控制权限校验身份的方式通讯。

其他

1. 【强制】不要通过 Msg 传递大的对象,会导致内存问题。
2. 【强制】不能使用 System.out.println 打印 log。
发布了70 篇原创文章 · 获赞 206 · 访问量 150万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览