使用SDK方式加载React Native

2,134 阅读4分钟

一、背景

​ 首先是我们的RN项目要放入其他团队的app中,其他团队的开发不想我们的代码入侵和增加开发工作量。所以我们考虑把iOS端的RN代码封装成framework文件,把加载我们自己的bundle文件的功能封装在里面,App团队小伙伴只要调用我们的初始化方法实现加载bundle文件。Android端打包成aar文件,同样我们加载bundle 文件的方法也会封装在其中,安卓小伙伴同样调用我们的初始化实现加载我们的RN项目。整体流程如下图所示:

image-20211127160206450.png

二、SDK开发

​ 这个方案能搜到,爱奇艺的,百度的,但是别人的代码都没开源。我也只能摸着石头过河。

1. Android

​ 从RN工程提取出相关文件放在RN工程新建的module(Library)中,然后通过module(Library)进行打包出aar文件,最后直接提供给总集成app使用。

生成步骤如下:

(1)创建Module(Library)

(2)把RN打包生成文件copy到对应目录下面

(3)把RN核心依赖添加到工程依赖中

(4)修改工程代码

这里重点说一下修改工程代码部分:

a . 如果你看过RN 脚手架生成的Android代码,你会发现

image-20211127162344878.png

image-20211127162317222.png 在安卓工程中必须要有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实现,也对自己是一种成长。