Android项目中接入网易云信聊天

首先上图

由于项目中原有的聊天出现收发消息不及时以及其他的问题,导致客服那边损失了不少的订单,遂接入新的第三方即时聊天sdk。有人可能会说,为什么不自己写呢?技术人员不够,时间长,开发成本高,最主要的是,有几个小公司自己搞聊天sdk啊!

首先看下网易云信的开发者文档,创建账号、应用,获取api key。详细请参考网易云信链接

https://dev.yunxin.163.com/docs/product/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E8%AE%AF/%E6%96%B0%E6%89%8B%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97

上面有详细的接入步骤。我们下面特跟着步骤来。

1、集成sdk进入项目中,文档上给出两种集成方式,通过Gradle和类库配置sdk,推荐是前一种方式。

2、sdk初始化工作

文档上有以下说明,可以在任意位置初始化

这里公司的项目是在MainActivity和Application做了初始化的处理,看下代码:

 private void initUIKit() {
        // 初始化
        NimUIKit.init(this, buildUIKitOptions());

        // IM 会话窗口的定制初始化。
        SessionHelper.init();
    }

    private UIKitOptions buildUIKitOptions() {
        UIKitOptions options = new UIKitOptions();
        // 设置app图片/音频/日志等缓存目录
        options.appCacheDir = NimSDKOptionConfig.getAppCacheDir(this) + "/app";
        return options;
    }

在Application中,主要是这句SessionHelper.init();// IM 会话窗口的定制初始化。然后是获取登录的accid和token,这两个参数具体是什么作用,在此就不多做说明,可以看下云信文档说明

  private void initIMConfig() {
        NIMClient.init(this, loginInfo(), new SDKOptions());
    }

    private SDKOptions options() {
        SDKOptions options = new SDKOptions();
        return options;
    }

    private LoginInfo loginInfo() {
        String account = SPUtils.getInstance().getString("accid");
        String token = SPUtils.getInstance().getString("token");

        if (!TextUtils.isEmpty(account) && !TextUtils.isEmpty(token)) {
            return new LoginInfo(account, token);
        }
        return null;
    }

以上代码需要在onCreat()中进行。

3、网易云信的聊天功能主要集中在uikit中,需要作为library导入到项目中,图示1-2所示可以看到具体依赖哪个modlue

4、将网易云信服务与本地服务器绑定,看下图示

 请求数据接口,登录,可以看下集成与登录的关系:

https://dev.yunxin.163.com/docs/product/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E8%AE%AF/%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D/%E5%B8%90%E5%8F%B7%E9%9B%86%E6%88%90%E4%B8%8E%E7%99%BB%E5%BD%95

主要登录业务代码

private void getIMToken() {
        if (!LoginHelper.isLogin()) {
            return;
        }
        RestClient.builder()
                .url("这里填后端给的数据接口")
                .params("uid", LoginHelper.uid())
                .params("secret", LoginHelper.secret())
                .params("type", "2")
                .success(this::handleIMResult)
                .build()
                .post();
    }

    private void handleIMResult(String response) {
        JLogger.e("TIM", "handleIMResult: " + response);
        final JSONObject jsonObject = JSON.parseObject(response);
        if (JConstants.OK.equals(jsonObject.getString("code"))) {
            final JSONObject data = jsonObject.getJSONObject("data");
            final String token = data.getString("token");
            final String accid = data.getString("accid");
            //将accid和token值存放到本地
            SPUtils.getInstance().put("accid", accid);
            SPUtils.getInstance().put("token", token);

            final LoginInfo info = new LoginInfo(accid, token);

            NIMClient.getService(AuthService.class).login(info)
                    .setCallback(new RequestCallback() {//sdk提供的手动登录方法
                        @Override
                        public void onSuccess(Object param) {
                            JLogger.e("IM onSuccess: " + param.toString());
                            initUnReadMessage();//未读消息
                            initUserMessage();//更新用户本人资料
                        }

                        @Override
                        public void onFailed(int code) {
                            JLogger.e("IM onFailed.");
                        }

                        @Override
                        public void onException(Throwable exception) {
                            JLogger.e("IM onException.");
                        }
                    });


        } else if (JConstants.SECRET_ERROR.equals(jsonObject.getString("code"))) {
            LoginHelper.loginOut();
            getSupportDelegate().start(new EcLoginDelegate());
        }
    }

手动解析获取accid和token值。

登陆成功只能算完成了一小部分,接下来开始痛苦的调试移植删除工作了。

5、会话列表

