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");
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,466評論 25 708
  • 本文會不定期更新,推薦watch下項目。如果喜歡請star,如果覺得有紕漏請提交issue,如果你有更好的點子可以...
    天之界線2010閱讀 18,418評論 19 153
  • ¥開啟¥ 【iAPP實現進入界面執行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,537評論 0 17
  • 2016年5月我迎來了最糟糕的日子 !畢業!對于如今的大學生來說 、畢業真的就是一場噩夢!成千上萬的人過獨木橋。還...
    陣雨嘩嘩下閱讀 189評論 0 0
  • 第二百五十七天 最近一直很抑郁,心態并不是很好。想的問題很多,越想越陷入惡性循環。不知道活著的意義,生命的價值在哪...
    95f8f0c20b38閱讀 312評論 1 1