Android项目中接入网易云信聊天
首先上图
由于项目中原有的聊天出现收发消息不及时以及其他的问题,导致客服那边损失了不少的订单,遂接入新的第三方即时聊天sdk。有人可能会说,为什么不自己写呢?技术人员不够,时间长,开发成本高,最主要的是,有几个小公司自己搞聊天sdk啊!
首先看下网易云信的开发者文档,创建账号、应用,获取api key。详细请参考网易云信链接
上面有详细的接入步骤。我们下面特跟着步骤来。
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、将网易云信服务与本地服务器绑定,看下图示
请求数据接口,登录,可以看下集成与登录的关系:
主要登录业务代码
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.****.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