两年前我做过了相似的发动优化剖析《怎么统计Android App发动时刻》和《怎么优化Androd App发动速度》。两年过后,今天看来,之前说的nimbledroid东西现已需求收费,而且Android Studio自带的Android Profiler现已足够强壮,而且Systrace也有了更为强壮的Perfetto UI剖析东西。咱们是时分来重新学习一下现在功能剖析的办法以及怎么在剖析的基础上做发动优化这个工作。转载请注明来源「Bug总柴」

功能剖析东西

首要咱们来学习一下怎么运用功能剖析的东西。咱们从一个详细的比如动身,便是怎么剖析运用发动的功能。

Android Profiler

装备

咱们来先看看Android Profiler。为了能在运用一发动就能立刻捕捉到剖析数据,咱们需求依照下面的进程装备一下:

  • 挑选 Run -> Edit Configurations
    Android性能分析&启动优化
  • 在设置里边挑选Profiling的tab,然后选中Start recording CPU activity on startup。留意这儿挑选的Sample Java Methods,表明能够定位到Java代码。其他选项的意义查看cpu-profiler#configurations。 假如想有更详细的信息的话,能够选中Enable advanced profiling。
    Android性能分析&启动优化
  • 在装备完之后挑选Run -> Profiler
    Android性能分析&启动优化
    在页面发动完成之后中止监测,能够得到发动进程的CPU、内存网络和电量耗费信息,如下图:
    Android性能分析&启动优化

CPU监控

剖析进程

点击进入CPU模块

Android性能分析&启动优化
能够挑选线程,并看到线程的详细代码耗时。 如以下比如
Android性能分析&启动优化
绿色表明咱们写的代码耗时,咱们能够挑选主线程进行调查。这儿显现在Applicaiton onCreate进程中需求耗费620ms。其中比较耗时的办法是registerByCourseKey和initYouzanSDK。而且经过Call Chart视图不断的往下看能够看出导致这个办法耗时的详细原因
Android性能分析&启动优化
Android性能分析&启动优化
经过这样不断的往下剖析,就能大致定位到发动CPU耗时的原因。下面咱们举一个详细的优化比如。

优化比如

优化前:

Android性能分析&启动优化
假如上图所示,在发动进程中RxBroadcast的时分带来了较大的耗时
Android性能分析&启动优化
查看代码:

private fun initBroadcast() {
    val filter = IntentFilter()
    ……
    disposables.add(RxBroadcast.fromLocalBroadcast(context, filter)
        .subscribe({ intent ->
            ……
        },
        { throwable: Throwable ->
            ……
        }
   ))
}

确实在initBroadcast运用了RxBroadcast.fromLocalBroadcast()办法,咱们测验运用LocalBroadcastManager.registerReceiver代替。修改为如下代码:

private fun initBroadcast() {
    val filter = IntentFilter()
    ……
    LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, filter)
}

优化后重新进行发动CPU剖析:

Android性能分析&启动优化
能够看出初始化的时刻比优化前削减了90ms。由此咱们也能够得到定论,运用RxBroadcast尽管比较炫酷,可是这是一个比较耗时的行为,因而应该尽量削减RxBroadcast的运用。

留意事项

  • 需求留意的是这儿的耗时有些是在CPU处于Sleep状态下的。 在Sleep状态表明CPU被其他线程占用,这个时分需求剖析主线程Sleep状态下其他线程的状况。例如:
    Android性能分析&启动优化
    这儿显现主线程在00:06左右的时刻处于Sleeping状态,这个时分查看其他线程的CPU占用
    Android性能分析&启动优化
    发现在MemoryAg的线程在占用CPU资源,这种状况下不应该认为对应的主线程办法耗时,而是要考虑例如内存收回或许其他线程占用了CPU资源的状况。
  • 还需求留意不是每次点击”Profiler”都会正常把信息记录下来,偶然会呈现运用闪退的状况,这可能是Android Studio的Bug或许是日志太大了的问题。这种状况不要灰心,多试几回就会好。

Perfetto UI

运用进程

在Android 10的手机上,开发者形式新增加了一个“体系盯梢”的功能,咱们首要将开发者形式下的“体系盯梢”翻开:

Android性能分析&启动优化
Android性能分析&启动优化
咱们也能够从“类别”选项中挑选咱们重视的信息类别:
Android性能分析&启动优化
设置完之后咱们会发现下拉快捷选项多了个棒棒糖形状的图标
Android性能分析&启动优化
这个时分杀掉咱们需求调试的运用,然后点击敞开棒棒糖,接着翻开运用,等候运用完全翻开之后,再点击一次棒棒糖,完毕录制。
Android性能分析&启动优化
Android性能分析&启动优化
然后咱们保存录制后的文件,后缀为“.perfetto-trace” 然后咱们在perfetto ui网站上挑选Open trace file上传刚刚得到的文件
Android性能分析&启动优化
烘托之后咱们能够得到相似于之前systrace的剖析,经过Perfetto UI咱们能够愈加简单控制
Android性能分析&启动优化

