首先,我们进行优化的目标是:
1) 流畅: 冷/热启动快,打开页面快,某一个业务逻辑快。
2) 稳定:内存占用小,代码结构合理。
3)省电:CPU资源占用小
4)安装包小: 没有无用资源。
基于以上目标,进行了一系列的优化,总结如下。
冷启动优化
- 最简单直接的办法,设置主题图片,然后在MainActivity的 onCreate前将主题图片去除
<item name="android:windowBackground">
@drawable/tp_ic_start_activity_background</item>
目前我们就是采用上述方案,但是如果想要在APP发布后再去修改 那么上述方案就不行了,我们可以使用一个独立的Activity来放置需要显示的图片。
以上俗称 “Splash Screen”
这种方案其实并没有做到实际的优化,只是改善了用户体验。
- 将Application启动的耗时任务放到工作线程中执行,如何找出启动过程中比较耗时的任务是关键。
SDK中提供了跟踪方法执行耗时的工具,比如:
在Application的onCreate中加入
Debug.startMethodTracing("Dialer_Coldstart"/*跟踪文件名*/);
在MainActivity的onWindowFocusChanged中加入
Debug.stopMethodTracing();
冷启动完成后,会在手机生成一个trace文件
/storage/emulated/0/Android/data/com.android.dialer/files/Dialer_Coldstart.trace
使用AndroidStudio打开该文件后
直接输入包名,就可以快速定位耗时的函数在哪里,蓝色的条越长,耗时越多。
在上面分析发现DialtactsActivity.onCreate占用时间比较长,因此我们也可以在onCreate里面加入
Debug.startMethodTracing("Dialer_Coldstart"/*跟踪文件名*/);
...
Debug.stopMethodTracing();
重新生成trace文件进行分析,分析起来更方便。
一般情况下,当发现耗时任务时,有如下2中处理方法:
a. 该任务不需要在主线程中执行的:可以将其放到异步线程中执行,注意最好维护一个全局的线程池,避免野线程的存在;同时也要考虑线程并发带来的数据安全问题。
b. 该任务必须在主线程中执行的:
比如包含2个Fragment的ViewPager,每个Fragment的inflateView都必须在主线程中执行,考虑到冷启动只需要先初始化一个Fragment就可以了,因此,另一个Fragment可以延迟到界面稳定显示后(获取用户选择该页面后),再去加载。这样就可以节省一个Fragment的加载时间。
具体可以采用ViewStub或者 一个空的FrameLayout实现。
热启动优化
Android为了提升用户体验,在用户点击返回按钮退出应用时,只是关闭了Activity,并不会杀掉应用,这样在下次启动该应用时,可以省去创建进程的时间。
参考对应的思想,我们可以重写返回按钮的逻辑,在点击返回时,不销毁Activity,仅仅是将Activity切换到后台,这样在下次打开应用时,连Activity都不用重建,从而达到秒开。
重写 onBackPressed:
if (!moveTaskToBack(true)) {
super.onBackPressed();
}
注意,在onStop时释放资源。
内存优化
电话APP对内存占用不高,分析只是发现一处内存占用的问题----为了加快通话背景展示,缓存了桌面的背景或者联系人头像,目前修改了高斯模糊的流程,去除了图片的缓存,减少了4M左右内存。
线程优化
使用 Android Device Monitor 可以查看具体的线程
相比于Helloworld程序,我把可疑的线程圈了出来,经过简单分析如下:
- NonUiExecutor 线程是我们自己实现的一个线程池中的线程,线程池核心线程数 为5
private static final ExecutorService sDefaultParallelExecutor =
Executors.newFixedThreadPool(
5,
new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread(r, "NonUiExecutor");
thread.setPriority(4);
return thread;
}
}
);
因此NonUiExecutor线程最多存在在5个,而且会一直保持着,之所以引入这个线程池,是为了缩减线程不停创建和销毁所带来的损耗。也是为了统一规范系统的异步任务,避免野线程的出现。
- 除了考虑上面稳定时 线程的情况, 还需要优化在进行某些业务逻辑时,随意创建线程执行异步任务所带来的性能损耗。
比如将APP切换到后台,再切换到前台,线程列表就出现了异常:
从线程的命名可以看出,是使用了AsyncTask来执行异步的任务,但是又没有指定线程池,因此,需要跟踪该业务逻辑。将AsyncTask提交到上面sDefaultParallelExecutor中执行,这就要求我们将sDefaultParallelExecutor暴露出来给使用者。
资源优化
打开Android Studio,点击菜单Analyze -> Run Inspection By Name -> 输入Unused resources 就可以查找出没有被引用的资源。
注意,需要检查代码中是否有类似如下获取资源的代码,避免误删
Resources r = getResources();
int id = r.getIdentifier("test1", "drawable", getContext().getPackageName());
r.getDrawable(id, null);
目前电话电话APP还没进行该项优化。
业务逻辑优化
关于业务逻辑主要分2部分
- 检查耗时的任务,当发现一段业务很卡,可以使用冷启动里面介绍的MethodTrace工具生成trace文件进行分析,
另外AndroidStudio也提供了图形界面的支持:
点击小红点开始Record a method trace. 再点一下就停止。会在地下生成一个函数调用堆栈耗时表,大体上和分析冷启动差不多。
2)对于分析结果的处理,除了减少耗时调用外,对于必须但耗时的操作,我们可以提前缓存,采用空间换时间的做法。
注意,考虑内存中数据的时效性。
比如上面的onCreateView函数,分析发现其实就是inflateView耗时,我们就可以考虑是否可以提前地去inflateView,然后缓存在内存中,等到用户点击了拨号盘后,那么就不需要再infalteView,从而加快了拨号盘的弹出时间。但是,有一个问题,如果用户一直不点击拨号盘,那么这部分内存则是浪费的。