Jeejio JM SDK 致力于让第三方开发者开发基于 Jeejio 智能设备的应用时,快速集成与智能设备及其所属用户通信的能力。除了接收用户发送的文字、语音外,还可以处理规定格式的命令。
请将如下 aar 包放入 libs 文件夹中
- jmessagemodule-preview
- smack-core-release
- smack-extensions-release
- smack-im-release
- smack-sasl-provided-release
- smack-tcp-release
eg:
implementation(name: 'jmessagemodule-preview', ext: 'aar')
implementation(name: 'smack-core-preview', ext: 'aar')
implementation(name: 'smack-extensions-preview', ext: 'aar')
implementation(name: 'smack-im-preview', ext: 'aar')
implementation(name: 'smack-sasl-provided-preview', ext: 'aar')
implementation(name: 'smack-tcp-preview', ext: 'aar')
QA 环境
- jmessagemodule-debug
- smack-core-debug
- smack-extensions-debug
- smack-im-debug
- smack-sasl-provided-debug
- smack-tcp-debug
implementation(name: 'jmessagemodule-debug', ext: 'aar')
implementation(name: 'smack-core-debug', ext: 'aar')
implementation(name: 'smack-extensions-debug', ext: 'aar')
implementation(name: 'smack-im-debug', ext: 'aar')
implementation(name: 'smack-sasl-provided-debug', ext: 'aar')
implementation(name: 'smack-tcp-debug', ext: 'aar')
除此以外,JM 还用到了 OkHttp 请求,Gson 解析及阿里云 Oss 文件存储,所以还需在 build.gradle 中添加如下依赖:
// 网络请求库
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
// Json 数据解析库
implementation 'com.google.code.gson:gson:2.8.5'
// 阿里云 OSS 存储
implementation('com.aliyun.dpa:oss-android-sdk:2.9.2', {
exclude module: "okhttp"
exclude module: "okio"
})
登录前需做 SDK 的初始化。
eg:
// 初始化 Oss
OssHelper.SINGLETON.init(context);
// 初始化 Jeejio Message
try {
// 如果需要处理命令,可以在 init 方法中传入命令处理者的 Class,命令规则详见:命令
JMClient.SINGLETON.init(context);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
接入 SDK 的应用是提供给物栖智能设备的应用,无需注册,当该应用下载到物栖智能设备上会自动完成注册操作。
当监听器的 connectionClosedOnError() 方法被回调时,如果错误信息中包含 conflict 关键字,则表示是同一个账号在另一个客户端上登录,当前客户端会被踢下线。
eg:
JMClient.SINGLETON.addOnConnectListener(new IOnConnectListener() {
@Override
public void onConnected() {
// 连接成功
}
@Override
public void onAuthenticated() {
// 登录成功
}
@Override
public void onConnectFailure(Exception e) {
// 连接异常断开
if (e instanceof SmackException.LoginConflictException) {
// 登录冲突
}
}
@Override
public void onDisconnected() {
// 连接断开
}
});
SDK 中所有操作均需在登录之后进行,在调用登录方法前需要设置连接监听器,登录成功会回调监听器的 onAuthenticated() 方法,登录失败会回调 onConnectFailure() 方法。
eg:
// 第三方应用用户登录
JMClient.SINGLETON.applicationLogin();
虽然即使不调用登出方法,服务器也会在一段时间后自动断开闲置客户端,但为了更好的状态同步,需在应用退出时及时调用登出方法。
eg:
JMClient.SINGLETON.logout();
服务端为了节省资源,通常会关闭闲置客户端,虽然客户端与服务端有心跳机制进行保活,但仍然存在会话失效的情况,这时候客户端在 ping 服务器发现没有响应(3 次)的时候,会关闭连接。 客户端可以监听到连接关闭,这时候可以调用登录方法进行手动重连。
eg:
JMClient.SINGLETON.addOnFriendStatusListener(new IOnFriendStatusListener() {
/**
* 联系人上线时回调该方法
* @param username 上线的用户的 username,即 localPart 部分
*/
@Override
public void onOnline(String username) {
}
/**
* 联系人下线时回调该方法
* @param username 下线的用户的 username,即 localPart 部分
*/
@Override
public void onOffline(String username) {
}
/**
* 收到好友请求时回调
* @param username 发送请求的用户的 username,即 localPart 部分
* @param additionalMsg 附加的描述,该参数可能没有
* @param createTime 申请时间
*/
@Override
public void onSubscribe(String username, String additionalMsg, String createTime) {
}
/**
* 对方同意添加联系人时回调该方法
* @param username 同意请求的用户的 username,即 localPart 部分
*/
@Override
public void onSubscribed(String username) {
}
/**
* 对方拒绝添加联系人时回调该方法
* @param username 对方的 username,即 localPart 部分
*/
@Override
public void onUnsubscribed(String username) {
}
/**
* 对方删除联系人时回调该方法
* @param username 发送请求的用户的 username,即 localPart 部分
*/
@Override
public void onUnsubscribe(String username) {
}
/**
* 发生错误时回调该方法
*/
@Override
public void onError() {
}
});
eg:
JMClient.SINGLETON.removeFriendListener(mOnFriendStatusListener);
eg:
JMClient.SINGLETON.addOnGroupChatStatusListener(new IOnGroupChatStatusListener() {
/**
* 群聊被创建时回调
* @param localpart 群聊标识
* @param owner 群主的 username
*/
@Override
public void groupChatCreated(String localpart, String owner) {
}
/**
* 群主变更时回调
* @param localpart 群聊唯一标识
* @param ownerCurrent 当前群主的 username
* @param ownerBefore 之前群主的 username
*/
@Override
public void ownerChanged(String localpart, String ownerCurrent, String ownerBefore) {
}
/**
* 有人修改在本群中的昵称时回调
* @param localpart 群聊唯一标识
* @param occupant 群成员的 username
* @param name 群成员的新的在本群中的昵称
*/
@Override
public void occupantNameChanged(String localpart, String occupant, String name) {
}
/**
* 群头像被修改时回调
* @param localpart 群聊唯一标识
* @param img 群头像
* @param executor 执行人的 username
*/
@Override
public void imageChanged(String localpart, String img, String executor) {
}
/**
* 群名称被修改时回调
* @param localpart 群聊唯一标识
* @param name 群聊名称
* @param executor 执行人的 username
*/
@Override
public void nameChanged(String localpart, String name, String executor) {
}
/**
* 有人退出群聊时回调
* @param localpart 群聊唯一标识
* @param occupant 退出的群成员的 username
*/
@Override
public void leave(String localpart, String occupant) {
}
/**
* 群销毁时回调
* @param localpart 群聊唯一标识
*/
@Override
public void destroy(String localpart) {
}
/**
* 有群成员被踢出群聊时回调
* @param localpart 群聊唯一标识
* @param kicked 被踢出的群成员的 username
* @param executor 执行人的 username
*/
@Override
public void kick(String localpart, String kicked, String executor) {
}
/**
* 有新的用户加入群聊时回调
* @param localpart 群聊唯一标识
* @param occupant 新成员的 username
* @param executor 执行人的 username
* @param pOccupant 新成员信息
*/
@Override
public void join(String localpart, String occupant, String executor, Presence.Occupant pOccupant) {
}
});
eg:
JMClient.SINGLETON.removeOnGroupChatStatusListener(mOnGroupChatStatusListener);
发送文本、语音、命令等消息(单聊、群聊通用)。发送消息需要明确指示消息接收方,其唯一标识可以在获取我的好友列表后通过 userDetailBean.getLoginName() 或获取我的群列表后通过 groupChatBean.getId() 拿到。
通过 JMClient 单例来获取 MessageManager
eg:
JMClient.SINGLETON.getMessageManager();
在需要监听消息的界面添加监听器,单聊、群聊通用,根据接收到的 message 的不同类型做不同的业务逻辑即可。
目前 MessageType 中仅列举了两种类型
- CHAT:单聊,对应值 1001
- GROUP_CHAT:群聊,对应值 1002
目前 MessageContentType 中列举了五种类型 TEXT:普通文本,对应值 2001 IMAGE:图片,对应值 2002 VOICE:语音,对应值 2003 FILE:(暂不支持)文件,对应值 2004 COMMAND:命令,对应值 2005
eg:
JMClient.SINGLETON.addOnMessageListener(new IOnMessageListener() {
@Override
public void onMessage(MessageBean messageBean) {
// 收到消息
}
});
eg:
// toUsername:单聊为对方 id,群聊为群 id
MessageBean message = MessageBean.createTextMessage(toUsername,content);
// 群聊 Message
// MessageBean message = MessageBean.createTextMessage(toUsername,content);
// message.setType(MessageBean.Type.GROUP_CHAT.getValue())
// 或
// MessageBean message = MessageBean.createTextMessage(toUsername,MessageBean.Type.GROUP_CHAT,content);
JMClient.SINGLETON.getMessageManager().sendMessage(message);
如果需要监听发送结果,则调用
eg:
JMClient.SINGLETON.getMessageManager().sendMessage(message, new JMCallback<MessageBean>() {
@Override
public void onSuccess(MessageBean messageBean) {
// 发送成功
}
@Override
public void onFailure(Exception e) {
// 发送失败会回调该方法,目前只有双方不为好友或超时会发送失败
}
});
eg:
// toUsername:单聊为对方 id,群聊为群 id
// filePath:语音文件路径
// time:语音时长
MessageBean message = MessageBean.createVoiceMessage(toUsername,filePath,time);
// 群聊 Message 请参考文本消息
JMClient.SINGLETON.getMessageManager().sendMessage(message);
如果需要监听发送结果,请参考文本消息
命令本质上与普通文本并无区别,所以在 Message 的创建时传参与普通文本一样,只是调用的创建方法不同。
eg:
// toUsername:单聊为对方 id,群聊为群 id
// content:命令内容
MessageBean message = MessageBean.createCommandMessage(toUsername,content);
// 群聊 Message 请参考文本消息
JMClient.SINGLETON.getMessageManager().sendMessage(message);
如果需要监听发送结果,请参考文本消息
如果以上方法仍满足不了需求,可以设置 Message 的 extend 属性,作为扩展字段,自己定义解析规则,推荐 json 格式的字符串。
eg:
MessageBean message = new MessageBean(content, toUsername);
// 群聊 Message 请参考文本消息
message.setExtend("{"key":"value"}");
通过 JMClient 单例来获取 FriendManager
eg:
JMClient.SINGLETON.getFriendManager();
由于 JM 的用户系统是区分类型的,分为普通用户类型(userType 为 1)、设备用户类型(userType 为 2)、应用用户类型(userType 为 3),所以在查询好友列表时需分别查询。 好友列表在登录成功后会同步一遍服务器数据,之后都是从缓存中获取
eg:
JMClient.SINGLETON.getFriendManager().getList(userType, new JMCallback<List<UserDetailBean>>() {
@Override
public void onSuccess(List<UserDetailBean> userDetailBeanList) {
}
@Override
public void onFailure(Exception e) {
}
});
eg:
// loginName 为对方的用户名
// remarkName 为新备注
JMClient.SINGLETON.getFriendManager().updateRemark("", "", new JMCallback<Object>() {
@Override
public void onSuccess(Object object) {
}
@Override
public void onFailure(Exception e) {
}
});
通过 JMClient 单例来获取 GroupChatManager
eg:
JMClient.SINGLETON.getGroupChatManager();
eg:
JMClient.SINGLETON.getGroupChatManager().getMyGroupChat(new JMCallback<List<GroupChatBean>>() {
@Override
public void onSuccess(List<GroupChatBean> groupChatBeanList) {
// groupChatBeanList 即我的群聊列表
}
@Override
public void onFailure(Exception e) {
}
});
eg:
// localpart: 群聊唯一标识
JMClient.SINGLETON.getGroupChatManager().getGroupChat(localpart, new JMCallback<GroupChatBean>() {
@Override
public void onSuccess(GroupChatBean groupChatBean) {
}
@Override
public void onFailure(Exception e) {
}
});
eg:
// localpart: 群聊唯一标识
// occupantName: 我在本群中的昵称
JMClient.SINGLETON.getGroupChatManager().updateOccupantName(localpart, occupantName, new JMCallback<Object>() {
@Override
public void onSuccess(Object o) {
}
@Override
public void onFailure(Exception e) {
}
});
命令是 JM SDK 的一大特色,使用者输入正确规则的命令内容,如果接收方对该命令有处理的话,会执行相应的操作。
- 关键字 -参数名简称 [参数值]
- 关键字 --参数名全称 [参数值]
其中关键字必须要有,参数是不定参数,多个参数之间以横线(“-”)隔开,参数可以传值也可以不传值,参数名与参数值之间以空格(“ ”)隔开。
eg:
- volume
- volume -up
- appInstall -i 1000
- appInstall --name 亲子教育
- appList -p 1 -pageSize 10
上面是命令的正确输入规则,按照上述规则输入的命令会通过验证,被接收方收到,但在收到时 SDK 中还会验证你是否可以处理该命令,想要处理一个命令,需定义一个命令处理者类,在该类中声明处理命令的方法,并使用 @Command 注解修饰该方法,用 @Param 注解修饰参数,最后在 JMClient.SINGLETON.init(this) 初始化的时候传入该命令处理者。
@Command 注解用于声明命令对应的处理方法。
属性:
- keyword:命令关键字,同一个应用内的命令关键字不能重复,否则会覆盖。
- englishAlias:英文别名,默认为空,如果设置了,则输入时输入的关键字为英文别名也会成功匹配,如上面的 volume 命令如果声明了英文别名为 v,则输入 v -up 也会成功匹配。
- chineseAlias:中文别名,默认为空,同上。
- letterAliasL:字母别名,默认为空,同上。
- desc:命令功能描述,给开发者看的,相当于注释,无实际意义。
@Param 注解用于标识命令中的参数,考虑到有的参数是必传参数值的,有的参数可以没有值,所以使用 @Param 修饰的参数必须是 Java 包装类。
属性:
- name:参数名简称,“ -”后面的被认为是参数名简称,如上面的 -i,i 会被认为是参数名简称。
- fullName:参数名全称,“ --”后面的被认为是参数名全称,如上面的 --name,name 会被认为是参数名全称。
- requireValue:该参数是否必须传值,默认为 true。当该值为 true 时,如果命令中传递了该参数而没有传参数值,而收到命令的验证就通不过,命令不会传递给命令处理者,当该值为 false 时,如果命令中传递了该参数,则收到的参数值为 defaultValue,如果没有设置 defaultValue,则值为参数对应类型的默认值,如 Integer 会为 0,Boolean 会为 false。
- defaultValue:当参数不是必须传值时的默认值。
- desc:参数描述,给开发者看的,相当于注释,无实际意义。
eg:
public class CommandHandler {
@Command(keyword = "volume", chineseAlias = "音量", letterAlias = "v", desc = "查询和调整设备端音量")
public void volume(@Param(name = "up", requireValue = false, defaultValue = "10", desc = "音量+") Integer up
, @Param(name = "down", requireValue = false, defaultValue = "10", desc = "音量-") Integer down
, @Param(name = "adjust", desc = "音量调整到") Integer adjust
, MessageBean messageBean) {
// 如果命令中有 up 参数,且有值,则 up 值为输入值,如 volume -up 20
// 如果命令中有 up 参数,但没有输入值,则 up 值为 defaultValue,如 volume -up
// 如果命令中没有 up 参数,则 up 为 null,如 volume -down 50
// do something
}
}
然后在 JMClient 初始化的时候传入该 Handler
// 初始化 Jeejio Message
try {
// 如果需要处理命令,可以在 init 方法中传入命令处理者的 Class,命令规则详见:命令
JMClient.SINGLETON.init(context,CommandHandler.class);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
- 优化长连接断开时的资源回收处理;
- 增加登录冲突异常和 Token 异常;
- 优化部分变量名。