版本
1. 下载云信 IM demo 源码
2. 拷贝 IM 源码到 RN 项目目录下
- 将下载下来的源码解压缩,拷贝源码中的
nim_demo/uikit
目录到RN工程的android目录下。 - 将 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
import
import
import
import
import
import
import
import
import
import
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 页面。
- 修改 app/src/main/res/layout/main.xml,先复制
<android.support.design.widget.AppBarLayout>
节点(将要粘贴到另一个文件里),然后注释或删除,此步操作完后,xml配置如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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>
-->
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"/>
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,你会发现这个配置文件里除了 LinearLayout 根元素之外,没有其他子节点。我们把上一步操作复制的代码粘贴到里面,完成后结果如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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
函数中添加三行代码,另外注释掉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.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
,删除图片后,代码或配置文件里有引用到图片的可以选择删除或注释相关代码块。 -
替换
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, "音视频通话网络探测"));
// 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");
}