使用SDK方式載入React Native

語言: CN / TW / HK

一、背景

​ 首先是我們的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頁面。程式碼如下:

```java 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通訊問題

java @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頁面

```objective-c /* @description 初始化RN頁面 @param options NSDictionary @{@"userInfo": NSDictionary, @"debug":@YES} / @interface ErRnView : UIView

-(instancetype)initWithFrame:(CGRect)frame andOptions:(NSDictionary *) options;

@end

```

(2)SDK與RN通訊

```objective-c // 接收傳過來的 NSString RCT_EXPORT_METHOD(addEventOne:(NSString *)name){ NSLog(@"接收傳過來的NSString+NSString: %@", name);

} ```

(3)SDK與原生通訊

SDK與原生通訊我們使用delegate代理的方式,在方法中把方法呼叫生成的delegate儲存,這樣SDK與原生同時能夠拿到這個delegate物件實現通訊。原始碼同樣不能貼。參考:如何使用delegate進行一對多通訊

三、總結

​ 很多原始碼由於公司限制是不能貼出來的。在開發中遇到很多困難,本身作為前端開發,對原生OC與Java程式碼不是很熟,一邊檢視文件,一邊厚臉皮請教別人。最終能把SDK實現,也對自己是一種成長。