分类 安卓 下的文章

Android 性能优化--Gradle 编译速度优化

1. 保持工具最新

Android 工具几乎每次更新都会获得构建优化和新功能,保持最新版本可以加快构建速度
Android Gradle 插件
Android Studio 和 SDK 工具

2. 使用 KSP 代替 kapt

Kapt(Kotlin 注释处理工具)允许您将 Java 注释处理器与 Kotlin 代码结合使用,即使这些处理器没有对 Kotlin 的特定支持。这是通过从 Kotlin 文件生成 Java 存根来完成的,然后处理器可以读取这些存根。这种存根生成是一项昂贵的操作,并且对构建速度有重大影响。
KSP(Kotlin 符号处理)是 kapt 的 Kotlin 优先替代品。KSP 直接分析 Kotlin 代码,速度提高了 2 倍。它还可以更好地理解 Kotlin 的语言结构。
建议尽可能从 kapt 迁移到 KSP。在大多数情况下,此迁移只需要更改项目的构建配置。
如果你的项目使用了数据绑定(dataBinding)暂无无法迁移到KSP,尚未计划对 KSP 支持 , 可以通过将dataBinding的使用隔离到单独的模块来减轻 kapt 对构建的影响。
许多流行的库(比如 GlideRoom 等)都已经具有 KSP 支持,还有一些正在添加支持,比如 Dagger
可参考 迁移KSP文档

3. 避免编译不必要的资源

避免编译和打包未测试的资源,例如其他语言本地化和屏幕密度资源。

android {
    productFlavors {
        create("dev") {
            // 如下配置显示 dev 渠道包,只打包 en 语言和 xxhdpi 屏幕密度的资源  
            resourceConfigurations("en", "xxhdpi")
}   }   }

或者如下修改

android {
    applicationVariants.configureEach { variant ->
        if (variant.buildType.name == "debug") {
            variant.mergedFlavor.resourceConfigurations.clear()
            variant.mergedFlavor.resourceConfigurations.add("zh-rCN")
            variant.mergedFlavor.resourceConfigurations.add("xxhdpi")
}     }    }

4. 优化 repositories maven 排序

如下图,可以查看到需要的依赖库下载结果

可以将多数能找到放最前面,比如在国内可以使用如下示例

repositories {
    maven { url 'https://maven.aliyun.com/repository/google' }
    maven { url 'https://maven.aliyun.com/repository/public' }
    maven { url 'https://your.private.io/repository' }
    mavenCentral()
    google()
}

5. 在调试构建中使用静态构建配置值

使用动态版本代码、版本名称、资源或任何其他更改清单文件的构建逻辑,
每次您想要运行更改时,都需要完整的应用程序构建,即使实际的更改可能只需要热交换。
如果 Release 版本确实需要,我们可以只跟改配置,使 debug 版本不生效。
比如在 BuildConfig 中版本号使用动态时间戳(精确到时分秒),导致小的修改,大编译,严重影响Rebuild速度。
在 Gradle 8.0 默认不生成 BuildConfig.java,进而无法直接引用 BuildConfig 中配置。

6. 使用静态依赖版本

在build.gradle文件中声明依赖项时,避免使用动态版本号
(末尾带有加号的版本号,如'androidx.appcompat:appcompat:1.+')
可能会导致意外的版本更新、难以解决版本差异以及 Gradle 检查更新导致的构建速度变慢。

7. 创建库模块

查找可以转换为 Android 库模块的代码。
以这种方式模块化您的代码,允许构建系统仅编译您修改的模块,并缓存这些输出以供将来构建。
模块化还可以使 并行项目执行更加有效

8. 为自定义构建逻辑创建任务

编译项目(Make Project)完成后,如下图,点击 Build Analyzer

会跳转到编译分析栏,会显示,Config 及 Task 执行时间,如下图

切换 OverviewTasks,可以显示各个 Task 执行耗时

创建构建配置文件后,如果构建配置文件显示相对较长的构建时间花费在 Configuration 阶段,
请检查您的build.gradle脚本,并查找要包含在自定义 Gradle 任务中的代码,
通过将一些构建逻辑移至任务中,您可以帮助确保任务仅在需要时运行,可以缓存结果以供后续构建使用

9. 将图像转换为 WebP

WebP 可以提供比 JPEG 或 PNG 更好的压缩。
减少图像文件大小而不必执行构建时压缩可以加快构建速度,特别是当您的应用程序使用大量图像资源时。
使用 Android Studio 可以 轻松 将图像转换为 WebP

10. 禁用 PNG 处理

如果您不将 PNG 图像转换为 WebP,您仍然可以通过在每次构建应用程序时禁用自动图像压缩来加快构建速度。
如果您使用的是 Android Gradle 插件 3.0.0 或更高版本,则默认情况下,“debug”构建类型会禁用 PNG 处理。
如果 Release 也需要禁用 PNG 处理,可以参见如下修改

