IMLib SDK for Cocos2d-x 开发指南

Android 集成指南

1. SDK 下载

下载融云 SDK

2. 创建 Cocos2d-x 工程

命令行下输入:cocos new -p <package name\> -l cpp <project name\>,创建工程目录。 如:

cocos new -p rong.cocos.demo -l cpp CocosDemo

3. Android Studio 打开工程

在 Android Studio 中选择 "Open an existing Android Studio project"。

image

选择刚建好的 Cocos 工程下 proj.android-studio 目录,载入工程。

image

4. 集成 SDK

使用 Project 方式浏览:

image

复制开发包中的 RongIMLib 目录到工程的 App 下:

image

导入 jar 包,右键点击 RongIMLib.jar ,选择 Add As Library...

image

配置 AndroidManifest.xml

  1. 添加权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
  1. 添加网络事件接收器:
<receiver android:name="io.rong.imlib.NetworkDealer$NetworkStatusReceiver">
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

5. 集成推送功能

可选。如需要接收 push 消息,则按下面方式修改添加。

因为 Cocos 工程是模板生成的,Activity 的包名还用的是模板名,这里首先需要将其与工程包名修改一致。

1、修改 package="org.cocos2dx.cpp" 为工程真正的包名。(此例为 rong.cocos.demo,下同)

image

2、右键单击 src 目录,新建 rong.cocos.demo,并将 CocosdxActivity.java 移动至其中。

image

3、删除原有的 org.cocos2dx.cpp

添加推送事件接收器:

<receiver
    android:name="io.rong.push.notification.PushMessageReceiver"
    android:permission="rong.permission.push.com.rong.app">
    <intent-filter>
        <action android:name="io.rong.push.intent.action.message" />
    </intent-filter>
</receiver>

<service
    android:name="io.rong.push.PushService"
    android:process="io.rong.push">
    <intent-filter>
        <category android:name="android.intent.category.DEFAULT" />
        <action android:name="io.rong.push" />
    </intent-filter>
</service>

<receiver
    android:name="io.rong.push.PushReceiver"
    android:process="io.rong.push">
    <intent-filter>
        <action android:name="io.rong.push.intent.action.HeartBeat" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.USER_PRESENT"/>
        <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
        <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
    </intent-filter>
</receiver>

Activity 标签中,添加通知栏消息事件接收器,注意包名要正确对应(此例为 rong.cocos.demo ):

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:host="rong.cocos.demo"
          android:pathPrefix="/conversation"
          android:scheme="rong"/>
</intent-filter>
image

 修改 Android.mk 文件:

加入静态库引用:

include $(CLEAR_VARS)
LOCAL_MODULE    := librongimlib
LOCAL_SRC_FILES := ../RongIMLib/$(TARGET_ARCH_ABI)/librongimlib.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := libsqlite
LOCAL_SRC_FILES := ../RongIMLib/$(TARGET_ARCH_ABI)/libsqlite.so
include $(PREBUILT_SHARED_LIBRARY)
image

在原有的 LOCAL_C_INCLUDES 下加入 $(LOCAL_PATH)/../RongIMLib/include ,使其变为:

LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../Classes \
                    $(LOCAL_PATH)/../RongIMLib/include

链接静态库:

LOCAL_WHOLE_STATIC_LIBRARIES := librongimlib
LOCAL_SHARED_LIBRARIES := libsqlite
image

 修改 Application.mk 文件: 

根据程序使用平台,添加对应支持,如:

APP_ABI := armeabi armeabi-v7a x86
image

iOS 集成指南

SDK 只支持 xcode7.x,不支持 xcode6.x ,支持 iOS6.0 以上版本。

集成 SDK

将压缩包解压后,找到 iOS 目录下的 RongIMLib 文件夹,拖到工程的 FrameWorks 下,如下所示:

image

在弹出的界面中选择如下:

image

同时请添加下图绿色框中的系统库:

image
注:iOS 10 下相关权限问题,请参考适配 iOS 10 的注意事项。 HTTPS 相关设置请参考 ATS 设置说明

