Android性能优化笔记二

1 阅读6分钟

一.启动速度优化.

1.android启动优化

(1).非首屏必须的SDK(友盟,推送,分享),延迟到首页加载完成或空闲时初始化.

public class MyApplication extends Application {
 MessageQueue mainQueue = Looper.getMainLooper().getQueue();
 mainQueue.addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                // 3. 执行延迟初始化任务(非核心 SDK)
                initThirdPartySDKs();
          
                // 4. 返回 false:执行一次后自动移除
                //    返回 true:每次空闲都会执行(一般不推荐)
                return false;
            }
        });
}

(2).创建一个baselineProfile 模块,BaselineProfileGenerator.generate定义核心的用户旅程(应用里最高频、最关键的几条路径),它本质上是在模拟真实用户在应用中最常用、最重要的操作路径。ART 运行时(Android Runtime)会根据这些路径,在应用安装时预编译相关代码,从而提升启动和渲染性能。

  • 定义核心的用户旅程,如下:

@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() {
        // The application id for the running build variant is read from the instrumentation arguments.
        rule.collect(
            packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
                ?: throw Exception("targetAppId not passed as instrumentation runner arg"),

            // See: https://d.android.com/topic/performance/baselineprofiles/dex-layout-optimizations
            includeInStartupProfile = true
        ) {
            // This block defines the app's critical user journey. Here we are interested in
            // optimizing for app startup. But you can also navigate and scroll through your most important UI.

            // Start default activity for your app
            pressHome()
            // 1. 等待冷启动完成,显示首帧
            startActivityAndWait()
            // 2. 模拟用户核心操作,比如点击登录按钮
            device.findObject(By.text("登录")).click()
            // 3. 等待登录完成
            device.waitForIdle()


            // 2. 等待主页加载完成
            device.waitForIdle()

            // 3. 模拟搜索操作
            device.findObject(By.res("com.your.app.package:id/search_box")).click()
            device.findObject(By.res("com.your.app.package:id/search_input"))
                .setText("手机")
            device.findObject(By.text("搜索")).click()
            device.waitForIdle()

            // 4. 等待搜索结果列表出现,并滚动浏览
            val searchResultList = device.findObject(By.res("com.your.app.package:id/result_list"))
            searchResultList.fling(Direction.DOWN) // 快速滑动
            device.waitForIdle()
            searchResultList.swipe(Direction.DOWN, 0.5f) // 慢速滑动[reference:20]

            // 5. 点击进入第一个商品详情页
            device.findObject(By.text("商品名称")).click()
            device.waitForIdle()

            // TODO Write more interactions to optimize advanced journeys of your app.
            // For example:
            // 1. Wait until the content is asynchronously loaded
            // 2. Scroll the feed content
            // 3. Navigate to detail screen

            // Check UiAutomator documentation for more information how to interact with the app.
            // https://d.android.com/training/testing/other-components/ui-automator
        }
    }
}

  • 在 Android Studio 的 Run Configuration 中,选择并运行 generateBaselineProfile 这个任务。生成成功后,你会在 app/src/main/  目录下看到 baseline-prof.txt 文件。它会随着应用一起打包,在安装时指导系统进行 AOT 预编译。
2.验证启动优化

(1).通过Adb命令查看界面启动时间

① 命令格式 : adb shell am start -W 包名/完整 Activity 类名 ;

② 本次命令 : adb shell am start -W kim.hsl.rtmp/kim.hsl.rtmp.MainActivity , 其中 kim.hsl.rtmp 是包名 , kim.hsl.rtmp.MainActivity 是完整的类名

冷启动时间: image.png

热启动时间:

image.png

二.内存优化.

1.内存泄漏检测与修复 使用 LeakCanary 自动检测 Activity/Fragment/ViewModel 泄漏;常见泄漏场景:静态持有 Context、未取消注册监听、Handler 匿名内部类、单例持有 Activity 引用

2.资源对象未关闭引起的内存泄露.

Cursor,Socket在Activity销毁的时候,没有关闭。

EventBus或者广播在Activity销毁的时候,没有注销。

Handler发送延时消息导致的内存泄漏

Handler是通过内部类来创建的,内部类持有外部类Activity的引用,也就是说Handler持有Activity的引用,当通过Handler发送延时Message的时候,因为Message中target就是Handler对象,也就是Message持有handler引用,而handle持有Activity引用,所以当我们关闭Activity的时候,activity的引用也是没有办法被回收。

