Android ObjectBox 实现环信显示用户昵称头像的解决方案


前言

最近因为项目需要即时通讯,项目集成了环信的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.csdn.net/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查询再保存。所以不会有被回收后看不到昵称头像的情况

猜你喜欢

转载自blog.csdn.net/qq_39178733/article/details/108941721
今日推荐