要做到开头gif图的效果,RecentContactsFragment.java(这个类就是会话列表类)类需要继承项目中的JumeiDelegate(这个类是项目的应用Fragment基类,具体怎么回事就不做说明了,我们老大封装好了一系列的方法。)如此这般点击消息就能跳转到会话列表了。

由于这部分代码和网易云信的demo一样,我就不贴了。

6、启动单人会话列表

package com.jm.ec.im;

import android.content.Context;

import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;

import cn.faxingw.uikit.api.NimUIKit;
import cn.faxingw.uikit.business.session.viewholder.SessionHelper;


public class ChatHelper {//项目中多处出现调用会话窗口,这里做了封装方便调用

    public static void chat(Context context, String userId) {//启动单人会话的方法,传入accid即可
        NimUIKit.startChatting(context, userId, SessionTypeEnum.P2P,
                SessionHelper.getMyP2pCustomization(), null);
    }

}

7、单人会话列表的时下

单聊的方法主要在P2PMessageActivity.java类中,看下代码

package cn.faxingw.uikit.business.session.activity;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.netease.nimlib.sdk.NIMClient;
import com.netease.nimlib.sdk.Observer;
import com.netease.nimlib.sdk.RequestCallback;
import com.netease.nimlib.sdk.friend.FriendService;
import com.netease.nimlib.sdk.friend.constant.VerifyType;
import com.netease.nimlib.sdk.friend.model.AddFriendData;
import com.netease.nimlib.sdk.msg.MsgServiceObserve;
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;
import com.netease.nimlib.sdk.msg.model.CustomNotification;
import com.netease.nimlib.sdk.msg.model.IMMessage;

import java.util.List;
import java.util.Set;

import cn.faxingw.uikit.R;
import cn.faxingw.uikit.api.NimUIKit;
import cn.faxingw.uikit.api.model.contact.ContactChangedObserver;
import cn.faxingw.uikit.api.model.main.OnlineStateChangeObserver;
import cn.faxingw.uikit.api.model.session.SessionCustomization;
import cn.faxingw.uikit.api.model.user.UserInfoObserver;
import cn.faxingw.uikit.api.wrapper.NimToolBarOptions;
import cn.faxingw.uikit.business.session.constant.Extras;
import cn.faxingw.uikit.business.session.fragment.MessageFragment;
import cn.faxingw.uikit.business.uinfo.UserInfoHelper;
import cn.faxingw.uikit.common.activity.ToolBarOptions;
import cn.faxingw.uikit.common.ui.imageview.HeadImageView;
import cn.faxingw.uikit.impl.NimUIKitImpl;


/**
 * 点对点聊天界面
 * <p/>
 * Created by huangjun on 2015/2/1.
 */
public class P2PMessageActivity extends BaseMessageActivity {

    private boolean isResume = false;
    private boolean naviToStylistDetail = false;
    private String contactId;

    private HeadImageView avatarRight;
    public static void start(Context context, String contactId, SessionCustomization customization, IMMessage anchor, boolean naviToStylistDetail) {
        Intent intent = new Intent();
        intent.putExtra(Extras.EXTRA_ACCOUNT, contactId);
        intent.putExtra(Extras.EXTRA_CUSTOMIZATION, customization);
        intent.putExtra("naviToStylistDetail", naviToStylistDetail);
        if (anchor != null) {
            intent.putExtra(Extras.EXTRA_ANCHOR, anchor);
        }
        intent.setClass(context, P2PMessageActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);

        context.startActivity(intent);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 单聊特例话数据,包括个人信息,
        requestBuddyInfo();
//        setHeadView();
        displayOnlineState();
        registerObservers(true);
        registerOnlineStateChangeListener(true);
    }

        private void setHeadView() {
        avatarRight = (HeadImageView)findViewById(R.id.message_item_portrait_right);
        avatarRight.loadBuddyAvatar(sessionId);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        registerObservers(false);
        registerOnlineStateChangeListener(false);
    }

    @Override
    protected void onResume() {
        super.onResume();
        isResume = true;
    }

    @Override
    protected void onStop() {
        super.onStop();
        isResume = false;
    }

    private void requestBuddyInfo() {
        setTitle(UserInfoHelper.getUserTitleName(sessionId, SessionTypeEnum.P2P));
        doAddFriend(null, true);  // 直接加为好友
        naviToStylistDetail = getIntent().getBooleanExtra("naviToStylistDetail", false);
    }

