把语聊房接到 Android 上,比普通音视频通话要多注意两件事:频道模式必须设为直播模式(Live Broadcasting),不能用默认的通话模式;纯音频场景不要调用 enableVideo()。这两个配置选错了,后续麦位管理会出问题,且因为基础功能看上去正常,排查起来很耗时。
其余步骤如Maven 依赖、权限申请、RtcEngine 初始化、Token 接入、上下麦切换……声网 SDK 封装得很完整,按本文顺序跟下来即可。SDK 通过 Maven Central 分发,最低支持 Android 4.1(API Level 16)。

一. 添加 SDK 依赖
在 app 模块的 build.gradle 里添加:
dependencies {
implementation 'cn.shengwang.rtc:full-sdk:4.6.1'
}
确认 repositories 里有 mavenCentral():
repositories {
mavenCentral()
}
同步完成后,在 External Libraries 里确认出现 cn.shengwang.rtc 相关包。如果 Maven Central 访问慢,可以去声网官网手动下载 SDK 包,放到 libs 目录用 fileTree 方式引入。
二. 声明权限
在 AndroidManifest.xml 里添加:
<!-- 必须 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- 弱网检测和网络切换 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- Android 12+ 蓝牙耳机支持 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
语聊房是纯音频场景,不需要 CAMERA 权限。
三. 运行时权限申请
RECORD_AUDIO 是危险权限,Android 6.0(API 23)以上必须在运行时动态申请。进入频道前先确认已获得:
private static final int PERMISSION_REQUEST_CODE = 1001;
private static final String[] REQUIRED_PERMISSIONS = {
Manifest.permission.RECORD_AUDIO
};
private boolean hasPermissions() {
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
private void requestPermissions() {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, PERMISSION_REQUEST_CODE
);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initializeRtcEngine();
} else {
Toast.makeText(this, "需要麦克风权限才能加入语聊房",
Toast.LENGTH_SHORT).show();
}
}
}
四. 初始化 RtcEngine
RtcEngine 是 SDK 的入口,通常在 Activity 或 ViewModel 初始化时创建,销毁时释放。
private RtcEngine engine;
private void initializeRtcEngine() {
RtcEngineConfig config = new RtcEngineConfig();
config.mContext = getApplicationContext();
config.mAppId = BuildConfig.AGORA_APP_ID; // 从配置读取,不要硬编码
config.mEventHandler = mRtcEventHandler;
try {
engine = RtcEngine.create(config);
} catch (Exception e) {
Log.e(TAG, "RtcEngine 初始化失败: " + e.getMessage());
return;
}
// 语聊房必须用直播模式(LIVE_BROADCASTING),不能用通话模式(COMMUNICATION)
// 通话模式下所有人默认都是发布者;直播模式支持 broadcaster/audience 角色区分
engine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
// 默认设为观众,上麦时再切换为 broadcaster
engine.setClientRole(Constants.CLIENT_ROLE_AUDIENCE);
// 语聊房不调用 enableVideo(),降低功耗,也不会触发摄像头权限请求
}
RtcEngine.create() 是耗时操作,不要在主线程直接调用,放到子线程或用协程处理。
五. 加入频道
加入频道前需要从服务端获取 Token(详见:《语聊房 Token 怎么生成》)。开发阶段可以先用声网控制台生成的临时 Token。
private void joinChannel(String token, String channelName, int uid) {
// 先以观众身份进入,上麦后再切换为 broadcaster
engine.setClientRole(Constants.CLIENT_ROLE_AUDIENCE);
int result = engine.joinChannel(
token, // 服务端生成的 Token
channelName, // 频道名,必须和生成 Token 时一致
null,
uid // 用户 ID,必须和生成 Token 时一致
);
if (result < 0) {
Log.e(TAG, "joinChannel 失败,错误码: " + result);
}
}
加入成功后,onJoinChannelSuccess 回调触发:
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
@Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
Log.d(TAG, "加入频道成功: " + channel + ", uid: " + uid + ", 耗时: " + elapsed + "ms");
runOnUiThread(() -> {
// 更新 UI,显示麦位状态
});
}
@Override
public void onUserOffline(int uid, int reason) {
// 其他用户离线(主动离开或断线)
Log.d(TAG, "用户离线: uid=" + uid + ", reason=" + reason);
runOnUiThread(() -> {
// 更新对应麦位 UI
});
}
@Override
public void onLeaveChannel(RtcStats stats) {
Log.d(TAG, "已离开频道, 通话时长: " + stats.totalDuration + "s");
}
};
六. 上麦与下麦
上麦就是把角色从 audience 切换为 broadcaster,切换立即生效,不需要重新加入频道。
// 上麦:开始发布音频流
public void goOnMic() {
engine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
}
// 下麦:停止发布,仍然可以收听
public void goOffMic() {
engine.setClientRole(Constants.CLIENT_ROLE_AUDIENCE);
}
切换结果通过 onClientRoleChanged 回调确认:
@Override
public void onClientRoleChanged(int oldRole, int newRole) {
runOnUiThread(() -> {
if (newRole == Constants.CLIENT_ROLE_BROADCASTER) {
// 上麦成功,更新麦位 UI
} else {
// 下麦成功,清除麦位 UI
}
});
}
麦位上的 broadcaster 可以控制自身的发送状态而不改变角色:
// 静音自己(仍是 broadcaster,只是停发音频)
engine.muteLocalAudioStream(true);
// 取消静音
engine.muteLocalAudioStream(false);
房主强制静音他人,SDK 层面没有直接 API,需要通过信令通知对方客户端执行 muteLocalAudioStream(true)。
七. Token 过期处理
@Override
public void onTokenPrivilegeWillExpire(String token) {
// Token 过期前 30 秒触发,去服务端换新 Token 后续期
fetchNewToken(currentChannelName, currentUid, newToken -> {
engine.renewToken(newToken);
// renewToken 成功后用户不会掉线
});
}
@Override
public void onRequestToken() {
// Token 已过期,需要重新加入频道
fetchNewToken(currentChannelName, currentUid, newToken -> {
engine.joinChannel(newToken, currentChannelName, null, currentUid);
});
}
八. 离开频道和资源释放
private void leaveChannel() {
if (engine != null) {
engine.leaveChannel();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
leaveChannel();
// destroy() 有一定耗时,放到后台线程
new Thread(() -> RtcEngine.destroy()).start();
engine = null;
}
RtcEngine.destroy() 调用后释放所有 SDK 资源,下次使用时需要重新 create()。
九. 音频质量配置
默认配置对普通语聊够用。有特殊需求时在 initializeRtcEngine() 的 setChannelProfile 之后添加:
// 标准语音质量,普通语聊场景适用(默认值)
engine.setAudioProfile(Constants.AUDIO_PROFILE_DEFAULT);
// 高质量音乐,KTV / 合唱场景使用
engine.setAudioProfile(Constants.AUDIO_PROFILE_MUSIC_HIGH_QUALITY);
十. 常见问题
joinChannel 返回 -2(ERR_INVALID_ARGUMENT)
检查 channelName 是否为空,uid 是否在有效范围(0 ~ 4294967295),token 格式是否正确(Base64 字符串,长度通常在 200 字符以上)。
进频道后听不到声音
先确认发送方已设为 broadcaster 且没有调用 muteLocalAudioStream(true)。通过 onRemoteAudioStateChanged 回调可以监控远端音频流的状态,看是否有收到数据。
特定机型有回声
SDK 的 AEC(回声消除)会自动处理大多数情况。如果特定机型仍有问题,外放时回声通常由扬声器收音导致,可以让用户使用耳机,或者检查是否有其他 App 正在占用麦克风。
Android 12+ 蓝牙耳机无声音
确认已在 AndroidManifest.xml 声明 BLUETOOTH_CONNECT 权限,并在运行时动态申请(Android 12+ 该权限属于危险权限,需要用户授权)。
