Android运用发动优化相关的文章现已有很多人都写过了,可是主要都是聚焦在,为了发动功能都做了哪些改动上,少见有文章会说应该怎么剖析、识别运用的发动功能。

本篇文章将会结合我个人对Perfetto的实践运用阅历,解说车载运用的发动时刻是怎么丈量得到的,丈量出发动时刻后,咱们又该怎么剖析出其间的功能瓶颈。

在剖析运用的发动功能之前,咱们先简单了解一些Android中有关运用发动时刻的根底性常识。

运用发动时刻

初始显现时刻(TTID)

初始显现时刻 (TTID,The Time to Initial Display) ,它是从体系接收到发动目的到运用程序显现榜首帧界面的时刻,也便是用户看到运用程序界面的时刻。

丈量TTID

当运用程序完结上面说到的一切作业时,能够在logcat中看到以下的日志输出。

/system_process I/ActivityTaskManager: Displayed xxxx/.MainActivity: +401ms

在一切资源彻底加载并显现之前,Logcat输出中的Displayed时刻目标,省去了布局文件中未引用的资源或运用作为目标初始化一部分创立资源的时刻。

有的时候logcat输出中的日志中会包含一个附加字段total。如下所示:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

在这种状况下,榜首个时刻丈量值仅针对榜首个制作的 activity。total 时刻丈量值是从运用进程发动时开端核算,而且能够包含首次发动但未在屏幕上显现任何内容的另一个 activity。total 时刻丈量值仅在单个activity的时刻和总发动时刻之间存在差异时才会显现。

一些博客中介绍的运用am start -S -W指令来测得的时刻,实践上便是初始显现时刻。大多数状况下,初始显现时刻并不能代表运用真实的发动时刻,例如,运用发动时需求从网络上同步最新的数据,当一切的数据加载完毕后的耗时才是真实的发动耗时,这个便是接下来要介绍的TTFD – 彻底显现时刻

彻底显现的时刻(TTFD)

彻底显现时刻 (TTFD,The Time to Full Display) 。它是从体系接收到发动目的到运用程序加载完结一切资源和视图层次结构的时刻,也便是用户能够真实运用运用程序的时刻。

丈量TTFD

办法一:调用reportFullyDrawn()

reportFullyDrawn()办法能够在运用程序加载完结一切资源和视图层次结构后调用,让体系知道运用程序现已彻底显现,从而核算出彻底显现时刻。假如不调用这个办法,体系只能核算出TTID而无法得知TTFD。

system_process I/ActivityTaskManager: Fully drawn xxxx/.MainActivity: +1s54ms

办法二:拆帧

拆帧法是现在核算车载运用发动耗不时最遍及的做法,拆帧法有许多不同的录制、拆帧办法。

常见的有,运用支撑60fps的摄像机(支撑60fps摄像的手机也能够)拍摄运用的发动视频,再运用Potplay视频播放器检查从桌面点击运用画面彻底显现出来的帧数差值,然后除以60就能够得到运用的发动耗时。

以上办法合适测试人员运用,这儿介绍另一种更合适开发人员操作的办法:FFmpeg拆帧。

FFmpeg 的下载地址:ffmpeg.org/download.ht…

首要运用adb连接Android设备,运用录屏指令录制运用发动时的视频。

adb shell screenrecord /sdcard/launch.mp4

运用FFmpeg检查视频的帧数

ffmpeg -i launch.mp4

【性能优化】使用Perfetto定位应用启动性能的瓶颈

假如视频帧数不足60fps,持续运用FFmpeg将视频补帧到60fps.

ffmpeg -i launch.mp4 -filter:v fps=60 output.mp4

将补帧后的视频,每一帧拆成一张图片,然后核算出从桌面点击运用画面彻底显现出来的帧数差值即可。

ffmpeg -i output.mp4 output_%04d.jpg

或将补帧后的视频转换成gif动图,运用图片浏览器数帧(MAC OS自带的图片浏览器就能够)。

ffmpeg -i output.mp4 -vf fps=60,scale=320:-1:flags=lanczos -loop 0 output.gif

关于运用的发动时刻和丈量办法就介绍到这儿,更多内容能够参考Android的官方文档「运用发动时刻 | 运用质量 | 安卓」,写得十分详细。

值得一提的是,当前主流车载运用的均匀发动耗时(以8155平台为例)如下:

  • 冷发动TTFD