剖析进程

首要咱们需求知道,经过“体系盯梢”得到的结果是相似于在Android Studio里边Profiler挑选“Trace System Calls”的结果,咱们能够看到体系中一切CPU在时刻轴的一切运行使命。而且咱们也能够看到体系一切的进程以及进程中一切的线程使命。

Android性能分析&启动优化
咱们打开Perfetto UI的调试运用里边的主线程:
Android性能分析&启动优化
能够看到线程中每个进程的耗时。咱们能够经过不断的放大来查看每个时刻段的体系调用。

优化比如

优化前:

Android性能分析&启动优化

Android性能分析&启动优化
能够看出在主页inflate的进程中,有个一个“bg_simple_dict_blueriver.jpg”的图标耗时了29ms加载。剖析其所在的代码:

<ImageView
    android:id="@+id/iv_simple_dict_bg"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="https://juejin.im/post/7236921385473179685/@drawable/bg_simple_dict_blueriver"
    android:scaleType="centerCrop"
    android:visibility="gone"
/>

因为这个图片只会在网络不畅的时分作为placeholder存在,因而这儿简单的做法能够将

android:src="https://juejin.im/post/7236921385473179685/@drawable/bg_simple_dict_blueriver"

修改为

tools:src="https://juejin.im/post/7236921385473179685/@drawable/bg_simple_dict_blueriver"

更好的办法也能够将ImageView改为ViewStub引进,在有需求的时分再烘托出来,节约布局烘托时刻。 优化后:

Android性能分析&启动优化
能够看出,在优化后inflate的时刻由本来的118ms下降到了103ms,而且在inflate进程中也没有了bg_simple_dict_blueriver.jpg图片加载的进程。

发动优化

有了以上的Sample Java Methods以及Trace System Calls剖析,咱们能够得到从微观代码层面以及微观CPU执行层面的发动使命耗时。

Proguard & R8

Android性能分析&启动优化
Android性能分析&启动优化

除了事务的懒加载处理之外,咱们能够看到dex文件的加载时刻占有了大部分的发动时刻。dex的加载时刻跟代码量级有关。因为长时间的前史引进了大量了第三方库以及本身事务增加带来的代码量增加,咱们dex加载的速度也越来越慢。为了处理dex加载慢的问题,咱们能够经过两个方面:首要是处理对dex加载有较大影响的加固进程,这个能够跟杭研进行交流处理。第二便是在代码中参加代码紧缩和混杂。

代码紧缩和混杂能够使得dex文件变小,从而削减dex文件加载的时刻。可是从零开端参加代码紧缩和混杂是一个非常艰巨的进程,因为代码紧缩和混杂后会导致很简单发生ClassNotFoundException以及NoSuchMethodError,而且会对诸如push、序列化等依靠类名以及属性名的代码失效。参加代码紧缩和混杂需求额定的仔细和较大的工作量。

在参加代码紧缩和混杂的进程中,咱们总结了以下的办法进程:

本地代码

  • 查看一切运用注解的代码,参加proguard 规矩
  • 查看一切JNI相关代码,参加proguard 规矩
  • 查看一切运用反射的代码,参加proguard 规矩
  • 查看一切序列化以及会运用Json转换为Modle的代码,参加proguard 规矩
  • 查看一切依据类名来运用的代码,例如Push等,参加proguard 规矩
  • 要求以后代码重构需求对Proguard进行相应改动
  • 要求新增的代码需求增加Proguard规矩

三方代码

  • 判别External Libraries中的三方库引用是否是release依靠或许debug依靠,假如是的话持续
  • 判别lib库是否为现在代码所需求的,假如引用了没有运用或许引用了现在代码上一切运用的当地都现已不再运用,则整理这个lib并整理相关没有用到的代码
  • 若果lib库为现在代码所需求的,到该lib库的官网查找相应的proguard规矩,并粘贴到proguard-rules.pro文件中
  • 假如该lib官网库没有相应proguard规矩,则调查lib库是否有用到native代码、annotation或许反射这种需求proguard处理的当地,有的话增加相应规矩
  • 增加完proguard规矩之后,找到现在项目中运用到这个库的当地,测验一下是否会有溃散呈现
  • 假如有溃散呈现,依据溃散提示增加相应proguard规矩

参考

  • Measure app performance with Android Profiler
  • Identify CPU hot spots
  • Inspect CPU activity with CPU Profiler
  • View the Java heap and memory allocations with Memory Profiler
  • Shrink, obfuscate, and optimize your app
  • Comparison of ProGuard vs. R8: October 2019 edition