昨天在各大公众号上看到此标题,赶紧下下来学习一下,昨天至现在已经看完,共68页。
博客写的是复制开发手册重点部分,需要了解细节和例子分析的托至最后下载原文pdf。
- Activity 的 layout 以 module_activity 开头
- 模块名业务功能描述控件描述_控件状态限定词,module_login_btn_pressed,module_tabs_icon_home_normal
- 【推荐】 anim 资源名称以小写单词+下划线的方式命名,采用以下规则:模块名逻辑名称[方向|序号]
- tween 动 画 资 源 : 尽 可 能 以 通 用 的 动 画 名 称 命 名 , 如 module_fade_in ,
module_fade_out , module_push_down_in (动画+方向),frame 动画资源:尽可能以模 块+功能命名+序号。如:module_loading_grey_001 - #33b5e5e5
- 1dp
- 父 style 名称.当前 style 名称
如:
…
- 父 style 名称.当前 style 名称
如:
…
- Id 资源原则上以驼峰法命名,View 组件的资源 id 需要以 View 的缩写作为
前缀。 - 大分辨率图片(单维度超过 1000)大分辨率图片建议统一放在 xxhdpi 目录
下管理,否则将导致占用内存成倍数增加。
【强制】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");
【强制】 避免在 Service#onStartCommand()/onBind()方法中执行耗时操作,如果确
实有需求,应改用 IntentService 或采用其他异步机制完成。【强制】避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作,
应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做。
说明:
由于该方法是在主线程执行,如果执行耗时操作会导致 UI 不流畅。可以使用
IntentService 、 创 建 HandlerThread 或 者 调 用 Context#registerReceiver
(BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他 Wroker 线程
执行 onReceive 方法。 BroadcastReceiver#onReceive()方法耗时超过 10 秒钟,可
能会被系统杀死。【强制】 避免使用隐式 Intent 广播敏感信息,信息可能被其他注册了对应
BroadcastReceiver 的 App 接收。说明:
通过 Context#sendBroadcast()发送的隐式广播会被所有感兴趣的 receiver 接收,恶
意应用注册监听该广播的 receiver 可能会获取到 Intent 中传递的敏感信息,并进行
其他危险操作。如果发送的广播为使用 Context#sendOrderedBroadcast()方法发送
的有序广播,优先级较高的恶意 receiver 可能直接丢弃该广播,造成服务不可用,
或者向广播结果塞入恶意数据。
如果广播仅限于应用内,则可以使用 LocalBroadcastManager#sendBroadcast()实
现,避免敏感信息外泄和 Intent 拦截的风险。- 【 推 荐 】 添 加 Fragment 时 , 确 保 FragmentTransaction#commit() 在
Activity#onPostResume()或者 FragmentActivity#onResumeFragments()内调用。
不要随意使用 FragmentTransaction#commitAllowingStateLoss()来代替,任何
commitAllowingStateLoss()的使用必须经过 code review,确保无负面影响。 - 不要在 Activity#onDestroy()内执行释放资源的工作,例如一些工作线程的
销毁和停止,因为 onDestroy()执行的时机可能较晚。可根据实际需要,在
Activity#onPause()/onStop()中结合 isFinishing()的判断来执行。 - 【推荐】 如非必须,避免使用嵌套的 Fragment。
说明:
嵌套 Fragment 是在 Android API 17 添加到 SDK 以及 Support 库中的功能,
Fragment 嵌套使用会有一些坑,容易出现 bug,比较常见的问题有如下几种:
1) onActivityResult()方法的处理错乱,内嵌的 Fragment 可能收不到该方法的回调,
需要由宿主 Fragment 进行转发处理;
2) 突变动画效果;
3) 被继承的 setRetainInstance(),导致在 Fragment 重建时多次触发不必要的逻
辑。
非必须的场景尽可能避免使用嵌套 Fragment,如需使用请注意上述问题。
- 【推荐】总是使用显式 Intent 启动或者绑定 Service,且不要为服务声明 Intent Filter,
保证应用的安全性。如果确实需要使用隐式调用,则可为 Service 提供 Intent Filter
并从 Intent 中排除相应的组件名称,但必须搭配使用 Intent#setPackage()方法设置
Intent 的指定包名,这样可以充分消除目标服务的不确定性。 - 【推荐】 Service 需要以多线程来并发处理多个启动请求,建议使用 IntentService,
可避免各种复杂的设置。 - 【推荐】 对于只用于应用内的广播,优先使用 LocalBroadcastManager 来进行注册
和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率。 - 【推荐】当前 Activity的 onPause方法执行结束后才会执行下一个 Activity的 onCreate
方法,所以在 onPause 方法中不适合做耗时较长的工作,这会影响到页面之间的跳
转效率。 - 【强制】 使用 Adapter 的时候,如果你使用了 ViewHolder 做缓存,在 getView()的
方法中无论这项 convertView 的每个子控件是否需要设置属性(比如某个 TextView
设置的文本可能为 null,某个按钮的背景色为透明,某控件的颜色为透明等),都需
要为其显式设置属性(Textview 的文本为空也需要设置 setText(“”),背景透明也需要
设置),否则在滑动的过程中,因为 adapter item 复用的原因,会出现内容的显示错
乱 - 【强制】Activity或者 Fragment 中动态注册 BroadCastReceiver 时,registerReceiver()
和 unregisterReceiver()要成对出现。 - ==【强制】 布局中不得不使用 ViewGroup 多重嵌套时,不要使用 LinearLayout 嵌套,
改用 RelativeLayout,可以有效降低嵌套数。== - ==【推荐】 在 Activity 中显示对话框或弹出浮层时,尽量使用 DialogFragment,而非
Dialog/AlertDialog,这样便于随Activity生命周期管理对话框/弹出浮层的生命周期。== - 【强制】 禁止在非 ui 线程进行 view 相关操作。
- 【强制】 禁止在设计布局时多次设置子 view 和父 view 中为同样的背景造成页面过
度绘制,推荐将不需要显示的布局进行及时隐藏。
【推荐】 灵活使用布局,推荐 Merge、 ViewStub 来优化布局,尽可能多的减少 UI
布局层级,推荐使用 FrameLayout,LinearLayout、 RelativeLayout 次之。 - 【推荐】 在需要时刻刷新某一区域的组件时,建议通过以下方式避免引发全局 layout
刷新:
- 1) 设置固定的 view 大小的高宽,如倒计时组件等;
- 2) 调用 view 的 layout 方式修改位置,如弹幕组件等;
- 3) 通过修改 canvas 位置并且调用 invalidate(int l, int t, int r, int b)等方式限定刷新区域;
- 4) 通过设置一个是否允许 requestLayout 的变量,然后重写控件的 requestlayout、
onSizeChanged 方 法 , 判 断 控 件 的 大 小 没 有 改 变 的 情 况 下 , 当 进 入
requestLayout 的时候,直接返回而不调用 super 的 requestLayout 方法。
- 【推荐】 不能在 Activity 没有完全显示时显示 PopupWindow 和 Dialog。
【推荐】 尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载
到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错。
说明:
Android 的帧动画可以使用 AnimationDrawable 实现,但是如果你的帧动画中如果
包含过多帧图片,一次性加载所有帧图片所导致的内存消耗会使低端机发生 OOM
异常。帧动画所使用的图片要注意降低内存消耗,当图片比较大时,容易出现 OOM。
扩展参考:
1) https://stackoverflow.com/questions/8692328/causing-outofmemoryerror-in-fr
ame-by-frame-animation-in-android
2) http://blog.csdn.net/wanmeilang123/article/details/53929484
3) https://segmentfault.com/a/1190000005987659
4) https://developer.android.com/reference/android/graphics/drawable/Animatio
nDrawable.html【强制】 不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因为这
样会把 ListView 的所有 Item 都加载到内存中,要消耗巨大的内存和 cpu 去绘制图
面。
说明:
ScrollView 中嵌套 List 或 RecyclerView 的做法官方明确禁止。除了开发过程中遇到
的各种视觉和交互问题,这种做法对性能也有较大损耗。 ListView 等 UI 组件自身有
垂直滚动功能,也没有必要在嵌套一层 ScrollView。目前为了较好的 UI 体验,更贴
近 Material Design 的设计,推荐使用 NestedScrollView。正例:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
<android.support.v4.widget.NestedScrollView>
<LinearLayout>
<ImageView/>
...
<android.support.v7.widget.RecyclerView/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>
##### 反例:
<ScrollView>
<LinearLayout>
<TextView/>
...
<ListView/>
<TextView />
</LinearLayout>
</ScrollView>
扩展参考:
1) https://developer.android.com/reference/android/widget/ScrollView.html
2) https://developer.android.com/reference/android/support/v4/widget/NestedSc
rollView.html
【强制】 不要通过 Intent 在 Android 基础组件之间传递大数据(binder transaction
缓存为 1MB),可能导致 OOM。【强制】 在 Application 的业务初始化代码加入进程判断,确保只在自己需要的进程
初始化。特别是后台进程减少不必要的业务初始化。- ==【强制】 新建线程时,必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor
或者其他形式自定义的线程池),不允许在应用中自行显式创建线程。
说明:
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解
决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致
消耗完内存或者“过度切换”的问题。 另外创建匿名线程不便于后续的资源使用分析,
对性能分析等会造成困扰。扩展参考:
https://blog.mindorks.com/threadpoolexecutor-in-android-8e9d22330ee3== - 【强制】 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方
式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool : 允 许 的 请 求 队 列 长 度 为
Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM;
2) CachedThreadPool 和 ScheduledThreadPool : 允 许 的 创 建 线 程 数 量 为
Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
扩展参考:
http://dev.bizo.com/2014/06/cached-thread-pool-considered-harmlful.html - 【强制】 不要在非 UI 线程中初始化 ViewStub,否则会返回 null。
- 【推荐】 新建线程时,定义能识别自己业务的线程名称,便于性能优化和问题排查。
- 【推荐】 ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时
线程能被释放。 - 【 推 荐 】 禁 止 在 多 进 程 之 间 用 SharedPreferences 共 享 数 据 , 虽 然 可 以
(MODE_MULTI_PROCESS),但官方已不推荐。 - 【推荐】 谨慎使用 Android 的多进程,多进程虽然能够降低主进程的内存压力,但会
遇到如下问题:
1) 不能实现完全退出所有 Activity 的功能;
2) 首次进入新启动进程的页面时会有延时的现象(有可能黑屏、白屏几秒,是白
屏还是黑屏和新 Activity 的主题有关);
3) 应用内多进程时,Application 实例化多次,需要考虑各个模块是否都需要在所
有进程中初始化;
4) 多进程间通过 SharedPreferences 共享数据时不稳定。
- 【强制】 任何时候不要硬编码文件路径,请使用 Android 文件系统 API 访问。
- 【强制】 当使用外部存储时,必须检查外部存储的可用性。
- 【强制】 应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用
FileProvider。 【 推 荐 】 SharedPreference 提 交 数 据 时 , 尽 量 使 用 Editor#apply() , 而 非
Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使
用 Editor#commit()。
说明:
SharedPreference 相关修改使用 apply 方法进行提交会先写入内存,然后异步写入
磁盘,commit 方法是直接写入磁盘。如果频繁操作的话 apply 的性能会优于 commit,apply 会将最后修改内容写入磁盘。但是如果希望立刻获取存储操作的结果,并据此
做相应的其他操作,应当使用 commit。【强制】 数据库 Cursor 必须确保使用完后关闭,以免内存泄漏。如果 Cursor 的使用还牵涉
多线程场景,那么需要自行保证操作同步。- 【强制】 多线程操作写入数据库时,需要使用事务,以免出现同步问题。
正例:
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) {
// TODO
} finally {
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);
}
- 【推荐】 大数据写入数据库时,请使用事务或其他能够提高 I/O 效率的机制,保证执
行速度。 【强制】 执行 SQL 语句时,应使用 SQLiteDatabase#insert()、 update()、 delete(),
不要使用 SQLiteDatabase#execSQL(),以免 SQL 注入风险。【强制】 如果 ContentProvider 管理的数据存储在 SQL 数据库中,应该避免将不受
信任的外部数据直接拼接在原始 SQL 语句中,可使用一个用于将 ? 作为可替换参
数的选择子句以及一个单独的选择参数数组,会避免 SQL 注入。
正例:
// 使用一个可替换参数
String mSelectionClause = "var = ?";
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;
反例:
// 拼接用户输入内容和列名
String mSelectionClause = "var = " + mUserInput;
- 【强制】 加载大图片或者一次性加载多张图片,应该在异步线程中进行。图片的加
载,涉及到 IO 操作,以及 CPU 密集操作,很可能引起卡顿。 - 【强制】 png 图片使用 tinypng 或者类似工具压缩处理,减少包体积。
- 【推荐】 应根据实际展示需要,压缩图片,而不是直接显示原图。手机屏幕比较小,
直接显示原图,并不会增加视觉上的收益,但是却会耗费大量宝贵的内存。 - 【强制】 使用完毕的图片,应该及时回收,释放宝贵的内存。
- 【强制】 在 Activity.onPause()或 Activity.onStop()回调中,关闭当前 activity 正在执
行的的动画。 - 【推荐】 使用 inBitmap 重复利用内存空间,避免重复开辟新内存。
- 【推荐】 使用 ARGB_565 代替 ARGB_888,在不怎么降低视觉效果的前提下,减少
内存占用。但是一定要注意 RGB_565 是没有透明度的,如果图片本身需要保留透明度,那么
就不能使用 RGB_565。 - 【推荐】 . 尽量减少 Bitmap(BitmapDrawable)的使用,尽量使用纯色(ColorDrawable)、
渐变色(GradientDrawable)、 StateSelector(StateListDrawable)等与 Shape 结
合的形式构建绘图。 - 【推荐】 谨慎使用 gif 图片,注意限制每个页面允许同时播放的 gif 图片,以及单个
gif 图片的大小。 【推荐】 在有强依赖 onAnimationEnd 回调的交互时,如动画播放完毕才能操作页
面 , onAnimationEnd 可 能 会 因 各 种 异 常 没 被 回 调 ( 参 考 :
https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-calle
d-onanimationstart-works-fine ), 建 议 加 上 超 时 保 护 或 通 过 postDelay 替 代
onAnimationEnd。【推荐】 当 View Animation 执行结束时,调用 View.clearAnimation()释放相关资源。
- 【强制】 使用 PendingIntent 时,禁止使用空 intent,同时禁止使用隐式 Intent
说明:
1) 使用 PendingIntent 时,使用了空 Intent,会导致恶意用户劫持修改 Intent 的内
容。禁止使用一个空 Intent 去构造 PendingIntent,构造 PendingIntent 的 Intent
一定要设置 ComponentName 或者 action。
2) PendingIntent 可以让其他 APP 中的代码像是运行自己 APP 中。 PendingIntent
的intent接收方在使用该intent时与发送方有相同的权限。在使用PendingIntent
时,PendingIntent 中包装的 intent 如果是隐式的 Intent,容易遭到劫持,导致
信息泄露。 - 【强制】 禁止使用常量初始化矢量参数构建 IvParameterSpec,建议 IV 通过随机方
式产生。 - 【强制】 将 android:allowbackup 属性设置为 false,防止 adb backup 导出数据。
- 【强制】 在实现的 HostnameVerifier 子类中,需要使用 verify 函数效验服务器主机
名的合法性,否则会导致恶意程序利用中间人攻击绕过主机名效验。 - 【强制】 利用 X509TrustManager 子类中的 checkServerTrusted 函数效验服务器端
证书的合法性。 - 【强制】 META-INF 目录中不能包含如.apk,.odex,.so 等敏感文件,该文件夹没有经
过签名,容易被恶意替换。 - 【强制】 Receiver/Provider 不能在毫无权限控制的情况下,将 android:export 设置
为 true。 - 【参考】 数据存储在 Sqlite 或者轻量级存储需要对数据进行加密,取出来的时候进
行解密。 - 【强制】 阻止 webview 通过 file:schema 方式访问本地敏感数据。
- 【强制】 不要广播敏感信息,只能在本应用使用 LocalBroadcast,避免被别的应用
收到,或者 setPackage 做限制。 - 【强制】 不要把敏感信息打印到 log 中。
- 【强制】 对于内部使用的组件,显示设置组件的”android:exported”属性为 false。
说明:
Android 应用使用 Intent 机制在组件之间传递数据,如果应用在使用 getIntent(),
getAction(),Intent.getXXXExtra()获取到空数据、异常或者畸形数据时没有进行异
常捕获,应用就会发生 Crash,应用不可使用(本地拒绝服务)。恶意应用可通过向
受害者应用发送此类空数据、异常或者畸形数据从而使应用产生本地拒绝服务。 - 【强制】 应用发布前确保 android:debuggable 属性设置为 false。
- 【强制】 使用 Intent Scheme URL 需要做过滤。
- 【强制】 将所需要动态加载的文件放置在 apk 内部,或应用私有目录中,如果应用
必须要把所加载的文件放置在可被其他应用读写的目录中(比如 sdcard),建议对不
可信的加载源进行完整性校验和白名单处理,以保证不被恶意代码注入。 - 【强制】 除非 min API level >=17,请注意 addJavascriptInterface 的使用。
- 【强制】 不要使用 loopback 来通信敏感信息。
- 【推荐】 对于不需要使用 File 协议的应用,禁用 File 协议,显式设置 webView.
getSettings().setAllowFileAccess(false),对于需要使用 File 协议的应用,禁止 File
协议调用 JavaScript,显式设置 webView.getSettings().setJavaScriptEnabled(false)。 - 【强制】 使用 Android 的 AES/DES/DESede 加密算法时,不要使用默认的加密模式
ECB,应显示指定使用 CBC 或 CFB 加密模式。 - 【强制】Android APP 在 HTTPS 通信中,验证策略需要改成严格模式。 说明:Android
APP 在 HTTPS 通信中,使用 ALLOW_ALL_HOSTNAME_VERIFIER,表示允许和
所有的 HOST 建立 SSL 通信,这会存在中间人攻击的风险,最终导致敏感信息可能
会被劫持,以及其他形式的攻击。 - 【推荐】 Android5.0 以后安全性要求 较高的应用 应该使 用 window.setFlag
(LayoutParam.FLAG_SECURE) 禁止录屏。 - 【推荐】 zip 中不建议允许../../file 这样的路径,可能被篡改目录结构,造成攻击。 说
明:当 zip 压缩包中允许存在”../”的字符串,攻击者可以利用多个”../”在解压时改变
zip 文件存放的位置,当文件已经存在是就会进行覆盖,如果覆盖掉的文件是 so、
dex 或者 odex 文件,就有可能造成严重的安全问题。 - 【推荐】 加密算法:使用不安全的 Hash 算法(MD5/SHA-1)加密信息,存在被破解
的风险,建议使用 SHA-256 等安全性更高的 Hash 算法。 - 【推荐】 Android WebView 组件加载网页发生证书认证错误时,采用默认的处理方法
handler.cancel(),停止加载问题页面。 - 【强制】 不要通过 Msg 传递大的对象,会导致内存问题。
原文pdf地址:
链接: https://pan.baidu.com/s/1bqpG6SZ 密码: q55e