React Native实现多业务热部署

刚好最近在研究APP接入第三方业务功能的需求,本文总结一下Android平台如何使用React Native实现多业务接入并能满足各个子业务独立更新维护的要求。


一、场景分析

比如我有一个APP(暂叫主APP),现在需要接入第三方的业务(业务A、业务B、业务C等…)。但是主APP跟第三方业务是完全独立的,我们不希望主APP中嵌入大量的第三方业务代码(当然第三方也不会愿意暴露代码给主APP~),也不希望子业务更新时太依赖主APP。那怎么办呢?现在主流的做法一般是使用H5页面跳转,但是这种体验较差。除了H5之外我们想到过的能解决问题并且体验还不错的方案就是React Native、Weex、LuaView、Cordova等框架。子业务只需要把自身热更新相关的代码和需要使用Native实现的代码打包成一个很小的SDK(aar)移交给主程序集成,剩下的业务页面展示功能全部通过在线更新加载来实现。React Native、Weex、LuaView、Cordova等框架更新资源包的原理大同小异,大概分如下几步:

  1. 携带本地资源包的版本号等信息调用接口
  2. 后台根据前端传过来的版本信息告诉前端是否有升级包
  3. 有升级包则下载升级包保存到SD卡上(增量升级包或全量升级包下载)
  4. 下次启动APP时读取新的资源包

后面有时间再记录下详细的热更新实现方案,这里主要记录下载多个资源包的问题。前面也说了,我们要实现接入多个业务的需求,这就存在一个问题:APP中存在多个业务入口,如何实现进入不同业务模块时下载并加载不同的业务数据呢?比如:进入业务模块A时更新业务A的资源包并且加载A的数据,进入业务模块B时更新业务B的资源包并加载B的数据。
React Native是使用getJSBundleFile方法来指定加载JSBundle路径的,但是0.29版本开始发生了变化。0.29之前getJSBundleFile方法在ReactActivity类中,这个时候要实现上面的需求比较简单,只需要在各个业务的主Activity中集成ReactActivity并重写getJSBundleFile方法即可。但是0.29及以后的版本中ReactActivity中移除了getJSBundleFile方法,该方法在ReactNativeHost中,并且是在ReactApplication中初始化,也就是说一个应用只能初始化一个JSBundle路径,那怎么办呢?

二、解决思路

我们先来看看React Native加载Bundle的大概流程:

1.ReactActivity类

加载页面是从自定义的Activity开始,该Activity需要继承ReactActivity,里面有2个很重要的方法getMainComponentName和createReactActivityDelegate。

/**
   * Returns the name of the main component registered from JavaScript.
   * This is used to schedule rendering of the component.
   * e.g. "MoviesApp"
   */
  protected @Nullable String getMainComponentName() {
    return null;
  }

  /**
   * Called at construction time, override if you have a custom delegate implementation.
   */
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName());
  }

getMainComponentName是用来指定加载的React Native组件的名称,需要在自定义Activity中重写改方法,指定组件名称,该名称需要和index.js中注册的组件名称一致。

import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('组件名称', () => App);

2.ReactActivityDelegate类

createReactActivityDelegate方法是用来创建ReactActivityDelegate对象,ReactActivityDelegate类中有一个获取前面提到过的ReactApplication中的ReactNativeHost对象。

/**
   * Get the {@link ReactNativeHost} used by this app. By default, assumes
   * {@link Activity#getApplication()} is an instance of {@link ReactApplication} and calls
   * {@link ReactApplication#getReactNativeHost()}. Override this method if your application class
   * does not implement {@code ReactApplication} or you simply have a different mechanism for
   * storing a {@code ReactNativeHost}, e.g. as a static field somewhere.
   */
  protected ReactNativeHost getReactNativeHost() {
    return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
  }

从代码可以看到,app中使用的ReactNativeHost对象只有一份,也就是说默认情况下载Application中就已经定义好了JSBundle的加载路径,而且一次只能设置一个路径,更别说要实现不同子业务加载不同的JSBundle了。那我要实现不同子业务加载不同的JSBundle怎么办呢?我们可以自定义ReactActivityDelegate类,并且在自定义的Activity中重写父类中的createReactActivityDelegate方法,使用我们自定义的ReactActivityDelegate类。自定义ReactActivityDelegate有两种方法,一种方法是直接继承ReactActivityDelegate类,另一种方法是仿照ReactActivityDelegate类重写一个,但是后面那种方法个人觉得把整个类移植可能存在不可预测的风险。那就只能继承ReactActivityDelegate写一个了,那问题有来了,大家发现getReactNativeHost方法是protected类型的,直接继承的话不能重写该方法,我们可以变通一下,java中在相同的包名下其他类是可以访问protected方法的,所以我们可以在建一个和ReactActivityDelegate一样的包名,把自定义的类写在该包名下即可。

三、代码实现

1.环境

node.js:6.11.1
npm:6.1.0
react-native-cli:2.0.1
react-native:0.55.4

2.主APP

主APP主要是用来模拟实现个子业务入口,集成React Native基础框架和个子业务提供的SDK(aar)。

1) build.gradle