集成推送功能

如果游戏需要推送功能,请按照以下步骤添加推送支持。不需要可以略过。

打开 AppController.mm 文件,如下图位置

image

导入头文件

#import "RongIM.h"

application:didFinishLaunchingWithOptions: 函数的最后,return 之前加上如下这段代码(推送处理 1 这段)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    ...

    /**
     * 推送处理1
     */
    if ([application
         respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        //注册推送, 用于 iOS8 以及 iOS8 之后的系统
        UIUserNotificationSettings *settings = [UIUserNotificationSettings
                                                settingsForTypes:(UIUserNotificationTypeBadge |
                                                                  UIUserNotificationTypeSound |
                                                                  UIUserNotificationTypeAlert)
                                                categories:nil];
        [application registerUserNotificationSettings:settings];
    } else {
        //注册推送,用于 iOS8 之前的系统
        UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge |
        UIRemoteNotificationTypeAlert |
        UIRemoteNotificationTypeSound;
        [application registerForRemoteNotificationTypes:myTypes];
    }

    return YES;
}

另外在这个文件中加上如下两个函数

/**
 * 推送处理2
 */
//注册用户通知设置
- (void)application:(UIApplication *)application
didRegisterUserNotificationSettings:
(UIUserNotificationSettings *)notificationSettings {
    // register to receive notifications
    [application registerForRemoteNotifications];
}

/**
 * 推送处理3
 */
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSString *token =
    [[[[deviceToken description] stringByReplacingOccurrencesOfString:@"<"
                                                           withString:@""]
      stringByReplacingOccurrencesOfString:@">"
      withString:@""]
     stringByReplacingOccurrencesOfString:@" "
     withString:@""];

    RongApi->setDeviceToken([token UTF8String]);
}

编译通过则集成工作就完成了。

使用说明

使用融云 SDK,需要导入头文件 RongIM.h ,并加入命名空间:

#include "RongIM.h"

USING_NS_RongIM;

异步回调通知主线程驱动,在 update 方法里写(只有安卓需要):

RongApi->runInMainLoop();

连接及消息状态监听

主类继承连接状态监听和消息状态监听,并实现监听方法:

class HelloWorld : public cocos2d::Layer,
    public ConnectionStatusListener, // RongSDK 连接状态监听
    public MessageListener // RongSDK 消息状态监听
{
    ...
    // ConnectionStatusListener
    // 连接状态回调
    void onConnectionStatusChange(ConnectionStatus connectionStatus);

    // MessageListener
    // 发送进度回调
    void onSendProgress(const Message &sentMsg, int progress);
    // 发送成功
    void onSendSuccess(const Message &sentMsg);
    // 发送失败
    void onSendFailure(const Message &sentMsg, int code);
    // 接收消息回调
    void onReceivedMessage(const Message &rcvdMsg, int nLeft);
};

初始化 SDK

在程序入口,进行 SDK 初始化:

RongApi->setDebugLevel(DL_DEBUG); // 设置 Debug log 输出等级,可选
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    RongApi->setJVM(JniHelper::getJavaVM()); // 设置 JavaVM, 只有 Android 平台需要调用
#endif

    // 初始化 SDK,传入从开发者平台申请的应用 AppKey
    const char *appKey = "p5tvi9dst19k4";
    RongApi->init(appKey);

    // 注册连接状态监听和消息事件监听,需在 connect 之前完成
    RongApi->registerConnectionStatusListener(this);
    RongApi->registerMessageListener(this);

    // 以 "UserA" 的身份连接融云服务器,token 是 userId 对应的身份标识,可通过融云服务器提供的 RestAPI 获取到
    const char *token = "OBP6jghYYvQAmUi49wYbjS+XAKNTbHuH4vqlPeV7sUjc1p9lfcrXGw7EkVRGdfrHtF5Kji11dvbWS9GWr3YveA==";
    const char *userId = "UserA";
    RongApi->connect(token, userId);

断开连接

在程序退出的地方,加入 disconnect 方法:

