在线咨询
专属客服在线解答,提供专业解决方案
工单支持
专业技术支持团队,随时响应服务需求

语聊房 Android 开发教程:SDK 接入与权限配置

语聊房接到 Android 上,比普通音视频通话要多注意两件事:频道模式必须设为直播模式(Live Broadcasting),不能用默认的通话模式;纯音频场景不要调用 enableVideo()。这两个配置选错了,后续麦位管理会出问题,且因为基础功能看上去正常,排查起来很耗时。

其余步骤如Maven 依赖、权限申请、RtcEngine 初始化、Token 接入、上下麦切换……声网 SDK 封装得很完整,按本文顺序跟下来即可。SDK 通过 Maven Central 分发,最低支持 Android 4.1(API Level 16)。

语聊房 Android 开发教程


一. 添加 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+ 该权限属于危险权限,需要用户授权)。

在声网,连接无限可能

想进一步了解「对话式 AI 与 实时互动」?欢迎注册,开启探索之旅。

本博客为技术交流与平台行业信息分享平台,内容仅供交流参考,文章内容不代表本公司立场和观点,亦不构成任何出版或销售行为。