23、性能优化:内存优化、CPU占用控制、渲染帧率优化、电量消耗优化
做视频会议APP,最怕什么?
用户打着打着,手机发烫了。或者后台切回来,画面卡成PPT。再或者,开完会手机没电了。
这些我都遇到过。有一次线上演示,客户的低端机直接闪退,场面一度非常尴尬。从那以后,性能优化就成了我开发流程里优先级最高的事。
今天我们就来聊聊,Android平台上WebRTC视频会议APP的四大性能优化方向。说白了,就是让APP跑得稳、不卡顿、不费电、不崩。
核心思路:性能优化不是一次性工作,而是贯穿开发全流程的持续改进。从架构设计到代码实现,再到线上监控,每一步都要有优化意识。
一、内存优化:别让APP成为内存杀手
视频会议APP的内存消耗,主要来自三块:视频帧数据、音频缓冲区、以及各种编解码器实例。我见过最夸张的情况,一个四路视频通话,内存直接飙到500MB以上。
1.1 视频帧复用
WebRTC内部,每一帧视频数据都是通过VideoFrame对象传递的。如果你每次收到帧都new一个新对象,GC会频繁触发,卡顿就来了。
// 错误做法:每次都创建新对象
VideoFrame frame = new VideoFrame(buffer, rotation, timestampNs);
// 正确做法:复用帧对象
private VideoFrame.I420Buffer reusableBuffer;
public void onFrame(VideoFrame frame) {
// 复用buffer,减少内存分配
VideoFrame.I420Buffer buffer = frame.getBuffer().toI420();
// 处理帧逻辑...
buffer.release(); // 记得释放
}
我的经验:在PeerConnection的VideoSink中,尽量使用setVideoFramePoolSize()设置帧池大小。我一般设为3-5,既能保证流畅,又不会浪费内存。
1.2 对象池化
编解码过程中,会频繁创建和销毁各种临时对象。比如编码后的数据包、RTP包等。用对象池来管理,能显著减少GC压力。
public class PacketPool {
private final Queue<EncodedImage> pool = new ArrayDeque<>(50);
public EncodedImage obtain() {
EncodedImage image = pool.poll();
return image != null ? image : new EncodedImage();
}
public void recycle(EncodedImage image) {
if (pool.size() < 50) {
image.buffer = null; // 清空引用
pool.offer(image);
}
}
}
1.3 Bitmap回收
如果你需要显示视频帧的缩略图或者做截图,记得及时回收Bitmap。Android 8.0之后Bitmap内存虽然在native堆,但依然受虚拟内存限制。
注意:不要在渲染线程中做Bitmap的decode操作。我曾经踩过这个坑,导致UI线程卡顿,画面掉帧严重。把解码操作放到子线程,用Handler发回主线程显示。
二、CPU占用控制:让手机不再发烫
CPU占用高,直接导致两个问题:手机发热、电池续航下降。视频会议中,编解码是CPU消耗的大头。
2.1 编码器选择
Android平台支持硬件编码器。能用硬编就别用软编。硬编的功耗只有软编的1/3左右。
| 编码器类型 | CPU占用 | 功耗 | 适用场景 |
|---|---|---|---|
| H.264 硬编 | 低(10-20%) | 低 | 大多数Android设备 |
| H.264 软编 | 高(40-60%) | 高 | 兼容性兜底 |
| VP8 硬编 | 中(20-30%) | 中 | 部分中高端设备 |
| VP9 软编 | 很高(60%+) | 很高 | 不推荐移动端使用 |
// 优先使用硬件编码器
MediaConstraints constraints = new MediaConstraints();
constraints.mandatory.add(
new MediaConstraints.KeyValuePair(
"googUseHardwareVideoEncoder", "true"
)
);
constraints.mandatory.add(
new MediaConstraints.KeyValuePair(
"googUseHardwareVideoDecoder", "true"
)
);
2.2 分辨率自适应
别傻傻地一直用1080p。网络差或者CPU高的时候,主动降分辨率。WebRTC自带的VideoSource.adaptOutputFormat()就是干这个的。
// 根据CPU负载动态调整分辨率
public void onCpuUsageHigh() {
videoSource.adaptOutputFormat(
640, 480, // 目标分辨率
15, // 目标帧率
null // 回调
);
}
public void onCpuUsageLow() {
videoSource.adaptOutputFormat(
1280, 720,
30,
null
);
}
避坑指南:我曾经在低端机上用1080p硬编,结果编码器直接崩溃。后来加了分辨率降级策略,当CPU占用超过70%时,自动降到720p甚至480p。效果立竿见影。
2.3 帧率控制
不是所有场景都需要30fps。屏幕共享、文档展示这些场景,15fps完全够用。降低帧率能直接减少编码器的计算量。
// 屏幕共享场景,降低帧率
VideoSource screenSource = factory.createVideoSource(false);
screenSource.adaptOutputFormat(
1280, 720,
15, // 15fps
null
);
三、渲染帧率优化:告别卡顿画面
渲染卡顿,用户感知最明显。我调试过很多次,发现大部分渲染问题都出在View的选择和线程模型上。
3.1 用SurfaceView,别用TextureView
SurfaceView有独立的渲染层,不占用主线程的绘制时间。TextureView虽然支持动画和变换,但会占用GPU资源,导致帧率下降。
| 对比项 | SurfaceView | TextureView |
|---|---|---|
| 渲染性能 | 高(独立窗口) | 中(共享窗口) |
| 动画支持 | 弱 | 强 |
| 内存占用 | 低 | 高 |
| 推荐场景 | 视频渲染 | 需要动画的UI |
// 推荐:使用SurfaceView渲染视频
SurfaceViewRenderer renderer = new SurfaceViewRenderer(context);
renderer.setMirror(true);
renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
3.2 开启硬件加速
在AndroidManifest中为Activity开启硬件加速,能显著提升渲染性能。
<activity
android:name=".VideoCallActivity"
android:hardwareAccelerated="true" />
3.3 帧率监控
我习惯在开发阶段开启帧率监控,用Choreographer或者FrameMetrics来检测掉帧情况。
// 使用FrameMetrics监控渲染性能
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
window.addOnFrameMetricsAvailableListener(
(window, frameMetrics, dropCountSinceLastInvocation) -> {
long frameDuration = frameMetrics.getMetric(
FrameMetrics.TOTAL_DURATION
);
if (frameDuration > 16_000_000) { // 超过16ms就是掉帧
Log.w("FrameDrop", "掉帧了,耗时: " + frameDuration / 1_000_000 + "ms");
}
},
new Handler(Looper.getMainLooper())
);
}
注意:帧率监控代码只在debug版本开启。线上版本不要加,会影响性能。我一般用BuildConfig.DEBUG来控制。
四、电量消耗优化:让会议撑到最后一刻
电量优化,说白了就是让APP在后台时少干活,在前台时高效干活。
4.1 网络策略优化
WebRTC默认的网路探测和保活机制,在弱网环境下会频繁发送STUN请求,非常耗电。我建议根据网络状态动态调整探测频率。
// 根据网络类型调整STUN探测间隔
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE
);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork != null) {
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
// WiFi下,每30秒探测一次
stunInterval = 30_000;
} else {
// 移动网络下,每60秒探测一次
stunInterval = 60_000;
}
}
4.2 休眠策略
当用户切到后台或者屏幕关闭时,主动降低视频质量,甚至暂停视频流。音频保持即可。
// 监听屏幕状态
private BroadcastReceiver screenReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
// 屏幕关闭,降低视频质量
videoSource.adaptOutputFormat(320, 240, 10, null);
// 或者直接停止视频发送
localVideoTrack.setEnabled(false);
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
// 屏幕亮起,恢复视频
videoSource.adaptOutputFormat(1280, 720, 30, null);
localVideoTrack.setEnabled(true);
}
}
};
我的习惯:在后台运行时,我会把视频编码器切换到低功耗模式。Android 10以上支持MediaCodec的低功耗配置,能省电30%左右。
4.3 避免唤醒锁滥用
很多开发者为了保持网络连接,会持有一个WakeLock。但如果不及时释放,电量会快速耗尽。我建议只在需要发送关键数据时短暂持有。
// 正确使用WakeLock
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"webrtc:keep_alive"
);
// 只在发送关键信令时持有
wakeLock.acquire(5000); // 最多持有5秒
// 发送信令...
wakeLock.release();
嗯,以上就是性能优化的四个核心方向。每个方向都有很多细节,但抓住关键点,就能解决80%的问题。我个人觉得,性能优化没有银弹,最好的方法就是持续监控、持续改进。在开发阶段就把性能指标卡死,别等到线上出问题再救火。
公众号:蓝海资料掘金营,微信deep3321