1. 程式人生 > >Android專案中接入網易雲信聊天

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