网易云信安卓 Demo 结构说明

网易云信 Demo 工程基于网易云信 SDK,演示了 SDK 聊天、群组、白板、实时音视频等功能接口的使用方法。Demo 工程依赖于 UIKit 工程,UIKit 实现了基本的消息收发,群组服务以及通讯录等功能,包含有完整的界面显示。开发者可以直接调用UIKit 中的接口,来进行功能开发,加快开发速度。用户可参照该 Demo,将网易云信 SDK 接入自己的 APP。

工程导入指引

下载编译 Demo

用户可在网易云信官网下载 Demo 源码工程。

总体环境需求:

如果你使用的 IDE 是 Android Studio,可直接在 IDE 中打开 Demo 工程,然后将工程目录下 gradle.properties 文件按照注释修改,就可以直接编译运行。

如果你使用的 IDE 是 Eclipse,可直接在 IDE 中打开工程,做如下修改后,即可编译运行。

源码结构

由于 Demo 依赖于 UIKit 进行开发。分为 Demo 工程和 UIKit 工程。分别介绍这两个工程的源码结构。

Demo源码结构

UIKit源码结构

修改Demo为己用

网易云信 Demo 实现了一个 IM 软件的所有基础功能,开发者可直接以 Demo 为基础开发自己的 IM 软件,也可以稍作修改,用于前期流程验证,也可以作为 SDK 开发的参考和指南。

聊天界面代码说明

结构说明

音视频代码说明

结构说明

初始化

AVChatActivity 的 oncreate 中,进行管理器的初始化工作

avChatUI = new AVChatUI(this, root, this);
if (!avChatUI.initiation()) {
    this.finish();
    return;
}
public boolean initiation() {
        AVChatProfile.getInstance().setAVChatting(true);
        avChatAudio = new AVChatAudio(root.findViewById(R.id.avchat_audio_layout), this, this);
        avChatVideo = new AVChatVideo(context, root.findViewById(R.id.avchat_video_layout), this, this);
        avChatSurface = new AVChatSurface(context, this, root.findViewById(R.id.avchat_surface_layout));

        return true;
    }

拨打

主流程:

1、传入参数,对方帐号和拨打的类型(AVChatType.AUDIO 或 AVChatType.VIDEO)。

avChatUI.outGoingCalling(receiverId, AVChatType.typeOfValue(state));

2、通知界面刷新,详见界面刷新 一节。

if (callTypeEnum == AVChatType.AUDIO) {
    onCallStateChange(CallStateEnum.OUTGOING_AUDIO_CALLING);
} else {
    onCallStateChange(CallStateEnum.OUTGOING_VIDEO_CALLING);
}

3、发起通话

/**
* 发起通话
* account 对方帐号
* callTypeEnum 通话类型:语音、视频
* videoParam 发起视频通话时传入,发起音频通话传null
* AVChatCallback 回调函数,返回AVChatInfo
*/
AVChatManager.getInstance().call(account, callTypeEnum, videoParam, new AVChatCallback<AVChatData>() {
            @Override
            public void onSuccess(AVChatData data) {
               ...
            }

            @Override
            public void onFailed(int code) {
                ...
            }

            @Override
            public void onException(Throwable exception) {
                ...
            }
        });

接听

1、传入参数 AVChatData

avChatUI.inComingCalling(avChatData);

2、通知界面刷新,详见界面刷新 一节。

if (callTypeEnum == AVChatType.AUDIO) {
    onCallStateChange(CallStateEnum.OUTGOING_AUDIO_CALLING);
} else {
    onCallStateChange(CallStateEnum.OUTGOING_VIDEO_CALLING);
}

界面刷新

界面刷新,详细流程如下。 1、调用 onCallStateChange 2、如果界面没有进行过初始化,则进行界面初始化 findViews,并为各个按钮添加响应事件。 3、根据 CallStateEnum 判断界面布局设置和显隐性。

// 有来电,界面状态更新
onCallStateChange(CallStateEnum.INCOMING_AUDIO_CALLING);

// 判断来电类型,是音频或视频
if(CallStateEnum.isAudioMode(state))
    findViews();

// 设置信息显示和界面布局
switch (state){
    ...
    case INCOMING_AUDIO_CALLING://免费通话请求
        setSwitchVideo(false);
        showProfile();//对方的详细信息
        showNotify(R.string.avchat_audio_call_request);
        setMuteSpeakerHangupControl(false);
        setRefuseReceive(true);
        receiveTV.setText(R.string.avchat_pickup);
        break;
    ...
}

按钮响应事件

AVChatAudio 和 AVChatVideo 中包含了挂断,拒绝,接受,禁音,开启扬声器,音视频切换和摄像头切换的操作。 按钮的点击响应事件,通过 AVChatUIListener 统一交给 AVChatUI 进行管理。示例如下:

// 初始化挂断按钮
hangup = mute_speaker_hangup.findViewById(R.id.avchat_audio_hangup);
hangup.setOnClickListener(this);

// 按钮响应事件处理
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.avchat_audio_hangup:
            listener.onHangUp();
        break;
    ...
    }
}

// 在AVChatUI的AVChatUIListener实现中,实现挂断或取消接口。
public void onHangUp() {
    if (isCallEstablish.get()) {
        hangUp(AVChatExitCode.HANGUP);
    } else {
        hangUp(AVChatExitCode.CANCEL);
    }
}

聊天室代码说明

结构说明

重点类说明

新老版本兼容问题

群通知相关

问题:群通知新增的通知消息类型,可能会造成老版本崩溃。 原因:TeamNotificationHelper#buildUpdateTeamNotification 的 a.getUpdatedFields() 的 size 为0,造成 sb 的 length为0,会抛出 StringIndexOutOfBoundsException 错误。 解决方案:判断 sb 的length,参考demo。

Android 6.0 权限管理

云信 demo 提供 Android 6.0 权限管理示例。相关方法的实现,在 uikit 的 permission 包中。

在需要相关权限的地方,发起申请并等待用户操作后的返回结果。具体实现方法:

private void requestBasicPermission() {
    MPermission.with(MainActivity.this)             
        .addRequestCode(BASIC_PERMISSION_REQUEST_CODE)
        .permissions(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            // ……
        )
        .request();
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    MPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}

@OnMPermissionGranted(BASIC_PERMISSION_REQUEST_CODE)
public void onBasicPermissionSuccess(){
    Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show();
}

@OnMPermissionDenied(BASIC_PERMISSION_REQUEST_CODE)
public void onBasicPermissionFailed(){
    Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show();
}