-
架构篇
-
组件化:
- 将app拆分成业务和基础组件,业务包括像拍摄、推送、账户、分享,业务组件之间的依赖可以通过定义接口来解耦接口和实现可以分别定义一个module,接口module提供给业务层,实现接口的module通过动态方式与接口关联起来,两个module可以放在一个文件夹下
- 基础库像播放器、网络库、UI组件、图片库等
-
性能篇
-
systrace使用(systrace详细使用介绍):
- 进入Android/sdk/platform-tools/systrace目录,执行python systrace.py -a "进程名“
- 执行后会在当前目录下生成trace.html,注意目前只支持python 2.x版本
- 打开chrom,输入chrome://tracing/,然后加载生成的trace.html
- 开始分析,找到对应的进程,先查看最上面总体CPU分布情况,然后点开进程对应的UI Thread,右边会列出每个时间段内执行的方法,比如activityStart,如果有进行动画操作还会出现Frames数据,绿色代表正常渲染16ms以内,黄色和红色代表超过16ms,点击具体的颜色会有对应的耗时分析,比如view.draw耗时太大等
- 总体来说,这个工具优势是android sdk自带工具,能够清晰看到CPU消耗情况,以及列出某个时间段内具体耗时方法
- 快捷键,w->放大,s->缩小,a->左移,d->右移
- systrace可以用来分析主线程耗时操作,做整体性能优化,如果发现前几秒的数据丢失,可能是前面抓取数据太多导致数据丢失,可以减少一次抓取时间长度
-
SharedPreference优化:
SharedPreference可以在multidex之前提前加载并且不会发生异常,所以提前加载不仅可以充分利用Application onCreate之前的CPU,也能为后面应用使用SharedPreference节省时间,具体做法是在attachBaseContext时候开启几个异步线程加载所有的SharedPreference,注意的是要重写Application的getApplicationContext:
@Override
public Context getApplicationContext(){
return this;
}
重写后attachBaseContext才不会调用getApplicationContext返回null
还有一点是这时候加载的SharedPreference名称必须是硬编码,不能是static变量,否则会和multidex冲突
-
异步初始化mainView
因为mainView通常是一个app UI最复杂的地方,特别相机首页,提前初始化可以缩减不少时间,代码如下:
public static void asyncInitMainView(final Context context,final int layoutId) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try{
View main = LayoutInflater.inflate(layoutId,null);
} catch (Exception e) {
}
}
});
// thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
}
注意的是由于该方法是在Application初始化时候调用的,所以传入的context不是当前activity的context,因此后面控件需要在UI线程中使用场景,比如弹窗,这时候可以通过getRootView().getContext()来适配,而不能直接用getContext()方式
-
anr定位
常用搜索关键词:anr in / low_memory / slow_operation(系统内存交换引起Kswapd)
搜索到anr in后主要看:
- load:27.81 / 27.07 / 24.71 (表示:anr发生前一分钟内正在使用和等待使用CPU的活动进程平均有27.81个,以此类推,27.07代表前五分钟,24.71代表前十五分钟)
- CPU usage: 主要是主进程、mmcqd(该进程出现代表发生IO读写,超过10%就说明有严重IO读写)、kswapd(该进程出现代表出现内存交换,即RAM交换到ROM, ROM交换到RAM,超过10%说明有严重内存交换)
- 单个app进程超过40%说明发生异常,如果发现CPU占比中user很低,kernel很高,很有可能是阻塞在wait操作,可能是某个子线程锁住了文件,主线程等待文件锁释放导致
分析过程:
发生ANR进程本身CPU占用比较高,再搜索"slow operation",“low_memory” 等关键字,都没有出现在log日志中,而lowmemorykiller也以较合理的频率出现在dmesg日志中,所以基本排除是内存过低导致
如果CPU方向继续分析log日志无法找到更多线索,可以思考既然主线程状态正常,那么高cpu一定是其它线程引起的,那就反馈trace继续分析,查看进程的其它线程发现,如果所有binder线程都处于waiting状态,只有Binder_2在工作状态,那么就可能Binder_2调用线程占用系统资源导致的
anr经典场景:
- UI线程等待其它线程释放某个锁,导致UI线程无法处理用户输入
- 游戏中每帧动画都进行了比较耗时的大量计算,导致CPU忙不过来
- Web应用中,网络状态不稳定,而界面在等待网络数据
- UI线程中进行了一些磁盘IO(包括数据库、SD卡等等)的操作,在个别设备上因为硬件损坏等原因阻塞住了
- 手机被其他App占用着CPU
Input产生的ANR比较特别(Service和BroadcastReceiver是启动一个超时检查),发生ANR的时序如下:
- input点击事件会被放入WaitQueue,然后传送到UI线程的队列中处理,处理完从WaitQueue中删除,但是input事件能否放入WaitQueue需要判断窗口是否就绪
- 窗口未就绪的判断条件是当前input事件产生时间大于WaitQueue队头事件产生时间500ms,大于500ms无法加入WaitQueue
- 窗口未就绪的话,当前事件会转为wait状态,并设置waitCause,waitTimeoutTime(waitTimeoutTime=currentTime+5s)等并启动5s后超时检查
- 超时检查时如果还是无法添加进WaitQueue,则会检查当前时间是否大于waitTimeoutTime,如果是,则触发ANR,因此,发生ANR的条件是:WaitQueue有元素,且mPendingEvent等待(加入OutboundQueue)超时,详情可见:https://zhuanlan.zhihu.com/p/53331495
- 所以一般UI线程阻塞时点击两次才会产生ANR的原理就是上面的原因,第一次点击因为WaitQueue是空的,所以可以直接加入,第二次点击由于WaitQueue不为空而启动超时检查再次尝试加入,时间到之后发现还是无法加入就触发ANR