void HelloWorld::menuCloseCallback(Ref* pSender)
{
    RongApi->disconnect(true); // RongSDK 断开连接方法
    Director::getInstance()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    exit(0);
#endif
}

会话与消息使用

融云 SDK 内置有数据库,存储了关于聊天的所有数据,可以调用 RongApi 来读取各种数据。

1. 会话的操作

关于会话的接口如下:

/**
 *  获取会话列表。
 *
 *  会话列表按照时间从前往后排列,如果有置顶会话,则置顶会话在前。
 *
 *  @param conversationTypeList 会话类型数组,存储对象为NSNumber类型 type类型为int
 *  @return 会话列表。
 */
list<Conversation> getConversationList(const list<ConversationType> &conversationTypes) const;

/**
 *  从会话列表中移除某一会话,但是不删除会话内的消息。
 *
 *  如果此会话中有新的消息,该会话将重新在会话列表中显示,并显示最近的历史消息。
 *
 *  @param conversation 会话。
 *
 *  @return 是否移除成功。
 */
bool removeConversation(const Conversation &conversation) const;

/**
 *  设置某一会话为置顶或者取消置顶。
 *
 *  @param conversation 会话。
 *  @param isTop            是否置顶。
 *
 *  @return 是否设置成功。
 */
bool setConversationToTop(const Conversation &conversation, bool isTop) const;

/**
 *  获取来自某用户(某会话)的未读消息数。
 *
 *  @param conversation 会话。
 *
 *  @return 未读消息数。
 */
int getUnreadCount(const Conversation &conversation) const;

/**
 *  获取某会话类型的未读消息数.
 *
 *  @param conversationTypes 会话类型, 存储对象为NSNumber type类型为int
 *
 *  @return 未读消息数。
 */
int getUnreadCount(const list<ConversationType> &conversationTypes) const;
对于普通的聊天应用,是有会话概念的,需要显示所有的会话记录。但对于游戏可能就不需要聊天列表,只需要一个世界频道(公共聊天室)、一个公会频道(群组),和私聊频道。游戏需要记住各个会话的类型和 id。
removeConversation 仅清除数据库中的会话记录,会话内的消息都还存在。如果想彻底删除,可以先调用 clearMessages,然后再调用 removeConversation

2. 消息列表的读取

消息列表的读取有如下两个函数,消息需要分批拉取,第一次要调用 getLatestedMessages ,返回 count 数目的消息。之后调用 getHistoryMessages ,参数 oldestMessageId 传入上次拉取最后一条消息的 messageId 。消息拉取的顺序是最新的消息在位置 0,最旧的消息在最后。


/**
 *  获取最新消息记录。
 *
 *  @param conversation 会话。
 *  @param count            要获取的消息数量。
 *
 *  @return 最新消息记录,按照时间顺序从新到旧排列。
 */
list<Message> getLatestedMessages(const Conversation &conversation, unsigned int count) const;

/**
 *  获取历史消息记录。
 *
 *  @param conversation 会话。
 *  @param oldestMessageId  最后一条消息的 Id,获取此消息之前的 count 条消息。
 *  @param count            要获取的消息数量。
 *
 *  @return 历史消息记录,按照时间顺序新到旧排列。
 */
list<Message> getHistoryMessages(const Conversation &conversation, long oldestMessageId, unsigned int count) const;
游戏的私聊可能都在一个私聊频道内,因此需要获取所有的单聊会话的所有消息。此时会话的会话类型设置为单聊类型,targetId 设置为空字符串即可。

3. 消息的操作

对消息的数据库操作如下:

/**
 *  插入一条消息。
 *
 *  @param conversation 会话。不支持聊天室会话。
 *  @param senderUserId     消息的发送者,如果为空则为当前用户。
 *  @param sendStatus       要插入的消息状态。
 *  @param content          消息内容
 *
 *  @return 插入的消息实体。
 */
Message insertMessage(const Conversation &conversation, const string &sendUserId, MessageStatus sendStatus, MessageContent *content) const;