3.onDraw内部创建对象,做耗时操作导致内存抖动,泄漏

4.大图导致的内存OOM

使用 Glide/Coil 等图片库自动管理缓存;根据设备屏幕尺寸加载适当分辨率的图片;优先使用 WebP 格式,或者使用熊猫压缩.

验证方法
  • Android Studio Memory Profiler:实时监控内存曲线,拍摄 Heap Dump 分析
  • LeakCanary:自动检测并报告内存泄漏
  • MAT (Memory Analyzer Tool) :深度分析 Heap Dump,生成 Leak Suspects 报告

验证步骤

  1. 使用 Memory Profiler 记录页面进入 → 操作 → 退出后的内存曲线
  2. 反复进出页面多次,观察内存是否回落至初始水平
  3. 拍摄 Heap Dump,检查是否存在不应该存活的对象
  4. 对比优化前后的内存峰值GC 频率 前后对比示例

优化前:反复进出页面 10 次,内存增长 15MB → 优化后:增长 < 2MB,无泄漏

三.UI渲染优化.

1.减少布局层级 使用 ConstraintLayout 替代多层嵌套的 LinearLayout/RelativeLayout

2.使用merge标签

在 include 布局时使用 <merge> 减少多余根布局

3.ViewStub 延迟加载

对非首屏或条件性显示的布局使用 ViewStub 懒加载

验证方法

工具

  • GPU 过度绘制调试:开发者选项 → “调试 GPU 过度绘制” → “显示过度绘制区域”

    • 蓝色:1 次(理想)| 绿色:2 次(可接受)| 粉色:3 次(需优化)| 红色:4+ 次(严重)
  • Layout Inspector:查看 View 树层级和每个 View 的属性

  • GPU 渲染模式分析:开发者选项中开启,观察每帧耗时直方图

验证步骤

  1. 开启 GPU 过度绘制,截图记录优化前的颜色分布
  2. 使用 Layout Inspector 统计最大布局深度
  3. 优化后重复上述操作,对比过度绘制区域减少和层级降低

前后对比示例

优化前:布局深度 7 层,大量红色过度绘制区域 → 优化后:深度 3 层,仅蓝绿色区域

四.APK体积优化

1.资源裁剪

启用 shrinkResources true,自动移除未引用的资源

2.图片格式优化

如果是png或者jpg可以熊猫压缩或者替换成VecotrDrawable或者.webp格式

3.ABI 过滤(so库过滤)

根据目标设备过滤不需要的 ABI(如只保留 arm64-v8a)

4.lint移除无用的资源,和废弃代码

验证方法

工具

  • APK Analyzer:Android Studio 内置,查看 APK/AAB 各目录和文件占比
  • 命令行unzip -l app-release.apk | sort -nr | head -40 快速定位大文件

验证步骤

  1. 使用 APK Analyzer 导出优化前的体积报告
  2. 记录 APK、AAB、各 ABI 包、dex、res、lib 的大小
  3. 实施优化后导出新报告,逐项对比

前后对比示例

优化前:APK 大小 48MB → 优化后:18MB(AAB 分发后用户下载约 12MB)

五. 卡顿优化

1.避免主线程耗时

文件 I/O、网络请求、数据库查询、复杂计算移到子线程

2.过度绘制治理 见第三节“布局渲染优化”

3.列表滑动优化

RecyclerView 复用 ViewHolder;避免在 onBindViewHolder 中做耗时操作;使用 DiffUtil 精准刷新

验证方法

工具

  • 开发者选项 → GPU 呈现模式分析:以条形图显示每帧耗时,超过绿线表示掉帧
  • Choreographer:代码层监控帧率,注册 FrameCallback 计算每秒帧数
  • Systrace / Perfetto:系统级 Trace,定位具体哪段代码导致主线程阻塞
  • BlockCanary:检测主线程卡顿,定位卡顿时的方法调用栈

验证步骤

  1. 在相同测试路径下(如滑动列表 10 次),记录优化前的平均 FPS 和掉帧次数
  2. 使用 Systrace 抓取 Trace,定位耗时最多的主线程操作
  3. 优化后重复测试,对比 FPS 和掉帧率

前后对比示例

优化前:滑动列表平均 42fps,掉帧率 18% → 优化后:平均 58fps,掉帧率 2%