android项目集成多个RN界面

公司app数量较多,为了避免手机桌面上都是app的启动图标,不方便使用。因此业务提出需求:安装一个app,进入app后,界面上显示不同图标(对应不同业务),点击不同图标,启动对应的业务界面。
我司开发平台使用的是React Native,如果按照常规做法,创建一个RN项目,所有业务都写在该项目中,则打包后的apk将越来越大,代码维护管理成本也大。
为了解决apk大小问题,确认了一个方案:原生项目集成多个RN界面,每个RN界面对应不同的业务,并且每个RN界面的bundle文件相互独立,用户可按需下载,app不会很大。
原理图:


app集成原理图.png

要实现android集成多个RN界面,需要做如下工作:

1.android工程集成react native
2.编辑ReactActivity业务界面
3.打离线包
4.图片不显示问题解决

1.android工程集成react native

1.1 创建package.json文件

在工程根目录路径下,执行npm init命令,并填写相关信息。成功后,生成package.json文件。

  • 在文件中添加"start": "node node_modules/react-native/local-cli/cli.js start"
  • 执行yarn add react-native react
//package.json
{
  "name": "react2native-demo2",
  "version": "1.0.0",
  "description": "no",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node node_modules/react-native/local-cli/cli.js start"
  },
  "author": "cjj",
  "license": "ISC",
  "dependencies": {
    "react": "^16.3.1",
    "react-native": "^0.55.1"
  }
}

1.2 .flowconfig文件

.flowconfig文件可以从facebook的github上复制,然后在工程的根目录创建.flowconfig文件,将其内容复制进去即可。

1.3 创建rn入口文件index.js

在根目录下创建index.js文件即可。

1.4 工程目录下的build.gradle文件修改

allprojects {
    repositories {
        jcenter()
        maven {
            // All of React Native (JS, Android binaries) is installed from npm
            url "$rootDir/node_modules/react-native/android"
        }
    }

    configurations.all {
        resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.0'
    }
}