android {
    buildTypes {
        release {
            crunchPngs false
            //  isCrunchPngs = false //for kotlin
}   }   }

11. 挑选 JVM 垃圾收集器

通过配置 Gradle 使用的最佳 JVM 垃圾收集器可以提高构建性能。
虽然 JDK 8 默认配置为使用并行垃圾收集器,但 JDK 9 及更高版本配置为使用 G1 垃圾收集器
建议 使用并行垃圾收集器测试您的 Gradle 构建,需要在 gradle.properties 添加如下修改
org.gradle.jvmargs=-XX:+UseParallelGC 或者 org.gradle.jvmargs=-Xmx4608M -XX:+UseParallelGC

12. 增加 JVM 堆大小

在 编译分析 栏,我们可以看到编译过程中,GC 花费的构建时间,
如果 超过 超过 15% ,我们可以增加 Java 虚拟机 (JVM) 堆大小
需要在 gradle.properties 中修改限制 4、6 或 8 GB ,参考如下
org.gradle.jvmargs=-Xmx6g

13. 使用非传递 R 类

使用非传递R类可以更快地构建具有多个模块的应用程序。
这样做可以确保每个模块的R类仅包含对其自身资源的引用,而不从其依赖项中提取引用,从而有助于防止资源重复。
这会带来更快的构建速度以及避免编译的相应好处。
这是 Android Gradle 插件 8.0.0 及更高版本中的默认行为。

14. 使用非常量 R 类

在APP模块和自动化测试时,使用非常量的 R.class 可以提高 Java 编译的增量并允许更精确的资源收缩。
在Library模块,R.class 一直不是常量,只有在打包APP或者自动化测试时,资源才被编号。
这是 Android Gradle Plugin 8.0.0 及更高版本中的默认行为,也就是如下代码会编译出错.

switch (viewId) {
    case R.id.bt_back:
        dosth1;
        break;
    case R.id.bt_enter:
        dosth2();
        break;
}

// 可以替换为  
if (viewId == R.id.bt_back) dosth1;
else if (viewId == R.id.bt_enter) dosth2();

15. 禁用 Jetifier 标志

由于大多数项目直接使用 AndroidX 库,因此您可以删除 Jetifier标志以获得更好的构建性能。
gradle.properties 修改如下即可
android.enableJetifier=false

16. 使用配置缓存

配置缓存 允许 Gradle 记录有关构建任务图的信息并在后续构建中重用它,不必再次重新配置整个构建。
可以在 gradle.properties 修改如下

org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn

注意可能会存在冲突的情况,参见 Gradle#13490
启用配置缓存后,第一次运行项目时,构建输出会显示
Calculating task graph as no configuration cache is available for tasks
在后续运行期间,构建输出显示
Reusing configuration cache


参考文档
https://developer.android.com/build/optimize-your-build
https://developer.android.com/build/migrate-to-ksp

Android Studio Bot 下载使用

本文最新更新地址 https://gitee.com/chenjim/chenjimblog
  • 下载 Android Studio Hedgehog
    当前的新版本是 2023.1.1 Canary 10
  • 找到Studio Bot: View->Tool Windows->Studio Bot,或者下图
  • 登录 Google 账号,注意当前限制只能US的账户使用 !!

    查看自己 Google 账户服务地区 https://policies.google.com/terms
    如果没有 US 账户,可以尝试开启全局 Proxy重新注册账户
  • 注意配置 Android Studio 的 Proxy,参考如下
  • 完成以上步骤后,我们就可以开始我们的 Studio Bot 体验之旅了

参考文章
https://blog.csdn.net/dai_jiawei/article/details/130702636
https://developer.android.com/studio/preview/studio-bot

Android Compose Button defaultButtonColors

本文最新更新地址 <https://gitee.com/chenjim/chenjimblog

发现问题

最近看 Android Compose 相关资料发现如下代码

    colors = defaultButtonColors(
        backgroundColor = if (count > 5) Color.Green else Color.White
    )

原文地址 https://developer.android.com/jetpack/compose/preview?hl=zh-cn

编译会出现异常 Unresolved reference: defaultButtonColors


解决问题

  1. 以上是中文页面,对应的 英文页面 ,当前(20230701) 已经没有相应的说明
    新版 compose preview 介绍参考 https://developer.android.com/jetpack/compose/tooling/previews
  2. 在新版本中,本文使用的是 implementation 'androidx.compose.material3:material3:1.1.1'
    已经没有 ButtonConstants.defaultButtonColorsbackgroundColor
    可以使用如下代码替换

        colors = ButtonDefaults.buttonColors(
            containerColor = if (count > 5) Color.Green else Color.Gray
        )
  3. android-compose-codelabs 示例中,也均使用的是 ButtonDefaults.buttonColors

