文章目录
前言
最近因为项目需要即时通讯,项目集成了环信的IM,但是联系人,最近消息列表以及聊天界面显示的默认头像和账号。我们需要的是昵称和自己服务器的头像。后来经过工单提问,得知环信只负责用户的id,用户头像和昵称是需要在客户端自己编写逻辑,并且给出了解决思路。
网上的相关资料少,自己没少走弯路,这次写出来希望能帮到有困惑的人
提示:本文包括两部分
1,环信官方解决方案介绍,解读
2,ObjectBox 负责本地用户数据库存储查询等
3,EasyUI 给EasyUI 设置用户信息提供者(UserProfileProvider)相关逻辑
一、环信easyUI的方案?
1.如何设置头像,昵称?
参考链接:http://www.imgeek.org/article/825307638
关于依赖easeui,设置头像、昵称问题
在调用EaseUI.getInstance().init初始化之后去设置用户信息提供者
//get easeui instance
EaseUI easeUI = EaseUI.getInstance();
//需要easeui库显示用户头像和昵称设置此provider
easeUI.setUserProfileProvider(new EaseUserProfileProvider() {
@Override
public EaseUser getUser(String username) {
return getUserInfo(username);
}
});
getUserInfo是自己实现的一个方法,在这个方法里去根据传入的username获取本地保存的对应的昵称、头像,设置给EaseUser的对象,并返回。
easeui里显示昵称、头像的时候会去调用EaseUserProfileProvider这个接口去获取EaseUser对象,会去执行在初始化之后设置的getUserInfo方法,如果没有显示昵称、头像,你就要去看getUserInfo里是否拿到昵称、头像设置给EaseUser对象了。
获取昵称、头像显示,我这里给大家两种方案,昵称、头像都保存在自己的服务器。
第一种
可以在登录之后去服务器获取所有好友的昵称、头像,包括自己的,保存在本地,getUserInfo方法里就去根据传入的username去本地获取,设置给EaseUser对象返回。
第二种
可以在getUserInfo方法里去判断本地是否有保存对应的昵称和头像,没有就发送网络请求去服务器获取对应的昵称头像保存到本地,设置给EaseUser对象返回 ,然后发送广播到聊天界面去提示刷新,刷新之后就会执行getUserInfo方法拿到本地的昵称、头像。
说明: 简单来说 就是easyUI其实有提供设置用户信息输入源的接口,只不过示例代码和demo中都是只有username的EaseUser,需要你自己在setUserProfileProvider 实现参数的getUser()里返回添加了昵称和头像的EaseUser对象.
getUser()什么时候被回调?需要显示用户信息的时候都会。。。
至于两种保存方案,都是说吧自己服务器的数据在本地保存,然后在getUser()里封装返回,根据自己的需求选择一个逻辑就是了。
ps 因为项目需求,不是好友也可以聊天,因此本案例是第二种,每当getUserInfo()被调用时,查询本地数据库是否有数据,如果没有,server请求获取该用户的头像昵称,保存,刷新,返回,显示。如果有,返回显示
1.每次上线,消息列表的数据怎么来的?
就是没有。。所以自己一个一取根据username取 然后老老实实去刷新吧。。
二、ObjectBox使用
1.ObjectBox简介
ObjectBox是一款高性能的NoSQL数据库,专为IoT和移动设备开发。背后的开发团队是开发了大名鼎鼎的GreenDao和EventBus的团队。跨平台支持Linux、Windows、Mac/iOS、Android,Raspberry Pi, ARM等
优点:
速度快,号称比目前主流数据库架构快 5-15 倍
NoSQL,没有 rows、columns、SQL,是完全面向对象的 API
数据库升级做到完全自动
2.依赖集成
根目录下 build.gradle :
buildscript {
ext.objectboxVersion = '2.7.1'
repositories {
jcenter
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath "io.objectbox:objectbox-gradle-plugin:$objectboxVersion"
}
}
app 下 build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'io.objectbox' // apply last
3.使用
(1)建立实体类
@Entity
public class User {
@Id
public long id;
public String nickName;
//账号
@Unique
public String loginName;
//头像
public String avatar;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
}
创建完数据类一定要build-makeProject;
(MyObjectBox 类是自动生成,如果没有生成,请Rebuild)
注释说明
@Entity:ObjectBox只保存用此注释标记的类的对象
@Id:实体必须有一个long类型的Id属性,这样能有效的获取或引用对象
@NameInDb:如果名称在Java端出现分歧(相对于DB),可以在这里指定数据库中使用的名称。允许在Java中进行简单的重命名。
@Transient:表示该字段不会持久存储在数据库中
@Index:指定该属性为索引,如果使用该属性进行查询。查询该属性时,可以提高性能。
@Unique:将属性值标记为唯一
@Backlink:定义反向链接关系,该关系基于另一个反向关系
@BaseEntity:实体基类的注释。
@NotNull:指定该属性不为空
@TargetIdProperty:定义作为ToOne的目标ID的属性。
(2)初始化
官方推荐在 Application 中初始化 ObjectBox 的实例
Application中配置BoxStore
获取用户表
private BoxStore boxStore;
public BoxStore getBoxStore() {
if (boxStore == null) {
boxStore = MyObjectBox.builder().androidContext(getApplicationContext()).build();
}
return boxStore;
}```
二、具体实现
ObjectBox基本用法网络上还是有很多的,这里推荐两篇
参考 简单使用和联表的介绍 https://www.jianshu.com/p/24b7ffbbe383
参考 数据监听 防止内存泄漏的官方中文翻译https://blog.youkuaiyun.com/Vxiaocai/article/details/78665623
1.使用场景
场景1:登陆时,查询有无该username的数据,有就更新。没有就添加。
场景2:easyUI需要显示用户信息的时候,查询本地,有就重新封装EaseUser,刷新最近联系人列表,无就Server请求,保存,刷新列表。
注:我们需要本地数据库查询有无这个账号的数据,当有结果的时候在主线程更新UI刷新头像昵称,并且,每次收到他人消息的时候在User表查询,发起查询动作的容器类是观察者observer,并且对User表的每个查询操作订阅subscribe。
当有结果的时候在主线程更新UI刷新头像昵称,并且,每次收到他人消息的时候在User表查询,发起查询动作的容器类是观察者observer,并且对User表的每个查询操作都订阅subscribe。并且在这个activity onDestory中取消订阅关系,这里用到了DataSubscriptionList,统一管理这些操作的订阅关系
2.代码
//管理数据库查询订阅关系list
private DataSubscriptionList subscriptions = new DataSubscriptionList();
public DataSubscriptionList getSubscriptions() {
return subscriptions;
}
// 取消数据库查询订阅
public void cancelSubscriptions() {
subscriptions.cancel();
}
场景1 登陆的时候
@Override
public void onSuccess(LoginBean loginBean) {
ToastUtil.showToast(this, "登录成功");
Box<User> userBox = MyApplication.getInstance().getBoxStore().boxFor(User.class);
DataSubscriptionList subscriptions = MyApplication.getInstance().getSubscriptions();
Query<User> query = userBox.query().equal(User_.loginName, loginBean.getUser().getLoginName()).build();
DataSubscription subscription = query.subscribe(subscriptions).on(AndroidScheduler.mainThread()).observer(new DataObserver<List<User>>() {
@Override
public void onData(List<User> data) {
//因为loginName唯一 所以查询一次就可以取消订阅 不然在发生改变的时候还会重复调用
MyApplication.getInstance().cancelSubscriptions();
if (data.size() == 0) {
//没查到数据 添加新数据
User user = new User();
user.setAvatar(loginBean.getUser().getAvatar());
user.setLoginName(loginBean.getUser().getLoginName());
user.setNickName(loginBean.getUser().getUserName());
userBox.put(user);
} else {
//更新用户信息
User updateUser = data.get(0);
updateUser.setAvatar(loginBean.getUser().getAvatar());
updateUser.setLoginName(loginBean.getUser().getLoginName());
updateUser.setNickName(loginBean.getUser().getUserName());
userBox.put(updateUser);
}
}
});
subscriptions.add(subscription);
}
场景2 最近联系人列表,好友列表,聊天界面可见
也就是环信的DemoHelper中,getUserInfo()里被调用
//正在查询中的用户名集合
private List<String> userNames = new ArrayList<>();
//查到的用户集合,用于直接封装EaseUser返回
private HashMap<String, EaseUser> users = new HashMap<>();
easeUI.setUserProfileProvider(new EaseUserProfileProvider() {
@Override
public EaseUser getUser(String username) {
return getUserInfo(username);
}
});
private EaseUser getUserInfo(String username) {
EaseUser easeUser = new EaseUser(username);
if (users.containsKey(username)) {
Log.e("dian", "返回了昵称为" + users.get(username).getNickname() + "的用户信息");
return users.get(username);
}
//demo采坑之一!避免多个地方同时调用这个方法,在第一次查询还没有出结果的时候,这个方法被其他地方调用了第二次,造成两次添加,因为username属性设置了唯一性,所以会报错
if (!userNames.contains(username)) {
userNames.add(username);
Box<User> userBox = MyApplication.getInstance().getBoxStore().boxFor(User.class);
DataSubscriptionList subscriptions = MyApplication.getInstance().getSubscriptions();
Query<User> query = userBox.query().equal(User_.loginName, username).build();
DataSubscription subscription = query.subscribe(subscriptions).on(AndroidScheduler.mainThread()).observer(new DataObserver<List<User>>() {
@Override
public void onData(List<User> data) {
if (data.size() == 0) {
// 数据库没有该用户 服务器请求
Log.e("dian", "数据库没有该用户 服务器请求");
HashMap param = new HashMap();
param.put("loginName", username);
RetrofitUtils.getApiUrl().getUserInfoByAccount(param)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new SimpleObserver<UserSimpleBean>() {
@Override
public void OnSuccess(UserSimpleBean bean) {
User user = new User();
user.setAvatar(bean.getData().getAvatar());
user.setLoginName(username);
user.setNickName(bean.getData().getUserName());
userBox.put(user);
userNames.remove(username);
easeUser.setAvatar(bean.getData().getAvatar());
easeUser.setNickname(bean.getData().getUserName());
users.put(username, easeUser);
EventBus.getDefault().post(new MessageEvent("RefreshUser", "", null, 0));
}
@Override
public void OnFail(ExceptionHandle.ResponeThrowable e) {
ToastUtil.showToast(appContext, "获取用户信息异常: " + e);
userNames.remove(username);
}
@Override
public void OnDisposable(Disposable d) {
DisposableManager.getInstance().add(d);
}
});
} else {
Log.e("dian", "数据库有该用户信息直接返回");
//数据库有该用户信息直接返回
User updateUser = data.get(0);
easeUser.setAvatar(updateUser.getAvatar());
easeUser.setNickname(updateUser.getNickName());
userNames.remove(username);
users.put(username, easeUser);
EventBus.getDefault().post(new MessageEvent("RefreshUser", "", null, 0));
}
}
});
subscriptions.add(subscription);
}
return easeUser;
}
记得onDestory解除订阅
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
EventBus.getDefault().unregister(MainFragment.this);
// 切断所有查询
MyApplication.getInstance().cancelSubscriptions();
}
在需要刷新UI的地方EventBus接收。
EventBus的解除和注册我就不贴了。。自己写
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(MessageEvent messageEvent) {
if ("RefreshUser".equals(messageEvent.getTag())) {
// 在服务端获取到新的用户资料 刷新好友列表
if (messageFragment != null) {
messageFragment.refresh();
}
}
}
退出登录的时候
//清理用户表
MyApplication.getInstance().getBoxStore().boxFor(User.class).removeAll();
网络请求类RetrofitUtils 普通的retrofit2 rxjava请求 不是重点 不贴了
总结
提示:环信的官方文档在这一块说明还是太少了。用的时候看一下有好多个地方重复调用相同的方法,做好判断就是了
查询操作的订阅关系要搞清楚,多次查询通过DataSubscriptionList,及时解除。
因为查询的时候是回调结果,所以需要有结果的时候存进map,刷新后取map中中的直接返回return。
map中只有查询到的结果会暂时放在内存里,算是标记用吧,即使被回收,再次查询没有意仍然会去数据库或者Server查询再保存。所以不会有被回收后看不到昵称头像的情况