版本
本文已 ReactNative 集成 NIM_iOS_Demo_v3.6.0 為例叫胁。Android Studio
版本為2.2.3
本人是在已有的ReactNative(以下簡稱RN)工程下集成云信IM(其他各版本集成方式大同小異)
1. 下載云信 IM demo 源碼
前往 網(wǎng)易云信 下載Android版 云信IM demo
下載完成后解壓縮,使用 Android Stuido導(dǎo)入項(xiàng)目森篷,然后運(yùn)行起來疾宏,確保下載下來是可以正常運(yùn)行的。
2. 拷貝 IM 源碼到 RN 項(xiàng)目目錄下
- 將下載下來的源碼解壓縮岩馍,拷貝源碼中的
nim_demo/uikit
目錄到RN工程的android目錄下。 - 將 nim_demo/demo 目錄下的源碼和自己項(xiàng)目下的 android/app下合并疫铜。
下載下來的 demo 和 rn 項(xiàng)目目錄結(jié)構(gòu)有點(diǎn)不一致,我這里已 rn 項(xiàng)目目錄結(jié)構(gòu)為主顽馋。
按照 Demo 代碼照貓畫虎的把所有代碼搬過去就行竟稳。
此處省略一萬字...
3. RN 界面和原生界面跳轉(zhuǎn)問題
創(chuàng)建類 RN2NativeModule
他爸,內(nèi)容如下
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);
// 注冊(cè)消息監(jiān)聽事件
this.registerReceiveMessage(reactContext, true);
}
@Override
public String getName() {
return MODULE_NAME;
}
/**
* 注冊(cè)/注銷 觀察者事件
* @param reactContext React 上下文對(duì)象
* @param isRegister true 為注冊(cè),false 為注銷
*/
private void registerReceiveMessage(final ReactApplicationContext reactContext, boolean isRegister) {
// 創(chuàng)建觀察中
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);
}
/**
* 發(fā)送事件給 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 登錄賬號(hào)
* @param password 登錄密碼
* @param promise 登錄的回調(diào)函數(shù)
*/
@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) {
// 緩存賬號(hào)
DemoCache.setAccount(account);
Preferences.saveUserAccount(account);
Preferences.saveUserToken(password);
// 初始化消息提醒配置
initNotificationConfig();
// 初始化消息提醒
NIMClient.toggleNotification(UserPreferences.getNoticeContentToggle());
// 構(gòu)建緩存
// 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());
// 加載狀態(tài)欄配置
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后臺(tái) IM 注銷");
Preferences.saveUserToken("");
NIMClient.getService(AuthService.class).logout();
// 清理緩存&注銷監(jiān)聽
LogoutHelper.logout();
}
/**
* 跳轉(zhuǎn)到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(), "跳轉(zhuǎn)失敗: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
/**
* 咨詢客服
* @param userId 用戶id
* @param textMsg 提醒內(nèi)容
*/
@ReactMethod
public void chatWithCS(String userId, String textMsg) {
IMMessage message = MessageBuilder.createTextMessage(userId, SessionTypeEnum.P2P, textMsg);
// 第二個(gè)參數(shù)表示發(fā)送失敗后重發(fā),false為不重發(fā)
NIMClient.getService(MsgService.class).sendMessage(message, true);
}
/**
* 發(fā)送tip給指定用戶
* @param userId 用戶id
* @param textMsg 提醒內(nèi)容
*/
@ReactMethod
public void p2pTipMsg(String userId, String textMsg) {
IMMessage message = MessageBuilder.createTipMessage(userId, SessionTypeEnum.P2P);
message.setContent(textMsg);
// 第二個(gè)參數(shù)表示發(fā)送失敗后重發(fā)混聊,false為不重發(fā)
NIMClient.getService(MsgService.class).sendMessage(message, false);
}
/**
* 發(fā)起p2p聊天窗
* @param userId 對(duì)方id
*/
@ReactMethod
public void toP2PChat(String userId) {
try {
SessionHelper.startP2PSession(getCurrentActivity(), userId);
} catch (Exception e) {
System.out.println("發(fā)起p2p聊天失敗: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 跳轉(zhuǎn)到指定頁
* @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(), "跳轉(zhuǎn)失敗: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
}
創(chuàng)建對(duì)應(yīng)的 package 類 RN2NativePackage
, 內(nèi)容如下
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()
);
}
當(dāng) RN 界面 調(diào)用 NativeModules.RN2Native.toYunXinIM
跳轉(zhuǎn)到原生im界面時(shí)咳胃,如下所示销睁。
此時(shí)如果想回到跳轉(zhuǎn)前的頁面冻记,我們發(fā)現(xiàn)沒有返回按鈕冗栗。因此接下來要做的就是添加返回按鈕隅居,返回到原來的 rn 頁面葛虐。
- 修改 app/src/main/res/layout/main.xml挡闰,先復(fù)制
<android.support.design.widget.AppBarLayout>
節(jié)點(diǎn)(將要粘貼到另一個(gè)文件里)摄悯,然后注釋或刪除申钩,此步操作完后瘪阁,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>
- 打開文件 app/src/main/res/layout/activity_main_tab.xml,你會(huì)發(fā)現(xiàn)這個(gè)配置文件里除了 LinearLayout 根元素之外廉涕,沒有其他子節(jié)點(diǎn)狐蜕。我們把上一步操作復(fù)制的代碼粘貼到里面层释,完成后結(jié)果如下
<?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>
- 打開文件 app/src/main/java/com/yuexing/main/activity/MainActivity.java 廉白,在
onCreate
函數(shù)中添加三行代碼蒙秒,另外注釋掉onBackPressed
函數(shù)。完成后如下代碼所示马澈。
//...
@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 運(yùn)行涤伐,然后跳轉(zhuǎn)到云信節(jié)目凝果,跳轉(zhuǎn)后如下圖器净,多了個(gè)返回按鈕,點(diǎn)擊返回則返回到 原來的 RN 頁面浪慌。
4. 去除無用功能和替換logo圖片操作
替換
app/src/main/res/drawable-hdpi
目錄下的about.logo.png
,actionbar_dark_logo_icon.png
欧宜,actionbar_white_logo_icon.png
冗茸,ic_logo.png
夏漱,ic_multiport_detail.png
挂绰,ic_stat_notify_msg.png
葵蒂,logo.png
。刪除login_bg.png
秦士,welcome_bg.png
和room_cover_*.png
隧土,刪除圖片后,代碼或配置文件里有引用到圖片的可以選擇刪除或注釋相關(guān)代碼塊卖毁。替換
app/src/main/res/drawable-mdpi
目錄下的ic_logo.png
落萎,ic_stat_notify_msg.png
替換
app/src/main/res/drawable-xdpi
目錄下的about_logo.png
亥啦,actionbar_dark_logo_icon.png
,actionbar_white_logo_icon.png
练链,ic_logo.png
翔脱,ic_multiport_detail.png
,ic_stat_notify_msg.png
媒鼓,logo.png
届吁,welcome_bg.png
替換
app/src/main/res/drawable-xxhdpi
目錄下的ic_logo.png
和ic_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, "音視頻通話網(wǎng)絡(luò)探測(cè)"));
// 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");
}