参考自 https://stackoverflow.com/questions/64376333

Perfetto 官方链接地址 https://github.com/google/perfetto/
  • 开启Android的trace跟踪服务
    Perfetto 是基于 Android 的系统追踪服务, 这个配置在 Android11(R) 之后是默认打开的,但是如果你是 Android 9 ( P ) 或者 10 ( Q ) ,那么就需要手动设置一下相应的 prop 属性。
    adb shell setprop persist.traced.enable 1

  • 使用 adb 抓取
    `adb shell perfetto -o /data/misc/perfetto-traces/trace_file.perfetto-trace -t 60s sched freq idle am wm gfx view binder_driver hal dalvik
    camera input res memory`
    -o /data/misc/perfetto-traces/trace_file.perfetto-trace 输出trace的路径
    -t 60s 最多抓取时长,可以Ctrl+C停止
    sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory 要抓取相关模块
    更多配置说明,参见文档:https://perfetto.dev/docs/concepts/config
    使用配置文件,通过adb抓取,参考如下
    adb push atrace.cfg /data/local/tmp/atrace.cfg
    adb shell "cat /data/local/tmp/atrace.cfg | perfetto --txt -c - -o /data/misc/perfetto-traces/trace.perfetto-trace"
    atrace.cfg 是配置信息,默认最多只抓取10秒,可以修改更长
    可以Ctrl+C停止,需执行adb shell ,再执行 cat /data/local/tmp/atrace.cfg | perfetto --txt -c - -o /data/misc/perfetto-traces/trace.perfetto-trace
    更多配置参见 https://github.com/google/perfetto/blob/master/test/configs
    然后 adb pull /data/misc/perfetto-traces/trace.perfetto-trace ,在 https://perfetto.dev/ 导入文件分析

  • 通过 perfetto 网页抓取
    打开 https://ui.perfetto.dev/#!/record
    "Add ADB Device" 选择手机设备
    如下图,有一些参数配置,根据自己的需要添加修改

    最后右上角 "Start Recording"

  • 直接在手机上抓取
    开启开发者模式:系统设置--关于手机--连续点击。部分机型可网上搜索开启方式。
    进入开发者模式,选择"系统跟踪",里面有一些相关设置
    类别,可以选择要抓取的数据种类
    抓取的trace信息会直接存储在手机 /data/local/traces,"查看跟踪文件",可以分享
    此方式比较适合没有电脑,随时抓取


  • 熟悉 perfetto 快捷键,会有事半功倍效果

  • 在 Android 上,使用adb shell perfetto 时有一些注意事项

    • 最好先执行adb shell,然后执行 perfetto 相关命令,因为在某些情况 Ctrl+C不通过ADB传播而无法停止.
    • 在 Android 12 之前的非 root 设备上,由于过度限制的 SELinux 规则,配置只能通过 (cat config | adb shell perfetto -c -) 传递 。由于 Android 12 /data/misc/perfetto-configs可用于存储配置。
    • 在 Android 10 之前的设备上,adb 无法直接拉取 /data/misc/perfetto-traces. 可以用 adb shell cat /data/misc/perfetto-traces/trace > trace变通。
    • 当捕获较长的跟踪时,例如在基准测试或 CI 的上下文中,使用 PID=$(perfetto --background)然后kill $PID停止。

  • 如果遇到curl超时无法访问某些地址,可能需要修改,参见如下

    diff --git a/tools/heap_profile b/tools/heap_profile
    @@ -243,8 +243,9 @@ def download_or_get_cached(file_name, url, sha256):
       if needs_download:
         # Either the filed doesn't exist or the SHA256 doesn't match.
         tmp_path = bin_path + '.tmp'
    +    proxy_7890='http://127.0.0.1:7890'
         print('Downloading ' + url)
    -    subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
    +    subprocess.check_call(['curl', '-f', '-L', '-#','-x',proxy_7890, '-o', tmp_path, url])
         with open(tmp_path, 'rb') as fd:
           actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
         if actual_sha256 != sha256:

  • 一个参考配置示例如下,更多可参见 https://github.com/google/perfetto/blob/master/test/configs

    buffers: {
        size_kb: 707200
        fill_policy: RING_BUFFER
    }
    buffers: {
        size_kb: 707200
        fill_policy: RING_BUFFER
    }
    data_sources: {
        config {
            name: "linux.process_stats"
            target_buffer: 1
            process_stats_config {
                scan_all_processes_on_start: true
                proc_stats_poll_ms: 1000
            }
        }
    }
    data_sources: {
        config {
            name: "android.log"
            android_log_config {
            }
        }
    }
    data_sources: {
        config {
            name: "android.surfaceflinger.frametimeline"
        }
    }
    data_sources: {
        config {
            name: "linux.sys_stats"
            sys_stats_config {
                meminfo_period_ms: 1000
                vmstat_period_ms: 1000
                stat_period_ms: 1000
                stat_counters: STAT_CPU_TIMES
                stat_counters: STAT_FORK_COUNT
            }
        }
    }
    data_sources: {
        config {
            name: "android.heapprofd"
            target_buffer: 0
            heapprofd_config {
                sampling_interval_bytes: 4096
                shmem_size_bytes: 8388608
                block_client: true
            }
        }
    }
    data_sources: {
        config {
            name: "android.java_hprof"
            target_buffer: 0
            java_hprof_config {
            }
        }
    }
    data_sources: {
        config {
            name: "linux.ftrace"
            ftrace_config {
                ftrace_events: "sched/sched_switch"
                ftrace_events: "power/suspend_resume"
                ftrace_events: "sched/sched_wakeup"
                ftrace_events: "sched/sched_wakeup_new"
                ftrace_events: "sched/sched_waking"
                ftrace_events: "power/cpu_frequency"
                ftrace_events: "power/cpu_idle"
                ftrace_events: "sched/sched_process_exit"
                ftrace_events: "sched/sched_process_free"
                ftrace_events: "task/task_newtask"
                ftrace_events: "task/task_rename"
                ftrace_events: "lowmemorykiller/lowmemory_kill"
                ftrace_events: "oom/oom_score_adj_update"
                ftrace_events: "ftrace/print"
                ftrace_events: "binder/*"
                atrace_categories: "input"
                atrace_categories: "gfx"
                atrace_categories: "view"
                atrace_categories: "webview"
                atrace_categories: "camera"
                atrace_categories: "dalvik"
                atrace_categories: "power"
                atrace_categories: "wm"
                atrace_categories: "am"
                atrace_categories: "ss"
                atrace_categories: "sched"
                atrace_categories: "freq"
                atrace_categories: "binder_driver"
                atrace_categories: "aidl"
                atrace_categories: "binder_lock"
                atrace_apps: "*"
            }
        }
    }
    duration_ms: 300000
    flush_period_ms: 30000
    incremental_state_config {
        clear_period_ms: 5000
    }
    write_into_file: true