/**
 *  删除指定的一条或者一组消息。
 *
 *  @param messageIds 要删除的消息 Id 列表, 存储对象为NSNumber
 *messageId,类型为long。
 *
 *  @return 是否删除成功。
 */
bool deleteMessages(const list<long> &msgIds) const;

/**
 *  清空某一会话的所有聊天消息记录。
 *
 *  @param conversation 会话。不支持聊天室会话。
 *
 *  @return 是否清空成功。
 */
bool clearMessages(const Conversation &conversation) const;

/**
 *  清除消息未读状态。
 *
 *  @param conversation 会话。不支持聊天室会话。
 *
 *  @return 是否清空成功。
 */
bool clearMessagesUnreadStatus(const Conversation &conversation) const;

/**
 *  清空会话列表
 *
 *  @param conversationTypeList 会话类型列表,存储对象为NSNumber, type类型为int
 *
 *  @return 操作结果
 */
bool clearConversations(const list<ConversationType> &conversationTypes) const;

/**
 *  设置接收到的消息状态。
 *
 *  @param messageId      消息 Id。
 *  @param receivedStatus 接收到的消息状态。
 */
bool setMessageReceivedStatus(long messageId, MessageStatus receivedStatus) const;


/**
 *  设置消息发送状态。
 *
 *  @param messageId      消息 Id。
 *  @param sentStatus 接收到的消息状态。
 */
bool setMessageSentStatus(long messageId, MessageStatus sentStatus) const;
发送消息时,SDK 会自动 insertMessage(MessagePsersistent 为存储类型)。
clearMessages 后,只删除了会话内的消息,会话记录还存在与数据库中。当需要清理多个会话内的消息时调用 clearConversations
setMessageSentStatus 。发送的消息会自动更新 sentstatus ,不用干预。

4. 消息内容的使用

消息内容类是多态的,它的共同基类是 MessageContent 。当有一个 Message 对象时,首先要判断消息内容类是哪一种类型,然后做强制转换后进行处理。

void HelloWorld::onReceivedMessage(const Message &rcvdMsg, int nLeft) {
    if (rcvdMsg.m_content->isText()) {
        TextMessageContent *textContent = (TextMessageContent *)rcvdMsg.m_content;
        CCLOG("received text content is %s", textContent->m_content.c_str());
    } else if (rcvdMsg.m_content->isAudio()) {
        AudioMessageContent *audioContent = (AudioMessageContent *)rcvdMsg.m_content;
        CCLOG("received audio content, audio url is %s, duration is %ld", audioContent->m_mediaUrl.c_str(), audioContent->m_duration);
    } else if(其它内置消息类型) {
        //其它内置消息类型的处理
    } else if(rcvdMsg.m_content->getObjectName() == "XX:XXXXMessage") {
        XXXXMessageContent *customMsg = (XXXXMessageContent *)rcvdMsg.m_content;
        //自定义消息的处理
    }
}

5. 媒体消息内容

媒体消息内容的基类是 MediaMessageContent ,包含有 ImageMessageContentAudioMessageContent 两个子类。这类消息与其它消息类型的处理方式是不同的:

  • 发送时:先上传媒体文件到媒体文件服务器,然后发送包含链接的消息。(上传和发送是 SDK 自动处理的,跟普通消息一样,调用 sendMessage 接口就行)
  • 接收时:收到的是包含链接的消息,当使用时调用 RongApi->downloadMedia 来下载媒体文件
  • 发送语音消息时,请使用 RongApi->startRecordVoice 来录音。接收到语音消息时,使用 RongApi->downloadMedia 来下载媒体文件,Android 平台为 amr 格式,iOS 平台为 wav 格式,播放还请调用各自平台方法自行处理。发送和接收下载的过程中有涉及到声音编解码的工作,请务必确保使用 SDK 的方法。图片或着其它类型媒体文件也可以使用其它方法下载。

发送文本消息:

Conversation conversation(ConversationType_PRIVATE, "UserB");
MessageContent *textContent = TextMessageContent::createContent("Hello world");
RongApi->sendMessage(conversation, textContent);

发送图片消息:

