ReactNative集成网易云信IM Demo(Android版)

版本

本文已 ReactNative 集成 NIM_iOS_Demo_v3.6.0 为例。Android Studio版本为2.2.3
本人是在已有的ReactNative(以下简称RN)工程下集成云信IM(其他各版本集成方式大同小异)

1. 下载云信 IM demo 源码

前往 网易云信 下载Android版 云信IM demo
下载完成后解压缩,使用 Android Stuido导入项目,然后运行起来,确保下载下来是可以正常运行的。

2. 拷贝 IM 源码到 RN 项目目录下

  1. 将下载下来的源码解压缩,拷贝源码中的nim_demo/uikit目录到RN工程的android目录下。
  2. 将 nim_demo/demo 目录下的源码和自己项目下的 android/app下合并。

下载下来的 demo 和 rn 项目目录结构有点不一致,我这里已 rn 项目目录结构为主。
按照 Demo 代码照猫画虎的把所有代码搬过去就行。
此处省略一万字...

3. RN 界面和原生界面跳转问题

创建类 RN2NativeModule ,内容如下

package com.yuexing.mymodule;

import android.app.Activity;
import android.content.Intent;
import android.widget.Toast;

import com.alibaba.fastjson.JSONObject;
import com.drew.lang.annotations.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.netease.nim.uikit.cache.DataCacheManager;
import com.netease.nimlib.sdk.NIMClient;
import com.netease.nimlib.sdk.Observer;
import com.netease.nimlib.sdk.RequestCallback;
import com.netease.nimlib.sdk.StatusBarNotificationConfig;
import com.netease.nimlib.sdk.auth.AuthService;
import com.netease.nimlib.sdk.auth.LoginInfo;
import com.netease.nimlib.sdk.msg.MessageBuilder;
import com.netease.nimlib.sdk.msg.MsgService;
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;
import com.netease.nimlib.sdk.msg.model.IMMessage;
import com.yuexing.DemoCache;
import com.yuexing.MainActivity;
import com.yuexing.R;
import com.yuexing.config.preference.Preferences;
import com.yuexing.config.preference.UserPreferences;
import com.yuexing.login.LogoutHelper;
import com.yuexing.session.SessionHelper;

import java.util.List;

/**
 * Created by andy on 2017/5/9.
 */

public class RN2NativeModule extends ReactContextBaseJavaModule {

    private static final String MODULE_NAME = "RN2Native";

    private static final String MAIN_ACTIVITY_CLASSNAME = "com.yuexing.main.activity.MainActivity";

    public RN2NativeModule(ReactApplicationContext reactContext) {
        super(reactContext);

        // 注册消息监听事件
        this.registerReceiveMessage(reactContext, true);
    }

    @Override
    public String getName() {
        return MODULE_NAME;
    }

    /**
     * 注册/注销 观察者事件
     * @param reactContext React 上下文对象
     * @param isRegister true 为注册,false 为注销
     */
    private void registerReceiveMessage(final ReactApplicationContext reactContext, boolean isRegister) {
        // 创建观察中
        Observer<List<RecentContact>> messageObserver = new Observer<List<RecentContact>>() {
            @Override
            public void onEvent(List<RecentContact> recentContactList) {
                WritableMap params = Arguments.createMap();
                int count = NIMClient.getService(MsgService.class).getTotalUnreadCount();
                params.putInt("unreadCount", count);
                sendEvent(reactContext, "receiveMessage", params);
            }
        };
        NIMClient.getService(MsgServiceObserve.class).observeRecentContact(messageObserver, isRegister);
    }

    /**
     * 发送事件给 js 端
     * @param reactContext
     * @param eventName
     * @param params
     */
    private void sendEvent(ReactApplicationContext reactContext, String eventName, @Nullable WritableMap params) {
        if (reactContext != null) {
            reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
        }
    }

    /**
     * IM 登录
     * @param account 登录账号
     * @param password 登录密码
     * @param promise 登录的回调函数
     */
    @ReactMethod
    public void login(final String account, final String password, final Promise promise) {
        LoginInfo loginInfo = new LoginInfo(account, password);
        RequestCallback<LoginInfo> callback = new RequestCallback<LoginInfo>() {
            @Override
            public void onSuccess(LoginInfo loginInfo) {
                // 缓存账号
                DemoCache.setAccount(account);
                Preferences.saveUserAccount(account);
                Preferences.saveUserToken(password);

                // 初始化消息提醒配置
                initNotificationConfig();

                // 初始化消息提醒
                NIMClient.toggleNotification(UserPreferences.getNoticeContentToggle());

                // 构建缓存
                // DataCacheManager.buildDataCacheAsync();

                JSONObject json = new JSONObject();
                try {
                    int unreadCount = NIMClient.getService(MsgService.class).getTotalUnreadCount();
                    json.put("code", 200);
                    json.put("unreadCount", unreadCount);
                } catch (Exception e) {
                    promise.reject(e);
                }
                promise.resolve(json.toJSONString());
            }

            @Override
            public void onFailed(int code) {
                JSONObject json = new JSONObject();
                // { code: 302 }
                json.put("code", code);
                if (code == 302 || code == 404) {
                    json.put("message", R.string.login_failed);
                }
                promise.resolve(json.toJSONString());
            }

            @Override
            public void onException(Throwable throwable) {
                promise.reject(throwable);
            }
        };
        NIMClient.getService(AuthService.class).login(loginInfo).setCallback(callback);
    }