相关文章连接
Android基于perfetto分析native内存泄露

官方文档(可在Chome直接翻译) https://perfetto.dev/docs/data-sources/native-heap-profiler
示例 raw-trace 资源地址 https://download.csdn.net/download/CSqingchen/87321798
本文示例是windows,这里使用了python工具,在Linux和mac同样适用
  1. 首先安装python3环境,参见 https://www.python.org/downloads/
  2. 下载 perfetto ,地址在 https://github.com/google/perfetto
    后面需要用到这里的 perfetto\tools\heap_profile 本文放在了目录 D:\tools\perfetto
  3. 抓取一次某个应用的内存命令如下,注意提前关闭其它adb程序,如AS
    python D:\tools\perfetto\tools\heap_profile -n com.app.package.name

    这里只能抓到一次内存的快照,如果想连续记录多次内存的数据需要能Root手机
    首次运行会有如下联网下载
    Downloading https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v32.1/windows-amd64/traceconv.exe
    如果下载失败,请 从连接下载 并参考里面说明放文件。
    或者参考如下,修改tools/heap_profile 中 curl 下载方式

    diff --git a/tools/heap_profile b/tools/heap_profile
    @@ -243,8 +243,9 @@ def download_or_get_cached(file_name, url, sha256):
       if needs_download:
         # Either the filed doesn't exist or the SHA256 doesn't match.
         tmp_path = bin_path + '.tmp'
    +    proxy_7890='http://127.0.0.1:7890'
         print('Downloading ' + url)
    -    subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
    +    subprocess.check_call(['curl', '-f', '-L', '-#','-x',proxy_7890, '-o', tmp_path, url])
         with open(tmp_path, 'rb') as fd:
           actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
         if actual_sha256 != sha256:
  4. 连续抓取多次内存快照
    adb shell killall -USR1 heapprofd 需要Root权限,上一步骤不要停止
    每执行一次,上一步会记录一次
    这里我上传了一份自己抓的数据,下载地址 https://download.csdn.net/download/CSqingchen/87321798
  5. 使用 perfetto 分析抓到的 raw-trace 文件,即从 https://ui.perfetto.dev/ 打开 raw-trace 文件

    通过点击 方块,对比不用时刻的内存。
    可以看到第一个大块有内存一直上升,结合其中的栈堆,分析并解决即可。
    下载资源 raw-trace.02 是解决问题后,抓取的tarce,可以看到问题已解决
  6. 相关问题思考

    • AndroidStudio Profile 也可以Dump内存,但内存分析没有这个直接
    • LeakCanary 也可以分析内存,主要是用来分析View视图,没法分析这个native内存的数据