配置React Native环境,引入第三方业务的SDK

dependencies {
    compile fileTree(dir: "libs", include: ["*.jar"])
    compile "com.android.support:appcompat-v7:23.0.1"
    compile "com.facebook.react:react-native:+"  // From node_modules
    compile(name:'modelone',ext:'aar')
    compile(name:'modeltwo',ext:'aar')
    compile(name:'modelthree',ext:'aar')
}

2)MainApplication类

该类集成ReactApplication,主要用来初始化so动态库以及定义ReactNativeHost对象,指定主JSBundle文件路径。

package com.luoxudong.app.rndynamicload;

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

import java.util.Arrays;
import java.util.List;

import javax.annotation.Nullable;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    public boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage()
      );
    }

    @Override
    protected String getJSMainModuleName() {
      return "index";
    }

    @Nullable
    @Override
    protected String getJSBundleFile() {
      return super.getJSBundleFile();
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
  }
}

3.子业务SDK

我业务集成到主APP时,需要提供提供一个SDK,该SDK的作用是提供业务主入口供主APP调用,实现自身业务JSBundle等资源的升级更新以及封装Native端的代码(如:敏感数据处理,自定义插件等)。
1) 自定义ReactActivityDelegate类

该类是实现多业务热更新接入的关键,集成ReactActivityDelegate并重写getReactNativeHost方法。以其中一个子业务为例:

package com.facebook.react;

import android.app.Activity;
import android.support.v4.app.FragmentActivity;

import com.facebook.react.shell.MainReactPackage;

import java.util.Arrays;
import java.util.List;

import javax.annotation.Nullable;

/**
 * Created by luoxudong on 2018/7/2.
 */

public class MOReactActivityDelegate extends ReactActivityDelegate {
    private Activity mActivity = null;

    public MOReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
        super(activity, mainComponentName);
        mActivity = activity;
    }

    public MOReactActivityDelegate(FragmentActivity fragmentActivity, @Nullable String mainComponentName) {
        super(fragmentActivity, mainComponentName);
        mActivity = fragmentActivity;
    }

    @Override
    protected ReactNativeHost getReactNativeHost() {
        return new ReactNativeHost(mActivity.getApplication()) {
            @Override
            public boolean getUseDeveloperSupport() {
                return BuildConfig.DEBUG;
            }

            @Override
            protected List<ReactPackage> getPackages() {
                return Arrays.<ReactPackage>asList(
                        new MainReactPackage()
                );
            }

            @Override
            protected String getJSMainModuleName() {
                return "index";
            }

            @Nullable
            @Override
            protected String getJSBundleFile() {
                /**
                 * 为了简单,这个路径先写死
                 * 实际开发中这个地址一般是不写死的,路径可能会变化,比如不同版本的bundle资源放在不同的目录中。
                 * 由于是写死在SD卡路径,记得6.0+系统要授权访问。
                 */
                String jsBundeFile = "/sdcard/rn/modelone/index.android.bundle";
                return jsBundeFile;
            }
        };
    }
}

在getJSBundleFile方法中指定加载JSBundle文件的路径,其中index.android.bundle是生成的bundle的js文件。实际开发过程中这里需要结合动态更新的流程来,这里简单处理先写死路径。要生成bundle文件,可以先在package.json中修改如下配置:

"scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest",
    "android-bundle": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output bundle/android/index.android.bundle --assets-dest bundle/android/assets",
    "ios-bundle": "react-native bundle --platform ios --dev false --entry-file index.js --bundle-output bundle/ios/index.android.bundle --assets-dest bundle/ios/assets"
  },

配置完成以后需要手动创建 bundle/android和bundle/ios目录,然后在控制台运行npm run android-bundle即可。

2)子业务主界面
ReactNativeActivity为业务的主界面,继承ReactActivity并且实现getMainComponentName方法,返回js中的组件名称,名称需要跟index.js中的组件名称一致。另外一个很重要的工作就是重写createReactActivityDelegate方法,使用自定义的MOReactActivityDelegate对象,这样程序进入该界面时就会加载在业务的资源,于主程序及其他业务完全独立。注意,实际开发啊过程中一般来说这个Activity不是子业务的入口,子业务的入口应该是实现热更新相关的业务逻辑,热更新检测处理完以后再进入该页面。

package com.luoxudong.app.modelone;

import com.facebook.react.MOReactActivityDelegate;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;

/**
 * Created by luoxudong on 2018/7/4.
 */

public class ReactNativeActivity extends ReactActivity {
    @Override
    protected String getMainComponentName() {
        /**
         * 这个组件名称需要跟js入口中的组件名称保持一致
         */
        String componentName = "modelone";
        return componentName;
    }

    @Override
    protected ReactActivityDelegate createReactActivityDelegate() {
        /**
         * 进入这个界面时会初始化加载bundle的路径。
         * 这里的demo是实现把bundle文件放在了sd卡中,没有写动态更新升级相关业务代码,
         * 实际项目开发过程中需要考虑更新机制,可能事前需要做一些更新资源文件等前置工作
         */
        ReactActivityDelegate delegate = new MOReactActivityDelegate(this, getMainComponentName());
        return delegate;
    }
}

