Android性能优化实践

首先,我们进行优化的目标是:

1) 流畅: 冷/热启动快,打开页面快,某一个业务逻辑快。

2) 稳定:内存占用小,代码结构合理。

3)省电:CPU资源占用小

4)安装包小: 没有无用资源。

基于以上目标,进行了一系列的优化,总结如下。

冷启动优化

  1. 最简单直接的办法,设置主题图片,然后在MainActivity的 onCreate前将主题图片去除

@drawable/tp_ic_start_activity_background

目前我们就是采用上述方案,但是如果想要在APP发布后再去修改 那么上述方案就不行了,我们可以使用一个独立的Activity来放置需要显示的图片。

以上俗称 “Splash Screen”

这种方案其实并没有做到实际的优化,只是改善了用户体验。

  1. 将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打开该文件后

Android性能优化实践_第1张图片
Trace文件.png

直接输入包名,就可以快速定位耗时的函数在哪里,蓝色的条越长,耗时越多。

在上面分析发现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 可以查看具体的线程

Android性能优化实践_第2张图片
简单HelloworldAPP启动稳定后线程
Android性能优化实践_第3张图片
我们电话APP启动完成后线程

相比于Helloworld程序,我把可疑的线程圈了出来,经过简单分析如下:

  1. 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个,而且会一直保持着,之所以引入这个线程池,是为了缩减线程不停创建和销毁所带来的损耗。也是为了统一规范系统的异步任务,避免野线程的出现。

  1. 除了考虑上面稳定时 线程的情况, 还需要优化在进行某些业务逻辑时,随意创建线程执行异步任务所带来的性能损耗。

比如将APP切换到后台,再切换到前台,线程列表就出现了异常:

Android性能优化实践_第4张图片
切换到后台线程.png

从线程的命名可以看出,是使用了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部分

  1. 检查耗时的任务,当发现一段业务很卡,可以使用冷启动里面介绍的MethodTrace工具生成trace文件进行分析,
    另外AndroidStudio也提供了图形界面的支持:
Android性能优化实践_第5张图片
StartMethodTrace.png

点击小红点开始Record a method trace. 再点一下就停止。会在地下生成一个函数调用堆栈耗时表,大体上和分析冷启动差不多。

2)对于分析结果的处理,除了减少耗时调用外,对于必须但耗时的操作,我们可以提前缓存,采用空间换时间的做法。
注意,考虑内存中数据的时效性。

比如上面的onCreateView函数,分析发现其实就是inflateView耗时,我们就可以考虑是否可以提前地去inflateView,然后缓存在内存中,等到用户点击了拨号盘后,那么就不需要再infalteView,从而加快了拨号盘的弹出时间。但是,有一个问题,如果用户一直不点击拨号盘,那么这部分内存则是浪费的。

你可能感兴趣的:(Android性能优化实践)