第三方大型互联网运用需求控制在2.6s以下,车载体系运用需控制在1.6s以下,

  • 温发动TTFD

遍及需控制在0.8s以下。

以上是我个人的经历,不同的主机厂商肯定会存在高低不同的功能要求。

Perfetto 介绍

Perfetto是Android 10 引入的体系级盯梢工具,支撑Android,Linux和Chrome,用于取代Systrace 。比较于ProfilerAGI,它不再局限于运用内,而是能够供给整个体系的运行状况,当咱们需求检查运用有没有影响到体系的稳定性和流畅性时,或者反过来用于剖析体系对运用运行的影响时,就能够运用Perfetto来进行体系级盯梢和剖析。

有关Perfetto的根底内容,能够检查我之前翻译的Android官方视频:【译】现代Android开发技术 – Perfetto入门

Perfetto 快速上手

Perfetto的运用办法有很多,个人主张运用record_android_trace脚本。它是Perfetto供给的一个辅佐脚本,能够协助咱们运用adb从Android设备上搜集功能数据。这个脚本有以下作用:

  • 主动检测设备上是否有perfetto二进制文件,假如没有,就尝试从GitHub下载并推送到设备上。
  • 主动设置盯梢的配置参数,例如盯梢时刻、缓冲区巨细、输出文件途径等。
  • 主动履行perfetto指令,并在盯梢完结后将输出文件拉取到电脑上。
  • 主动在浏览器中翻开输出文件,让你能够检查和剖析盯梢成果。

record_android_trace的下载地址:raw.githubusercontent.com/google/perf…

record_android_trace的用法如下:

./record_android_trace [options] [category1] [category2] ...

其间,options是一些可选的参数,例如:

  • -o OUT_FILE:指定输出文件的途径,假如不指定,默以为 perfetto_trace.pb。
  • -t TIME:指定盯梢的时刻,假如不指定,默以为 10 秒。
  • -b SIZE:指定盯梢的缓冲区巨细,假如不指定,默以为 32 MB。

category是一些要盯梢的atrace或ftrace类别,能够运用–list检查设备支撑的Trace类别,输出成果可能如下:

link@link-PC:~/Desktop$ ./record_android_trace --list
         gfx - Graphics
       input - Input
        view - View System
     webview - WebView
          wm - Window Manager
          am - Activity Manager
          sm - Sync Manager
       audio - Audio
       video - Video
      camera - Camera
         hal - Hardware Modules
         res - Resource Loading
      dalvik - Dalvik VM
          rs - RenderScript
      bionic - Bionic C Library
       power - Power Management
          pm - Package Manager
          ss - System Server
    database - Database
     network - Network
         adb - ADB
    vibrator - Vibrator
        aidl - AIDL calls
       nnapi - NNAPI
         rro - Runtime Resource Overlay
         pdx - PDX services
       sched - CPU Scheduling
         irq - IRQ Events
         i2c - I2C Events
        freq - CPU Frequency
        idle - CPU Idle
        disk - Disk I/O
        sync - Synchronization
       workq - Kernel Workqueues
  memreclaim - Kernel Memory Reclaim
  regulators - Voltage and Current Regulators
  binder_driver - Binder Kernel driver
  binder_lock - Binder global lock trace
   pagecache - Page cache
      memory - Memory
     thermal - Thermal event
         gfx - Graphics (HAL)
         ion - ION allocation (HAL)

例如,假如想要盯梢 sched、gfx和view,输出文件为 trace.perfetto-trace,盯梢时刻为 5 秒,缓冲区巨细为 16 MB,能够履行以下指令:

./record_android_trace -o trace.perfetto-trace -t 5s -b 16mb sched gfx view

Perfetto 剖析发动功能

运用Perfetto剖析运用的发动功能十分简单,首要运用record_android_trace抓取运用的发动数据,履行如下指令:

./record_android_trace -o trace.perfetto-trace -t 15s -b 200mb gfx input view webview wm am sm audio video camera hal res dalvik rs bionic power pm ss database network adb vibrator aidl nnapi rro pdx sched irq i2c freq idle disk sync workq memreclaim regulators binder_driver binder_lock pagecache memory gfx ion