    private void doAddFriend(String msg, boolean addDirectly) {
        final VerifyType verifyType = addDirectly ? VerifyType.DIRECT_ADD : VerifyType.VERIFY_REQUEST;
        NIMClient.getService(FriendService.class).addFriend(new AddFriendData(sessionId, verifyType, msg))
                .setCallback(new RequestCallback<Void>() {
                    @Override
                    public void onSuccess(Void param) {
                        if (VerifyType.DIRECT_ADD == verifyType) {
                            Log.d("TAG", "添加好友成功");
                        } else {
                            Log.d("TAG", "添加好友请求发送成功");
                        }
                    }

                    @Override
                    public void onFailed(int code) {
                        if (code == 408) {
                            Toast.makeText(P2PMessageActivity.this, R.string.network_is_not_available, Toast
                                    .LENGTH_SHORT).show();
                        } else {
                            Log.i("TAG","on failed:"+code);
                        }
                    }

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

    }


    private void registerObservers(boolean register) {
        if (register) {
            registerUserInfoObserver();
        } else {
            unregisterUserInfoObserver();
        }
        NIMClient.getService(MsgServiceObserve.class).observeCustomNotification(commandObserver, register);
        NimUIKit.getContactChangedObservable().registerObserver(friendDataChangedObserver, register);
    }

    ContactChangedObserver friendDataChangedObserver = new ContactChangedObserver() {
        @Override
        public void onAddedOrUpdatedFriends(List<String> accounts) {
            setTitle(UserInfoHelper.getUserTitleName(sessionId, SessionTypeEnum.P2P));
        }

        @Override
        public void onDeletedFriends(List<String> accounts) {
            setTitle(UserInfoHelper.getUserTitleName(sessionId, SessionTypeEnum.P2P));
        }

        @Override
        public void onAddUserToBlackList(List<String> account) {
            setTitle(UserInfoHelper.getUserTitleName(sessionId, SessionTypeEnum.P2P));
        }

        @Override
        public void onRemoveUserFromBlackList(List<String> account) {
            setTitle(UserInfoHelper.getUserTitleName(sessionId, SessionTypeEnum.P2P));
        }
    };

    private UserInfoObserver uinfoObserver;

    OnlineStateChangeObserver onlineStateChangeObserver = new OnlineStateChangeObserver() {
        @Override
        public void onlineStateChange(Set<String> accounts) {
            // 更新 toolbar
            if (accounts.contains(sessionId)) {
                // 按照交互来展示
                displayOnlineState();
            }
        }
    };

    private void registerOnlineStateChangeListener(boolean register) {
        if (!NimUIKitImpl.enableOnlineState()) {
            return;
        }
        NimUIKitImpl.getOnlineStateChangeObservable().registerOnlineStateChangeListeners(onlineStateChangeObserver, register);
    }

    private void displayOnlineState() {
        if (!NimUIKitImpl.enableOnlineState()) {
            return;
        }
        String detailContent = NimUIKitImpl.getOnlineStateContentProvider().getDetailDisplay(sessionId);
        setSubTitle(detailContent);
    }

    private void registerUserInfoObserver() {
        if (uinfoObserver == null) {
            uinfoObserver = new UserInfoObserver() {
                @Override
                public void onUserInfoChanged(List<String> accounts) {
                    if (accounts.contains(sessionId)) {
                        requestBuddyInfo();
                    }
                }
            };
        }
        NimUIKit.getUserInfoObservable().registerObserver(uinfoObserver, true);
    }

    private void unregisterUserInfoObserver() {
        if (uinfoObserver != null) {
            NimUIKit.getUserInfoObservable().registerObserver(uinfoObserver, false);
        }
    }

    /**
     * 命令消息接收观察者
     */
    Observer<CustomNotification> commandObserver = new Observer<CustomNotification>() {
        @Override
        public void onEvent(CustomNotification message) {
            if (!sessionId.equals(message.getSessionId()) || message.getSessionType() != SessionTypeEnum.P2P) {
                return;
            }
            showCommandMessage(message);
        }
    };

    protected void showCommandMessage(CustomNotification message) {
        if (!isResume) {
            return;
        }

        String content = message.getContent();
        try {
            JSONObject json = JSON.parseObject(content);
            int id = json.getIntValue("id");
            if (id == 1) {
                // 正在输入
                Toast.makeText(P2PMessageActivity.this, "对方正在输入...", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(P2PMessageActivity.this, "command: " + content, Toast.LENGTH_SHORT).show();
            }

        } catch (Exception e) {

        }
    }

    @Override
    protected MessageFragment fragment() {
        Bundle arguments = getIntent().getExtras();
        arguments.putSerializable(Extras.EXTRA_TYPE, SessionTypeEnum.P2P);
        MessageFragment fragment = new MessageFragment();
        fragment.setArguments(arguments);
        fragment.setContainerId(R.id.message_fragment_container);
        return fragment;
    }

    @Override
    protected int getContentViewId() {
        return R.layout.nim_message_activity;
    }

    @Override
    protected void initToolBar() {
        ToolBarOptions options = new NimToolBarOptions();
        setToolBar(R.id.toolbar, options);
    }
}

这里说明下doAddFriend方法,网易云信demo中开始和对方会话是先加好友,或是经过对方同意后加好友,这里直接加好友后开始聊天。

服务器端在加用户好友之前需要获取用户的信息,将用户的sid转换为accid。这个方法放在了MessageFragment.java中的getFriendInfo()方法

private void getFriendInfo() {
        RestClient.builder()
                .url("这里是用户信息接口")
                .params("accid", sessionId)
                .success(new ISuccess() {
                    @Override
                    public void onSuccess(String response) {
                        JLogger.e(response);
                        final JSONObject jsonObject = JSON.parseObject(response);
                        if ("101".equals(jsonObject.getString("code"))) {
                            StylistEntity stylistEntity = StylistEntityConverter.convert(sessionId, response);
                            Jumei.getConfigurator().withStylistId(stylistEntity.getAccid());
                        }
                    }
                })
                .error(new IError() {
                    @Override
                    public void onError(int code, String msg) {
                        Jumei.getConfigurator().withStylistId("");
                    }
                })
                .failure(new IFailure() {
                    @Override
                    public void onFailure() {
                        Jumei.getConfigurator().withStylistId("");
                    }
                })
                .build()
                .post();
    }

再来看setTitle(UserInfoHelper.getUserTitleName(sessionId, SessionTypeEnum.P2P));这个方法是获取对方的姓名,网上有人在这里遇到坑了,可以看下这篇文章https://blog.csdn.net/brucechen1994/article/details/79787896

我的方法是直接绕过去了

package cn.faxingw.uikit.business.uinfo;

import android.text.TextUtils;

import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;
import com.netease.nimlib.sdk.uinfo.model.UserInfo;

import cn.faxingw.uikit.api.NimUIKit;
import cn.faxingw.uikit.business.team.helper.TeamHelper;

public class UserInfoHelper {

    // 获取用户显示在标题栏和最近联系人中的名字
    public static String getUserTitleName(String id, SessionTypeEnum sessionType) {
        if (sessionType == SessionTypeEnum.P2P) {
            String account = NimUIKit.getAccount();
            if (account !=null) {
                return "我的电脑";
            } else
                {
                return getUserDisplayName(id);
            }
        } else if (sessionType == SessionTypeEnum.Team) {
            return TeamHelper.getTeamName(id);
        }
        return id;
    }

    /**
     * @param account 用户帐号
     * @return
     */
    public static String getUserDisplayName(String account) {
        String alias = NimUIKit.getContactProvider().getAlias(account);
        if (!TextUtils.isEmpty(alias)) {
            return alias;
        } else {
            UserInfo userInfo = NimUIKit.getUserInfoProvider().getUserInfo(account);
            if (userInfo != null && !TextUtils.isEmpty(userInfo.getName())) {
                return userInfo.getName();
            } else {
                return account;
            }
        }
    }

    // 获取用户原本的昵称
    public static String getUserName(String account) {
        UserInfo userInfo = NimUIKit.getUserInfoProvider().getUserInfo(account);
        if (userInfo != null && !TextUtils.isEmpty(userInfo.getName())) {
            return userInfo.getName();
        } else {
            return account;
        }
    }

    /**
     * @param account         账号
     * @param selfNameDisplay 如果是自己,则显示内容
     * @return
     */
    public static String getUserDisplayNameEx(String account, String selfNameDisplay) {
        if (account.equals(NimUIKit.getAccount())) {
            return selfNameDisplay;
        }

        return getUserDisplayName(account);
    }
}

项目中注释了原有的很多东西才跑通、、调试的过程不说也罢。其他的就是些网易云信原本的东西了。至此,项目引进网易云聊天,当然后续的点击客服头像跳转到发型师详情页面,这涉及到EventBus的知识,下次再说咯。

最后贴上我项目中uikit聊天的百度云链接,其实基本上和网易云信的demo是相同的,只是注释了定位,群聊,聊天室等一些项目中用不到的功能,只是简单的聊天而已

uikit

https://pan.baidu.com/s/1ymXV3FnD17P1giOF8Q1qlg 

网易云信demo百度云链接

https://pan.baidu.com/s/1ekML668Sp6ukuYyCW0E60w

猜你喜欢

转载自blog.csdn.net/yun382657988/article/details/83376700