Conversation conversation(ConversationType_PRIVATE, _targetId);
  string path = FileUtils::getInstance()->fullPathForFilename("HelloWorld.png");
  MessageContent *imageContent = ImageMessageContent::createContent(path);
RongApi->sendMessage(conversation, imageContent);

发送语音消息:

Conversation conversation(ConversationType_PRIVATE, _targetId);
  MediaMessageContent *voiceContent = AudioMessageContent::createContent(voicePath,             duration);
RongApi->sendMessage(conversation, voiceContent);

6. 发送和接收消息时,消息列表的建议

在消息界面生成时,读取一次数据库,把需要显示的消息存放到列表中。之后每当收到消息或者发送消息时,把新消息加入到列表的最前端。当用户下拉获取更多历史消息时(getHistoryMessages),读取的消息放倒列表的尾部。然后把列表中的内容显示在界面上。

自定义消息

SDK 内置的消息内容类不足以满足用户多样的需要,因此提供了自定义消息的方法来扩展消息,满足用户的各种需求。

1. 使用现有模版

  • SDK 内置了四个消息内容类 CommandMessageContent/NotificationMessageContent/CustomMessageContent/StatusMessageContent ,参数有 typecontent 还有所有消息都有的 extra 字段。用户可以把消息内容放入到这几个字断中用来传递消息。比如 type 中放入消息的类型,contentextra 中放入消息的内容。如果内容字段过多,可以放到一个 json 串中。
  • 这四种消息不同之处在于本地数据库存储和服务器处理。CommandMessageContent 消息内容在本地不存储不计数,NotificationMessageContent 消息内容只存储不计数,CustomMessageContent 消息内容存储并计数,StatusMessageContent 消息内容不存储不计数,且如果对端不在线就把消息直接抛弃。
  • 当用户有多种消息使用同一种模版时,用户需要来做好区分工作。

2. 自定义消息内容

除了使用 1 的模版外,还可以自定义消息内容类。自定义消息内容类需要做到如下几个方面。

1. 定义自定义消息内容类的工厂类

实现以下 3 个虚函数。

class MyContentFactory : public MessageContentFactory {
public:
    virtual const string getObjectName() const
    {
        return "MC:Content";
    }
    virtual MessagePsersistent getPersistentFlag() const {
        return MessagePersistent_PERSIST_COUNT;
    }
    virtual MessageContent* createEmptyContent() const
    {
        return new MyContent();
    }
};
ObjectName 是在网络上传输的名称,必须确保唯一性。融云内置的消息都是以 RC: 开头,建议 App 的自定义消息以自己公司缩写开头避免重复。
2. 定义自定义消息内容类

定义自定义消息内容类需要实现以下 4 个虚函数。

class MyContent : public MessageContent {
    public:
        virtual string encode() const;
        virtual void decode(const string &rawData);
        virtual const MessageContentFactory* getFactory() const ;
        MessageContent* copy() const;

        //类的其它成员
    };
encode 用来消息内容编码,把消息内容转化为可读的字符串。内置的消息都是使用 json 序列化各个成员,非字符串成员可以用 base64 处理一下。
decode 用来消息内容解码,把字符串转化为消息内容。必须确保经过 encode 编码出来的字符串,再调用 decode 能够得到一样的消息内容。
getFactory 获取消息内容工厂类,返回一个 1 定义的对象
copy 函数提供消息内容深 copy 的能力。
3. 注册自定义消息内容工厂类

建议在调用完 init 之后注册。

const MyContentFactory *factory = new MyContentFactory();
RongApi->registerMessageContentFactory(factory);
4. 自定义消息内容类的使用

做到以上 3 步,你的自定义消息内容类就与内置的消息内容类一样了,你可以通过以下代码发送。接收方在 MessageListener 中就会收到该消息

MyContent *myContent = new MyContent();
myContent.xxx = ...;
RongIM::Message message = RongApi->sendMessage(Conversation(RongIM::ConversationType_PRIVATE, "receiverId"), myContent);

© 2016 RongCloud. All Rights Reserved. Version 2.8.5