15s后record_android_trace会主动帮咱们翻开浏览器。在Android App Startups一栏展现的便是运用的发动耗时。如下所示,需求注意的是Android App Startups显现的时刻是运用的TTID初始显现时刻

【性能优化】使用Perfetto定位应用启动性能的瓶颈

在Perfetto的左侧挑选Metrics,然后挑选android_startup,点击Run,Perfetto会主动帮咱们剖析中运用发动时的各项数据,如下所示。

【性能优化】使用Perfetto定位应用启动性能的瓶颈

android_startup {
  startup {
    startup_id: 1
    startup_type: "warm"
    package_name: "com.xxx.xxx.weather"
    process_name: "com.xxx.xxx.weather"
    process {
      name: "com.xxx.xxx.weather"
      uid: 1000
      pid: 3376
    }
    zygote_new_process: false
    activity_hosting_process_count: 1
    event_timestamps {
      intent_received: 100680138137
      first_frame: 102167532928
    }
    to_first_frame {
      dur_ns: 1487394791
      dur_ms: 1487.394791
      main_thread_by_task_state {
        running_dur_ns: 1316606193
        runnable_dur_ns: 34121303
        uninterruptible_sleep_dur_ns: 20429636
        interruptible_sleep_dur_ns: 84415940
        uninterruptible_io_sleep_dur_ns: 12221457
        uninterruptible_non_io_sleep_dur_ns: 8208179
      }
      time_activity_manager {
        dur_ns: 16070209
        dur_ms: 16.070209
      }
      time_activity_start {
        dur_ns: 97578437
        dur_ms: 97.578437
      }
      time_activity_resume {
        dur_ns: 833413073
        dur_ms: 833.413073
      }
      time_choreographer {
        dur_ns: 481555469
        dur_ms: 481.555469
      }
      time_inflate {
        dur_ns: 1241538748
        dur_ms: 1241.538748
      }
      time_get_resources {
        dur_ns: 6173178
        dur_ms: 6.173178
      }
      time_verify_class {
        dur_ns: 1675365
        dur_ms: 1.675365
      }
      time_gc_total {
        dur_ns: 82049531
        dur_ms: 82.049531
      }
      time_dlopen_thread_main {
        dur_ns: 15522344
        dur_ms: 15.522344
      }
      time_lock_contention_thread_main {
        dur_ns: 4711976
        dur_ms: 4.711976
      }
      time_jit_thread_pool_on_cpu {
        dur_ns: 375033124
        dur_ms: 375.033124
      }
      time_gc_on_cpu {
        dur_ns: 81314427
        dur_ms: 81.314427
      }
      jit_compiled_methods: 218
      other_processes_spawned_count: 6
    }
    verify_class {
      name: "com.xxx.xxx.weather.service.VoiceActionManager"
      dur_ns: 1675365
    }
    dlopen_file: "/system/priv-app/Weather/Weather.apk!/lib/arm64-v8a/libffavc.so"
    dlopen_file: "/system/priv-app/Weather/Weather.apk!/lib/arm64-v8a/libpag.so"
    dlopen_file: "/vendor/lib64/hw/android.hardware.graphics.mapper@4.0-impl-qti-display.so"
    dlopen_file: "libadreno_utils.so"
    dlopen_file: "/vendor/lib64/hw/android.hardware.graphics.mapper@3.0-impl-qti-display.so"
    dlopen_file: "/vendor/lib64/hw/gralloc.msmnile.so"
    dlopen_file: "libadreno_app_profiles.so"
    dlopen_file: "libEGL_adreno.so"
    system_state {
      dex2oat_running: false
      installd_running: false
      broadcast_dispatched_count: 0
      broadcast_received_count: 0
      most_active_non_launch_processes: "media.codec"
      most_active_non_launch_processes: "app_process"
      most_active_non_launch_processes: "media.hwcodec"
      most_active_non_launch_processes: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"
      most_active_non_launch_processes: "/system/bin/audioserver"
      installd_dur_ns: 0
      dex2oat_dur_ns: 0
    }
slow_start_reason: "GC Activity"
slow_start_reason: "Main Thread - Time spent in Running state"
slow_start_reason: "Time spent in view inflation"
  }
}

android_startup是一个用于记录和剖析Android运用发动功能的数据结构,它包含了运用发动过程中的各种信息,例如发动类型、发动时刻、发动原因、发动依赖、体系状况等。