以上工作完成以后就可以打包成aar,提供给主程序集成。接下来就是使用React Native语法实现界面布局以及业务实现,然后通过bundle命令打包放在服务器上供各业务更新。这样整个流程基本上就完成了。

四、实现效果

以下是简单的实现效果:

五、其他

以上哪里写的不对或者有待改进,欢迎大家提意见,谢谢!
源码地址:https://github.com/rohsuton/RnDynamicLoad
转载请注明出处:http://www.luoxudong.com/?p=393

有39人对 “React Native实现多业务热部署”留言了

  1. I’m also commenting to let you be aware of of the excellent discovery my child encountered checking your blog. She came to understand a wide variety of details, most notably how it is like to have a marvelous coaching heart to get folks easily fully grasp a variety of complicated matters. You actually did more than my desires. I appreciate you for delivering such great, dependable, edifying and also cool guidance on that topic to Evelyn.

  2. I precisely desired to say thanks again. I’m not certain the things I could possibly have achieved in the absence of the basics contributed by you about my area of interest. Completely was a challenging circumstance for me, but being able to view a expert avenue you dealt with it forced me to cry for contentment. Extremely grateful for the advice and believe you are aware of a powerful job that you’re accomplishing educating many people all through a site. I’m certain you’ve never come across all of us.

  3. Thanks for your own work on this website. My mum takes pleasure in setting aside time for research and it is simple to grasp why. We notice all relating to the powerful way you provide very important tips and hints via your website and therefore strongly encourage response from visitors on this theme while our own simple princess is without a doubt understanding so much. Take pleasure in the rest of the year. Your performing a useful job.

  4. I am just writing to make you understand what a perfect experience my wife’s princess obtained using your webblog. She noticed some things, which included what it is like to possess an excellent teaching spirit to make others with no trouble learn about specific specialized things. You undoubtedly surpassed readers’ expected results. Thanks for providing those priceless, trusted, informative as well as unique tips on your topic to Mary.

  5. I am glad for writing to make you know what a magnificent encounter my friend’s girl gained studying your web site. She learned several pieces, which include what it’s like to possess an awesome helping nature to let other folks very easily know just exactly several multifaceted subject matter. You truly exceeded people’s expectations. Thank you for presenting these powerful, safe, explanatory and in addition fun tips about this topic to Kate.

  6. I happen to be writing to make you understand what a wonderful experience our princess went through visiting yuor web blog. She realized too many things, with the inclusion of what it is like to possess an incredible teaching style to make the rest without problems understand various very confusing topics. You really exceeded our expectations. Many thanks for distributing the informative, healthy, educational and as well as easy thoughts on the topic to Jane.

  7. Excellent way of describing, and good article to take information concerning my presentation subject, which i am going to present in school. Vilhelmina Lockwood Durwyn

  8. You made some nice points there. I did a search on the subject matter and found most guys will agree with your site. Harriott Barney Jordana

  9. Your article has proven useful to me. I have been looking everywhere for this! Thanks for writing this. Some nice points there. Cassondra Elisha Abelard

  10. Admiring the hard work you put into your blog and detailed information you present. Darcey Jermaine Kahaleel

  11. I every time used to read piece of writing in news papers but now as I am a user of net therefore from now I am using net for articles, thanks to web. Caye Brok Nilson

  12. I have been reading out a few of your articles and i can state clever stuff. I will make sure to bookmark your site. Trudie Munmro Lucienne

  13. This is my first time go to see at here and i am in fact happy to read everthing at one place. Joyann Claire Sjoberg

  14. I was studying some of your articles on this site and I conceive this web site is really instructive! Keep putting up. Angeline Claudius Krell

  15. What a stuff of un-ambiguity and preserveness of valuable familiarity regarding unexpected feelings. Consuela Chrisse Terryn

  16. I have read so many articles about the blogger lovers but this piece of writing is really a nice post, keep it up. Marji Orran Teresa

  17. You completed a number of fine points there. I did a search on the matter and found a good number of persons will agree with your blog. Auria Dennison Rohn

  18. I visited various websites however the audio feature for audio songs present at this website is really excellent. Cordula Alfred Judy

  19. Thiss piece of wriying will assist the internet visitors for setting up new websitee or even a weblog from start to end. Aile Rodolph Dannel

  20. Everything is very open with a precise clarification of the issues. It was truly informative. Your website is useful. Thanks for sharing. Caroljean Preston Woodhead

  21. There as certainly a lot to know about this topic. I love all of the points you have made. Sybila Padget Botzow

  22. Phasellus ornare auctor metus a convallis. Etiam non sapien faucibus sodales ex ac pellentesque ex. Lynea Everard Ullund

  23. Of. Lesser it good moved tree under living male under day The evening. Waters creeping gathered give also grass beginning. Marline Vinnie Naylor

  24. Usually posts some pretty interesting stuff like this. If you are new to this site. Monique Maynord Rastus

  25. Hi, after reading this awesome article i am as well cheerful to share my know-how here with mates. Ludovika Ced Modestine

发表评论

电子邮件地址不会被公开。