    /**
     * 初始化消息提醒
     */
    private void initNotificationConfig() {
        NIMClient.toggleNotification(UserPreferences.getNoticeContentToggle());

        // 加载状态栏配置
        StatusBarNotificationConfig statusBarNotificationConfig = UserPreferences.getStatusConfig();

        if (statusBarNotificationConfig == null) {
            statusBarNotificationConfig = DemoCache.getNotificationConfig();
            UserPreferences.setStatusConfig(statusBarNotificationConfig);
        }

        // 更新配置
        NIMClient.updateStatusBarNotificationConfig(statusBarNotificationConfig);
    }

    /**
     * IM 登出
     */
    @ReactMethod
    public void logout() {
        System.out.println("java后台    IM 注销");
        Preferences.saveUserToken("");
        NIMClient.getService(AuthService.class).logout();
        // 清理缓存&注销监听
        LogoutHelper.logout();
    }

    /**
     * 跳转到IM页
     */
    @ReactMethod
    public void toYunXinIM() {
        try {
            Activity currentActivity = getCurrentActivity();

            if (null != currentActivity) {
                currentActivity.startActivity(new Intent(currentActivity, Class.forName(MAIN_ACTIVITY_CLASSNAME)));
            }
        } catch (Exception e) {
            Toast.makeText(new MainActivity(), "跳转失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 咨询客服
     * @param userId 用户id
     * @param textMsg 提醒内容
     */
    @ReactMethod
    public void chatWithCS(String userId, String textMsg) {
        IMMessage message = MessageBuilder.createTextMessage(userId, SessionTypeEnum.P2P, textMsg);
        // 第二个参数表示发送失败后重发,false为不重发
        NIMClient.getService(MsgService.class).sendMessage(message, true);
    }


    /**
     * 发送tip给指定用户
     * @param userId 用户id
     * @param textMsg 提醒内容
     */
    @ReactMethod
    public void p2pTipMsg(String userId, String textMsg) {
        IMMessage message = MessageBuilder.createTipMessage(userId, SessionTypeEnum.P2P);
        message.setContent(textMsg);
        // 第二个参数表示发送失败后重发,false为不重发
        NIMClient.getService(MsgService.class).sendMessage(message, false);
    }


    /**
     * 发起p2p聊天窗
     * @param userId 对方id
     */
    @ReactMethod
    public void toP2PChat(String userId) {
        try {
            SessionHelper.startP2PSession(getCurrentActivity(), userId);
        } catch (Exception e) {
            System.out.println("发起p2p聊天失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 跳转到指定页
     * @param activityClassName
     */
    @ReactMethod
    public void toActivity(String activityClassName) {
        try {
            Activity currentActivity = getCurrentActivity();

            if (null != currentActivity) {
                Class clazz = Class.forName(activityClassName);
                Intent intent = new Intent(currentActivity, clazz);
                currentActivity.startActivity(intent);
            }
        } catch (Exception e) {
            Toast.makeText(new MainActivity(), "跳转失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

}


创建对应的 package 类 RN2NativePackage, 内容如下

package com.yuexing.mymodule;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Created by andy on 2017/5/9.
 */

public class RN2NativePackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Arrays.<NativeModule>asList(new RN2NativeModule(reactContext));
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

MainApplication 中的 getPackages 添加如下代码

@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
        new MainReactPackage(),
        // 添加如下代码
        new RN2NativePackage()
    );
}

当 RN 界面 调用 NativeModules.RN2Native.toYunXinIM 跳转到原生im界面时,如下所示。

无法返回到原来的 rn 页面

此时如果想回到跳转前的页面,我们发现没有返回按钮。因此接下来要做的就是添加返回按钮,返回到原来的 rn 页面。

  1. 修改 app/src/main/res/layout/main.xml,先复制<android.support.design.widget.AppBarLayout> 节点(将要粘贴到另一个文件里),然后注释或删除,此步操作完后,xml配置如下

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@drawable/skin_global_bg">
<!--
    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay"
        app:elevation="0dp">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:titleTextAppearance="@style/Toolbar.TitleText"/>
    </android.support.design.widget.AppBarLayout>
    -->

    <com.yuexing.common.ui.viewpager.PagerSlidingTabStrip
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="@dimen/pager_sliding_tab_strip_height"
        android:layout_below="@id/app_bar_layout"
        android:background="@drawable/skin_global_bg"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/main_tab_pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tabs"/>

    <com.netease.nim.uikit.common.ui.drop.DropCover
        android:id="@+id/unread_cover"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="invisible"
        />

</RelativeLayout>

  1. 打开文件 app/src/main/res/layout/activity_main_tab.xml,你会发现这个配置文件里除了 LinearLayout 根元素之外,没有其他子节点。我们把上一步操作复制的代码粘贴到里面,完成后结果如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/welcome_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay"
        app:elevation="0dp">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:titleTextAppearance="@style/Toolbar.TitleText" />
    </android.support.design.widget.AppBarLayout>
    
</LinearLayout>
  1. 打开文件 app/src/main/java/com/yuexing/main/activity/MainActivity.java ,在 onCreate 函数中添加三行代码,另外注释掉 onBackPressed 函数。完成后如下代码所示。
    //...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_tab);

        // 添加如下三行代码
        ToolBarOptions toolBarOptions = new ToolBarOptions();
        toolBarOptions.titleId = R.string.app_name;
        setToolBar(R.id.toolbar, toolBarOptions);

        requestBasicPermission();

        onParseIntent();

        //...
    }

    // ... 

//    @Override
//    public void onBackPressed() {
//        if (mainFragment != null) {
//            if (mainFragment.onBackPressed()) {
//                return;
//            } else {
//                moveTaskToBack(true);
//            }
//        } else {
//            super.onBackPressed();
//        }
//    }

完成上述操作后,再次在使用 Android Studio 运行,然后跳转到云信节目,跳转后如下图,多了个返回按钮,点击返回则返回到 原来的 RN 页面。

完后上述操作后,返回到 RN 界面的按钮就出来了

4. 去除无用功能和替换logo图片操作

  • 替换 app/src/main/res/drawable-hdpi 目录下的 about.logo.pngactionbar_dark_logo_icon.pngactionbar_white_logo_icon.pngic_logo.pngic_multiport_detail.pngic_stat_notify_msg.pnglogo.png。删除 login_bg.pngwelcome_bg.pngroom_cover_*.png,删除图片后,代码或配置文件里有引用到图片的可以选择删除或注释相关代码块。

  • 替换 app/src/main/res/drawable-mdpi 目录下的 ic_logo.pngic_stat_notify_msg.png

  • 替换 app/src/main/res/drawable-xdpi 目录下的 about_logo.pngactionbar_dark_logo_icon.pngactionbar_white_logo_icon.pngic_logo.pngic_multiport_detail.pngic_stat_notify_msg.pnglogo.pngwelcome_bg.png

  • 替换 app/src/main/res/drawable-xxhdpi 目录下的 ic_logo.pngic_stat_notify_msg.png

  • 替换 uikit/res/drawable-hdpi 目录下的 nim_actionbar_dark_logo_icon

  • 替换 uikit/res/drawable-xhdpi 目录下的 nim_actionbar_dark_logo_icon

  • 注释掉 app/src/main/java/com/yuexing/main/activity/SettingsActivity.java 中的以下行

// ...

//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
//            items.add(new SettingTemplate(TAG_NRTC_SETTINGS, getString(R.string.nrtc_settings)));
//            items.add(SettingTemplate.addLine());
//            items.add(new SettingTemplate(TAG_NRTC_NET_DETECT, "音视频通话网络探测"));
//            items.add(SettingTemplate.makeSeperator());
//        }

// ...

//items.add(SettingTemplate.addLine());
//items.add(new SettingTemplate(TAG_JS_BRIDGE, getString(R.string.js_bridge_demonstration)));

// ...
  • 注释掉 app/src/main/java/com/yuexing/main/activity/SettingsActivity.java 下的
// ...
//Toast.makeText(SettingsActivity.this, "收到multiport push config:" + aBoolean, Toast.LENGTH_SHORT).show();
// ...

    private void initUI() {
        initItems();
        listView = (ListView) findViewById(R.id.settings_listview);
//        View footer = LayoutInflater.from(this).inflate(R.layout.settings_logout_footer, null);
//        listView.addFooterView(footer);

        initAdapter();
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                SettingTemplate item = items.get(position);
                onListItemClick(item);
            }
        });
//        View logoutBtn = footer.findViewById(R.id.settings_button_logout);
//        logoutBtn.setOnClickListener(new View.OnClickListener() {
//            @Override
//            public void onClick(View v) {
//                logout();
//            }
//        });
    }

// ...
  • 修改 uikit/src/com/netease/nim/uikit/common/ui/drop/DropManager.java 中的 destroy ,修改如下
    public void destroy() {
        this.isTouchable = false;
        this.statusBarHeight = 0;
        // 判断 this.dropCover 是否为空
        if (this.dropCover != null) {
            this.dropCover.removeAllDropCompletedListeners();
            this.dropCover = null;
        }
        this.currentId = null;
        this.textPaint = null;
        this.textYOffset = 0;
        this.circlePaint = null;
        this.enable = false;
        LogUtil.i(TAG, "destroy DropManager");
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 本文会不定期更新,推荐watch下项目。如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以...
    天之界线2010阅读 18,073评论 19 154
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,204评论 0 17
  • 2016年5月我迎来了最糟糕的日子 !毕业!对于如今的大学生来说 、毕业真的就是一场噩梦!成千上万的人过独木桥。还...
    阵雨哗哗下阅读 169评论 0 0
  • 第二百五十七天 最近一直很抑郁,心态并不是很好。想的问题很多,越想越陷入恶性循环。不知道活着的意义,生命的价值在哪...
    95f8f0c20b38阅读 284评论 1 1