23、Android性能优化(上):布局优化、内存优化、卡顿检测与Systrace使用
性能优化这个话题,说实话,是每个Android工程师绕不开的坎。我做了这么多年系统层开发,见过太多应用因为性能问题被用户吐槽。今天咱们就聊聊布局优化、内存优化、卡顿检测这几个硬骨头,还有Systrace这个神器怎么用。
一、布局优化:从渲染源头下手
布局优化说白了就是让界面加载更快、更流畅。你想想看,用户点开一个页面,如果卡个一两秒才出来,第一印象就毁了。
1. 减少层级嵌套
我个人习惯,写布局时尽量保持层级在3层以内。为什么?因为每多一层,measure和layout就要多走一遍,CPU开销翻倍。
核心原则:能用RelativeLayout解决的就别用LinearLayout嵌套。能用ConstraintLayout的就别用多层RelativeLayout。
我在项目中遇到过这样一个案例:一个列表项布局嵌套了7层,滑动时明显掉帧。后来用ConstraintLayout重构,层级降到2层,帧率从40fps直接拉回到58fps。
2. 使用和复用布局
复用布局时,
<!-- 父布局 -->
<LinearLayout>
<include layout="@layout/common_header"/>
</LinearLayout>
<!-- common_header.xml -->
<merge xmlns:android="...">
<TextView .../>
<Button .../>
</merge>
嗯,这里要注意:
3. ViewStub延迟加载
有些布局不是一开始就需要显示的,比如错误提示、广告位。用ViewStub,只有inflate时才真正加载。
<ViewStub
android:id="@+id/stub_error"
android:layout="@layout/layout_error"
android:inflatedId="@+id/error_layout"/>
// 使用时
ViewStub stub = findViewById(R.id.stub_error);
stub.inflate();
小技巧:ViewStub一旦inflate后,自身就被替换掉了。所以不要重复inflate,否则会抛异常。
二、内存优化:别让OOM找上门
内存优化这块,说白了就是管好你的对象生命周期。我曾经接手过一个项目,内存泄漏严重到每次打开相册就崩。查了三天,最后发现是一个静态变量持有了Activity引用。
1. 避免静态变量持有Activity
静态变量的生命周期和应用一样长。如果你把Activity赋值给静态变量,Activity退出后无法被GC回收。
// 错误示例
public class Utils {
private static Activity sActivity;
public static void setActivity(Activity activity) {
sActivity = activity;
}
}
// 正确做法:使用弱引用
public class Utils {
private static WeakReference<Activity> sActivityRef;
public static void setActivity(Activity activity) {
sActivityRef = new WeakReference<>(activity);
}
}
2. 使用LeakCanary检测泄漏
LeakCanary是我每次新项目必加的工具。它能在发生内存泄漏时自动弹出通知,告诉你哪个对象泄漏了、引用链是什么。
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
}
加完依赖,什么都不用配。运行App,如果有泄漏,通知栏会提示。点进去就能看到完整的引用链。
注意:LeakCanary只建议在debug版本使用。release版本一定要去掉,否则会影响性能。
3. Bitmap内存管理
Bitmap是内存大户。我见过一个App,加载一张4K图片直接吃掉30MB内存。优化方法其实很简单:
- 使用BitmapFactory.Options的inSampleSize进行采样压缩
- 使用LruCache做内存缓存
- 及时调用bitmap.recycle()(Android 3.0之前需要手动回收)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.big_image, options);
// 计算采样率
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
int scaleFactor = Math.min(imageWidth / targetWidth, imageHeight / targetHeight);
options.inJustDecodeBounds = false;
options.inSampleSize = scaleFactor;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.big_image, options);
三、卡顿检测:找到元凶
卡顿的本质是掉帧。Android每16.6ms刷新一帧,如果某帧的绘制时间超过这个阈值,就会丢帧。我习惯用两种方式检测:
1. 使用Choreographer监听帧率
Choreographer是系统级的帧率控制器。我们可以注册FrameCallback来监听每一帧的绘制时间。
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
long lastFrameTimeNanos = 0;
@Override
public void doFrame(long frameTimeNanos) {
if (lastFrameTimeNanos == 0) {
lastFrameTimeNanos = frameTimeNanos;
}
long diffMs = (frameTimeNanos - lastFrameTimeNanos) / 1000000;
if (diffMs > 16.6f) {
// 掉帧了
Log.w("FrameMonitor", "掉帧: " + diffMs + "ms");
}
lastFrameTimeNanos = frameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
}
});
2. 使用BlockCanary检测主线程耗时
BlockCanary是另一个好用的工具。它通过Looper的Printer机制,检测主线程中超过指定阈值的耗时操作。
Looper.getMainLooper().setMessageLogging(new Printer() {
@Override
public void println(String x) {
if (x.startsWith(">>>>> Dispatching to")) {
// 开始执行
} else if (x.startsWith("<<<<< Finished to")) {
// 执行结束,计算耗时
}
}
});
经验之谈:我一般把阈值设为200ms。超过200ms的操作,用户就能感知到卡顿了。
四、Systrace使用:系统级性能分析
Systrace是Google官方提供的性能分析工具。它能抓取系统各个进程的运行状态,包括CPU调度、线程状态、GC次数等。说白了,就是给你一张全局的「性能地图」。
1. 基本使用
通过命令行启动Systrace:
python systrace.py -t 10 -o trace.html sched gfx view wm am
参数说明:
- -t 10:抓取10秒
- -o trace.html:输出文件名
- sched gfx view wm am:要跟踪的模块
2. 如何分析Systrace
打开生成的trace.html,你会看到时间轴。重点关注以下几点:
- Alerts区域:系统自动标记的异常点,比如掉帧、GC频繁
- CPU调度:看线程是否被频繁切换,如果切换太频繁,说明CPU资源紧张
- UI Thread:看主线程是否有长时间的空闲或阻塞
- GC事件:如果GC太频繁,说明内存分配有问题
3. 自定义Trace标签
在代码中打标签,可以更精确地定位问题:
// 在关键操作前后打标签
Trace.beginSection("loadData");
loadData();
Trace.endSection();
这样在Systrace中就能看到"loadData"这个区间,直接知道它花了多少时间。
核心观点:Systrace不是用来找具体bug的,而是用来发现「系统级」的性能瓶颈。比如某个线程占用了太多CPU、GC太频繁导致掉帧。这些宏观问题,用Systrace一眼就能看出来。
五、知识体系总览
下面这张图,是我梳理的Android性能优化核心脉络。你可以把它当作一个检查清单:
这张图把今天讲的内容串起来了。布局优化解决渲染效率,内存优化解决资源泄漏,卡顿检测帮你定位问题,Systrace则从系统层面给出全局视角。四者配合,基本能覆盖90%的性能问题。
好了,这一章就到这里。性能优化是个持续的过程,没有银弹。多动手、多分析、多总结,慢慢你就会有感觉。