android_startup的内容是一个protobuf格局的文本,它表明了一个名为com.xxx.xxx.weather的气候运用的发动数据。其间,最重要的是最终一段的slow_start_reason,它向咱们展现了运用可能导致发动缓慢的原因,在第三节咱们会要点剖析。

其他字段的含义如下:

startup_id: 是一个仅有标识符,表明这是榜首次发动;

startup_type: 是一个枚举类型,表明这是一个warm(温)发动,即运用进程现已存在,但没有活动在前台;

package_nameprocess_name表明运用的包名和进程名;

process: 表明运用进程的信息,包含称号、用户标识符(uid)和进程标识符(pid);

zygote_new_process: 表明是否通过zygote创立了新进程,这儿为false;

activity_hosting_process_count: 表明有多少个活动保管在这个进程中,这儿为1;

event_timestamps: 表明各种事件发生的时刻戳,例如intent_received表明收到发动目的的时刻,first_frame表明显现榜首帧画面的时刻;

to_first_frame: 表明从收到发动目的到显现榜首帧画面所花费的时刻和细节,包含总时刻、主线程各种状况的时刻、各种操作的时刻、各种资源的运用状况等;

verify_class: 表明验证类加载的信息,包含类名和时刻;

dlopen_file: 表明翻开共享库文件的信息,包含文件名;

system_state: 表明体系状况的信息,包含是否有dex2oat或installd在运行、是否有播送发送或接收、哪些非发动进程最活泼等;

Perfetto实践

发动时触发GC

现象slow_start_reason中呈现”GC Activity”,表明在发动阶段GC活动拖慢了运用程序的发动。

剖析:点击【show timeline】返回到Perfetto时刻轴界面。在发动时刻轴中,能够看到有一个线程名为HeapTaskDaemon,他便是运用程序的GC线程,在发动阶段活泼了约100ms左右,导致activityResume的时刻轴也被拉长100ms。为防止是偶发现现象,进行了屡次丈量,发现该运用发动时必定会触发GC活动。如图所示:

【性能优化】使用Perfetto定位应用启动性能的瓶颈

原因:依据时刻轴向前剖析,发现该运用在发动阶段会加载一个特别字体,该字体约13MB,通过与运用的开发交流,确认该字体现已移动到体系层,运用层不必加载该字体。移除字体后,运用发动时不再100%触发GC。

主线程耗时操作

现象slow_start_reason呈现 “Main Thread – Time spent in Running state”,表明发动阶段,主线程中履行较多的耗时操作。

原因:这种状况在运用开发时很常见,一些跨进程的获取数据的操作,运用开发人员会很天然的将其放在主线程Activity的OnCreate或onStart办法下履行,这些IPC办法虽然不至于触发ANR,可是会拖慢运用的发动,应该放置到线程池或协程中履行。

OpenDexFilesFromOat耗时

现象slow_start_reason呈现”Main Thread – Time spent in OpenDexFilesFromOat*”,表明发动阶段,花费了较多时刻在读取dex文件上。

【性能优化】使用Perfetto定位应用启动性能的瓶颈

原因:这种状况在车载Android体系中较为常见。这可能是因为体系为了加速发动速度,修改了体系中dex2oat的流程,导致此现象,耗时不多的话能够疏忽。

这儿仅仅说是“可能”,是因为现在的车载OS为了快速发动,对原生Android的修改十分多,咱们需求结合自身的实践状况再做详细剖析。

接连多帧制作超时

现象: 某个运用在Perfetto中的发动时刻并不长,大约在1.3s左右,可是运用拆帧法后,发现该运用发动后会有些许卡顿,导致实践发动时刻拉长到2.1s。表现在Perfetto上如下所示,在榜首帧制作完毕后,后续2、4、5帧的制作时刻都超过了150ms。

【性能优化】使用Perfetto定位应用启动性能的瓶颈

剖析:Perfetto给出的帧制作时刻轴显现大部分时刻花费在View的Layout上,这说明在首帧制作完毕后,又触发了屡次页面重绘。

原因:通过代码结合运用的日志,发现该运用在发动时会运用空数据改写一次页面,然后会从IPC接口中再获取一次数据更新页面,而且因为代码缺点,数据改写会接连履行4次,导致了该状况。修改缺点代码,首帧之后就不会再发生接连制作超时的状况了。

参考资料

developer.android.com/topic/perfo…