【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
2)CachedThreadPool 和 ScheduledThreadPool : 允许 的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
我的思考:
FixedThreadPool和SingleThreadPool的执行策略存在堆积大量任务;CachedThreadPool,ScheduledThreadPool则存在堆积大量线程。这两种情况都可能造成OOM异常。
详细说明看这篇英文文章:传送门
概述为:
It's unbounded, which means that you're opening the door for anyone to cripple your JVM by simply injecting more work into the service (DoS attack)
The unbounded problem is exacerbated by the fact that the Executor is fronted by a SynchronousQueue which means there's a direct handoff between the task-giver and the thread pool
【强制】新建线程时,必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor 或者其他形式自定义的线程池),不允许在应用中自行显式创建线程
说明:
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解 决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。另外创建匿名线程不便于后续的资源使用分析,对性能分析等会造成困扰
我的思考:
也就是说不要使用new Thread(new Runnable(){}).start()来开启子线程任务
【推荐】ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时线程能被释放。
我的思考:
该推荐是对上面点的细节的展开
【推荐】禁止在多进程之间用 SharedPreferences 共享数据,虽然可以 (MODE_MULTI_PROCESS),但官方已不推荐。
【推荐】谨慎使用 Android 的多进程,多进程虽然能够降低主进程的内存压力,但会遇到如下问题:
- 不能实现完全退出所有 Activity 的功能
2) 首次进入新启动进程的页面时会有延时的现象(有可能黑屏、白屏几秒,是白屏还是黑屏和新 Activity 的主题有关)
3)应用内多进程时,Application实例化多次,需要考虑各个模块是否都需要在所有进程中初始化
4)多进程间通过 SharedPreferences 共享数据时不稳定
我的思考:
3)问题需要获取当前进程名呢?Kotlin代码如下
fun getProcessName(): String{
val currentPid = android.os.Process.myPid()
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningAppProcesses = activityManager.runningAppProcesses
for(process in runningAppProcesses){
if(process.pid == currentPid){
return process.processName
}
}
return ""
}
【强制】应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用 FileProvider。
我的思考:
具体实现步骤,参考官网文档https://developer.android.com/reference/android/support/v4/content/FileProvider.html
【强制】多线程操作写入数据库时,需要使用事务,以免出现同步问题。
说明:
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) {
// TODO
} finally {
db.endTransaction();
}
}
我的思考:
db.beginTransaction();
db.setTransactionSuccessful();
db.endTransaction();
通过上述事务语句,就能保证线程安全了
【强制】执行 SQL 语句时,应使用 SQLiteDatabase#insert()、update()、delete(), 不要使用 SQLiteDatabase#execSQL(),以免SQL注入风险。
【强制】如果 ContentProvider 管理的数据存储在 SQL 数据库中,应该避免将不受 信任的外部数据直接拼接在原始 SQL 语句中,可使用一个用于将 ? 作为可替换参 数的选择子句以及一个单独的选择参数数组,会避免 SQL 注入。
我的思考:
使用execSQL(),该方法参数是传入的查询语句,如果对这个语句不做过滤而是直接使用用户提交的表单内容,那么查询语句里包含着表单内容,那么用户可能在表单里写上相关的查询的数据库语句,此时就可能造成SQL注入。
【强制】png 图片使用 tinypng 或者类似工具压缩处理,减少包体积。
我的思考:
tinyPng是一个网站,在改网站上传一张png,能为你压缩该png。发现一个Mac的客户端支持批量操作(https://github.com/kyleduo/TinyPNG4Mac);
实践证明,效果是惊人的。我的一个drawable-xxdpi里的png图片经tinypng压缩一次后,容量从3.4M降到了1.1M。二次压缩后容量为1M。
【强制】在 Activity.onPause()或 Activity.onStop()回调中,关闭当前 activity 正在执 行的的动画。
我的思考:
而不是放在Activity.onDestroy()回调中,因为destroy回调时机过晚,如果不退出或者不做销毁,动画资源便不会被释放
欢迎阅读
- 阿里Android开发手册读后感-下篇