添加的内容:
maven {url "$rootDir/node_modules/react-native/android"}
configurations.all { resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.0' }

1.5 app目录下的build.gradle文件修改

添加的内容:
compile "com.facebook.react:react-native:+" // From node_modules

defaultConfig {
        ...
        ndk {
            abiFilters "armeabi-v7a", "x86"
        }
    }

添加完后,Sync。

1.6 AndroidManifest.xml文件修改

添加权限:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

添加Activity:
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

1.7 gradle.properties文件

添加:
android.useDeprecatedNdk=true

2.编辑ReactActivity业务界面

创建BaseReactActivity,各业务Activity继承BaseReactActivity,重写对象的方法,加载不同的jsbundle。

public abstract class BaseReactActivity extends AppCompatActivity
        implements DefaultHardwareBackBtnHandler,PermissionAwareActivity {

    private static final String TAG = "BaseReactActivity";
    private static final String JS_BUNDLE_LOCAL_FILE = "index.android.bundle";
    private ReactInstanceManager mReactInstanceManager;
    private ReactRootView mReactRootView;
    @Nullable
    private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
    @Nullable
    private Callback mPermissionsCallback;
    @Nullable
    private PermissionListener mPermissionListener;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mReactRootView = new ReactRootView(this);
        initReactRootView();
        setContentView(mReactRootView);
    }

    protected void initReactRootView() {

        ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setJSMainModulePath(getJSMainModulePath())
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED);
        String jsBundleFile = getJSBundleFile();
        File file = null;
        if (!TextUtils.isEmpty(jsBundleFile)){
            file = new File(jsBundleFile);
        }
        if (file!=null && file.exists()){
            builder.setJSBundleFile(getJSBundleFile());
            Log.i(TAG, "load bundle from local cache");
        } else {
            String bundleAssetName = getBundleAssetName();
            builder.setBundleAssetName(TextUtils.isEmpty(bundleAssetName) ? JS_BUNDLE_LOCAL_FILE : bundleAssetName);
            Log.i(TAG, "load bundle from asset");
        }
        if (getPackages() != null){
            builder.addPackages(getPackages());
        }
        mReactInstanceManager = builder.build();
        mReactRootView.startReactApplication(mReactInstanceManager,getJsModuleName(),null);
        mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();

    }

    abstract protected String getJSMainModulePath();

    /**
     *读取bundle文件的路径,返回null时,从assets下读取
     *
     * @return
     */
    abstract protected String getJSBundleFile();

    /**
     * assets 中自带的 bundle名称
     *
     * @return
     */
    abstract protected String getBundleAssetName();

    /**
     * 自定义模块集
     * @return
     */
    abstract protected List<ReactPackage> getPackages();

    /**
     * 入口文件注册名
     * @return
     */
    abstract protected String getJsModuleName();

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mReactInstanceManager!=null){
            mReactInstanceManager.onHostPause(this);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mReactInstanceManager!=null){
            mReactInstanceManager.onHostResume(this,this);
        }
        if (mPermissionsCallback != null) {
            mPermissionsCallback.invoke();
            mPermissionsCallback = null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mReactInstanceManager!=null){
            mReactInstanceManager.onHostDestroy(this);
        }
        ReactNativePreLoader.deatchView(getJsModuleName());
    }

    @Override
    public void onBackPressed() {
        if (mReactInstanceManager!=null){
            mReactInstanceManager.onBackPressed();
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager!=null){
            mReactInstanceManager.showDevOptionsDialog();
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (mReactInstanceManager!=null) {
            mReactInstanceManager.onActivityResult(this,requestCode,resultCode,data);
        }else{
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @TargetApi(Build.VERSION_CODES.M)
    public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener){
        mPermissionListener = listener;
        requestPermissions(permissions,requestCode);
    }

    @Override
    public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
        mPermissionsCallback = new Callback() {
            @Override
            public void invoke(Object... args) {
                if (mPermissionListener != null && mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
                    mPermissionListener = null;
                }
            }
        };
    }
}

3.打离线包

react-native bundle --entry-file index.js --platform android --dev false --bundle-output ./app/src/main/assets/index.android.bundle --assets-dest ./app/src/main/res/
相关指令可前往RN官网查看。
不同业务对应的--entry-file文件不一样,打包时填写正确的入口文件名,并且--bundle-output输出的文件名也需要根据业务区分。

4.图片不显示问题解决

如果jsbundle文件在assets路径下,图片加载显示正常,但是当我们加载sd卡上的jsbundle文件时,图片不显示。针对该问题,需要修改源码(react native 0.55.1):node_modules / react-native / Libraries / Image /AssetSourceResolver.js

defaultAsset(): ResolvedAssetSource {
    if (this.isLoadedFromServer()) {
      return this.assetServerURL();
    }

    if (Platform.OS === 'android') {
      return this.isLoadedFromFileSystem()
        ? this.drawableFolderInBundle()
        : this.resourceIdentifierWithoutScale();
    } else {
      return this.scaledAssetURLNearBundle();
    }
  }

defaultAsset方法中根据平台的不同分别执行不同的图片加载逻辑。重点我们来看android platform:
drawableFolderInBundle方法为在存在离线Bundle文件时,从Bundle文件所在目录加载图片。resourceIdentifierWithoutScale方法从Asset资源目录下加载。由此,我们需要修改isLoadedFromFileSystem方法中的逻辑。

修改isLoadedFromFileSystem方法

isLoadedFromFileSystem(): boolean {  
  var imgFolder = getAssetPathInDrawableFolder(this.asset);  
  var imgName = imgFolder.substr(imgFolder.indexOf("/") + 1);  
  var isPatchImg = patchImgNames.indexOf("|"+imgName+"|") > -1;  
  return !!(this.jsbundleUrl && this.jsbundleUrl.startsWith('file://')) && isPatchImg;  
}  

注:不同react native版本,源码变量存在不同问题,需注意。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容