一、背景
首先是我们的RN项目要放入其他团队的app中,其他团队的开发不想我们的代码入侵和增加开发工作量。所以我们考虑把iOS端的RN代码封装成framework文件,把加载我们自己的bundle文件的功能封装在里面,App团队小伙伴只要调用我们的初始化方法实现加载bundle文件。Android端打包成aar文件,同样我们加载bundle 文件的方法也会封装在其中,安卓小伙伴同样调用我们的初始化实现加载我们的RN项目。整体流程如下图所示:
二、SDK开发
这个方案能搜到,爱奇艺的,百度的,但是别人的代码都没开源。我也只能摸着石头过河。
1. Android
从RN工程提取出相关文件放在RN工程新建的module(Library)中,然后通过module(Library)进行打包出aar文件,最后直接提供给总集成app使用。
生成步骤如下:
(1)创建Module(Library)
(2)把RN打包生成文件copy到对应目录下面
(3)把RN核心依赖添加到工程依赖中
(4)修改工程代码
这里重点说一下修改工程代码部分:
a . 如果你看过RN 脚手架生成的Android代码,你会发现
在安卓工程中必须要有application,这是继承ReactActivity这个类必须需要的,要不然代码会报错。但是我们是继承到别人的App中,不能修改application所以我们采用RN老的方式来加载我们的RN页面。代码如下:
package com.example.ehrbunny;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.facebook.react.BuildConfig;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactInstanceManagerBuilder;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.Promise;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import com.facebook.react.shell.MainReactPackage;
import com.reactnative.ivpusic.imagepicker.PickerPackage;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.swmansion.reanimated.ReanimatedPackage;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
public class MyReactActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
public static Promise rnPromise;
private PermissionListener permissionListener;
@Nullable
protected String getMainComponentName() {
return null;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
if(getSupportActionBar() != null) {
getSupportActionBar().hide();
}
loadBundleFromFilePath("index.android.bundle", "");
}
private void loadBundleFromFilePath(String bundleFile, String moduleName) {
mReactRootView = null;
mReactRootView = new RNGestureHandlerEnabledRootView(this);
String bundlePath = "assets://index.android.bundle";
ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.addPackages(getPackages())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.setJSBundleFile(bundlePath);
mReactInstanceManager = builder.build();
mReactRootView.startReactApplication(mReactInstanceManager, moduleName, null);
setContentView(mReactRootView);
}
private List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new MyReactPackage(),
new PickerPackage(),
new RNGestureHandlerPackage(),
new ReanimatedPackage()
);
}
@TargetApi(Build.VERSION_CODES.M)
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
permissionListener = listener;
requestPermissions(permissions, requestCode);
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (permissionListener != null && permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
permissionListener = null;
}
}
@Override
public void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause();
}
}
@Override
public void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
}
}
// 物理返回事件传递
@Override
public void onBackPressed() {
super.onBackPressed();
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode,resultCode,data);
if (mReactInstanceManager != null) {
mReactInstanceManager.onActivityResult(this,requestCode,
resultCode, data);
}
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
在继承AppCompatActivity这个类的时候必须实现DefaultHardwareBackBtnHandler, PermissionAwareActivity这两个类,要不然有些第三方的依赖不生效,比如物理返回事件传递消息、权限等。
b. 解决SDK原生通信问题
我们在代码中使用工厂模式创建共享实例。在加载SDK的时候创建实例,并创建实例方法,SDK会在需要的地方调用这个实例的方法,原生监听这个实例的状态并返回值给到SDK。具体代码不能贴。
c. SDK与RN通信问题
@ReactMethod
public String sendMsg(String msg, Promise promise) {
return "我是来自Android的信息";
}
到此Android的SDK基本完成,能加载RN页面,能与原生通信,能与RN通信。
2. iOS
相比较与Android的SDK,iOS比较麻烦。
a. 首先创建一个framework工程
b. 把加载bundle文件的方法提取出来
c. 把需要暴露出去的方法的头文件对外公布
重点介绍一下SDK的实现:
(1)加载RN页面
/**
@description 初始化RN页面
@param options NSDictionary @{@"userInfo": NSDictionary, @"debug":@YES}
*/
@interface ErRnView : UIView
-(instancetype)initWithFrame:(CGRect)frame andOptions:(NSDictionary *) options;
@end
(2)SDK与RN通信
// 接收传过来的 NSString
RCT_EXPORT_METHOD(addEventOne:(NSString *)name){
NSLog(@"接收传过来的NSString+NSString: %@", name);
}
(3)SDK与原生通信
SDK与原生通信我们使用delegate代理的方式,在方法中把方法调用生成的delegate存储,这样SDK与原生同时能够拿到这个delegate对象实现通信。源码同样不能贴。参考:如何使用delegate进行一对多通信
三、总结
很多源码由于公司限制是不能贴出来的。在开发中遇到很多困难,本身作为前端开发,对原生OC与Java代码不是很熟,一边查看文档,一边厚脸皮请教别人。最终能把SDK实现,也对自己是一种成长。