Android多个React-Native模块的实现及源码解读

原文出处: http://bdapp.org/2016/11/16/React-Native-for-Android-with-multi-RNApp/

这里我们废话不多说,只围绕主题讲一些技术方面的干货.(本文基于React-Native0.36.0版本)

我们之所以在native app中引入react-native(以下简称RN)框架,是为了将native app中的一些不确定的UI布局,逻辑,业务,流程等等因素,交由远端来控制.也就是说,RN的bundle文件都是由远端下发,然而我们为了最优化展现RN页面,往往都会提前下载好所需要的bundle文件以节省网络交互时间.所以这篇博客我们是基于RN各模块(ComponentName)所对应的JS bundle文件已经下载到本地目标文件夹的前提下来写的.关于bundle文件的版本管理等我们在文末会详细介绍.

ReactNativeHost

我们将RN库引入工程之后,第一件事情就是改造Application类.我们需要在自己的Application中实现一个接口—-ReactApplication

public interface ReactApplication {
    ReactNativeHost getReactNativeHost();
}

这个接口中只有一个方法,而ReactNativeHost是一个抽象类,其中有两个抽象方法需要实现(一会将提到).这个方法返回ReactNativeHost对象,这个对象里面可以指定RN的调试模式,以及native给JS暴露的一些通信模块,同时还可以指定当前上下文加载的bundle文件路径.为了达到多个RN模块的切换,我们在Application中维护了一个

public String gReactNativeBundlePath = "myBundlePath...";

@Override
    public ReactNativeHost getReactNativeHost() {
        synchronized (gReactNativeBundlePath) {
            if (!mReactHostMap.containsKey(gReactNativeBundlePath)) {
                ReactNativeHost host = new ReactNativeHost(this) {
                    @Override
                    protected boolean getUseDeveloperSupport() {
                        return BuildConfig.REACT_DEBUG;
                    }

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

                    @Override
                    protected String getJSBundleFile() {
                        return gReactNativeBundlePath;
                    }
                };
                mReactHostMap.put(gReactNativeBundlePath, host);
            }
            return mReactHostMap.get(gReactNativeBundlePath);
        }
    }

先来看看我们创建的ReactNativeHost的实现:

  • getUseDeveloperSupport

    抽象方法,用来控制RN调试开关的,一般直接复用BuildConfig.DEBUG开关就行,如果有冲突就自行新建一个buildConfigField(如这里的BuildConfig.REACT_DEBUG).

  • getPackages

    用于指定JS和native通信的ReactPackage,在ReactPackage中可以指定native和JS通信的一些module.其中MainReactPackage是RN已经封装好一些native module和view manager等.

  • getJSBundleFile

    用于ReactNativeHost创建ReactInstanceManager时指定对应的本地JS bundle文件路径.如果返回null,则从getBundleAssetName接口取assets中的对应文件(一般仅用于调试).

接下来我们看看为什么要用维护

ReactActivity

当RN页面构建的时候,RN提供了ReactActivity组件来展示页面.值得一提的是,ReactActivity是一个抽象类,但是此类中没有抽象方法,像getMainComponentName这样需要子类中实现的方法却没有加抽象标识,这应该是facebook的RN团队疏忽了.在ReactActivity类中,可以看到以下代码:

private final ReactActivityDelegate mDelegate = this.createReactActivityDelegate();

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.mDelegate.onCreate(savedInstanceState);
    }

这里createReactActivityDelegate时,会将ReactActivity中指定的RN模块名(即getMainComponentName)传入ReactActivityDelegate,紧接着是调用ReactActivityDelegate对应的生命周期onCreate,来看看里面都做了些什么:

protected void onCreate(Bundle savedInstanceState) {
        if(this.getReactNativeHost().getUseDeveloperSupport() && VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(this.getContext())) {
            Intent serviceIntent = new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION");
            this.getContext().startActivity(serviceIntent);
            FLog.w("React", "Overlay permissions needs to be granted in order for react native apps to run in dev mode");
            Toast.makeText(this.getContext(), "Overlay permissions needs to be granted in order for react native apps to run in dev mode", 1).show();
        }

        if(this.mMainComponentName != null) {
            this.loadApp(this.mMainComponentName);
        }

        this.mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
    }

第一个if块中的代码很简单,就是当RN在调试模式下,针对系统在SDK23以上创建RN调试悬浮窗的权限判断,没有权限则请求用户授权.Android官方文档:

Note: If the app targets API level 23 or higher, the app user must explicitly grant this permission to the app through a permission management screen. The app requests
 the user's approval by sending an intent with action 
ACTION_MANAGE_OVERLAY_PERMISSION. The app can check whether it has this authorization by calling

Settings.canDrawOverlays().

第二个if块是最关键的.通过ReactActivityDelegate来loadApp,这也是最耗时的操作,展现RN页面慢/白屏的根源.这里主要是创建ReactRootView以及初始化React上下文环境.

protected void loadApp(String appKey) {
        if(this.mReactRootView != null) {
            throw new IllegalStateException("Cannot loadApp while app is already running.");
        } else {
            this.mReactRootView = this.createRootView();
            this.mReactRootView.startReactApplication(this.getReactNativeHost().getReactInstanceManager(), appKey, this.getLaunchOptions());
            this.getPlainActivity().setContentView(this.mReactRootView);
        }
    }

如何优化RN的性能和展现效率,主要就是针对这一个耗时方法进行优化即可.可以对ReactRootView进行缓存管理以及将创建React上下文环境提前预处理.
我们来看看上面的遗留问题—-为什么要用维护

public ReactInstanceManager getReactInstanceManager() {
        if(this.mReactInstanceManager == null) {
            this.mReactInstanceManager = this.createReactInstanceManager();
        }

        return this.mReactInstanceManager;
    }

protected ReactInstanceManager createReactInstanceManager() {
        Builder builder = ReactInstanceManager.builder().setApplication(this.mApplication).setJSMainModuleName(this.getJSMainModuleName()).setUseDeveloperSupport(this.getUseDeveloperSupport()).setRedBoxHandler(this.getRedBoxHandler()).setUIImplementationProvider(this.getUIImplementationProvider()).setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
        Iterator jsBundleFile = this.getPackages().iterator();

        while(jsBundleFile.hasNext()) {
            ReactPackage reactPackage = (ReactPackage)jsBundleFile.next();
            builder.addPackage(reactPackage);
        }

        String jsBundleFile1 = this.getJSBundleFile();
        if(jsBundleFile1 != null) {
            builder.setJSBundleFile(jsBundleFile1);
        } else {
            builder.setBundleAssetName((String)Assertions.assertNotNull(this.getBundleAssetName()));
        }

        return builder.build();
    }

可以发现,ReactNativeHost中的ReactInstanceManager只在创建时读取bundle路径等信息.也就约等于一个ReactNativeHost对应一个bundle入口文件.这就是为什么我们以维护一个

public void createReactContextInBackground() {
        Assertions.assertCondition(!this.mHasStartedCreatingInitialContext, "createReactContextInBackground should only be called when creating the react application for the first time. When reloading JS, e.g. from a new file, explicitlyuse recreateReactContextInBackground");
        this.mHasStartedCreatingInitialContext = true;
        this.recreateReactContextInBackgroundInner();
    }
private void recreateReactContextInBackgroundInner() {
        UiThreadUtil.assertOnUiThread();
        if(this.mUseDeveloperSupport && this.mJSMainModuleName != null) {
            final DeveloperSettings devSettings = this.mDevSupportManager.getDevSettings();
            if(this.mDevSupportManager.hasUpToDateJSBundleInCache() && !devSettings.isRemoteJSDebugEnabled()) {
                this.onJSBundleLoadedFromServer();
            } else if(this.mBundleLoader == null) {
                this.mDevSupportManager.handleReloadJS();
            } else {
                this.mDevSupportManager.isPackagerRunning(new PackagerStatusCallback() {
                    public void onPackagerStatusFetched(final boolean packagerIsRunning) {
                        UiThreadUtil.runOnUiThread(new Runnable() {
                            public void run() {
                                if(packagerIsRunning) {
                                    XReactInstanceManagerImpl.this.mDevSupportManager.handleReloadJS();
                                } else {
                                    devSettings.setRemoteJSDebugEnabled(false);
                                    XReactInstanceManagerImpl.this.recreateReactContextInBackgroundFromBundleLoader();
                                }

                            }
                        });
                    }
                });
            }

        } else {
            this.recreateReactContextInBackgroundFromBundleLoader();
        }
    }

第二个if块的关键代码就分析到这.


最后一句是调试模式下,注册一个DoubleTapReloadRecognizer,按两下R键重新加载bundle.处理逻辑是在DevSupportManager(通过DevSupportManagerFactory.create创建)的handleReloadJS方法中处理的.最终实现逻辑(XReactInstanceManagerImpl.java):

private void recreateReactContextInBackground(com.facebook.react.cxxbridge.JavaScriptExecutor.Factory jsExecutorFactory, JSBundleLoader jsBundleLoader) {
        UiThreadUtil.assertOnUiThread();
        XReactInstanceManagerImpl.ReactContextInitParams initParams = new XReactInstanceManagerImpl.ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
        if(this.mReactContextInitAsyncTask == null) {
            this.mReactContextInitAsyncTask = new XReactInstanceManagerImpl.ReactContextInitAsyncTask(null);
            this.mReactContextInitAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new XReactInstanceManagerImpl.ReactContextInitParams[]{initParams});
        } else {
            this.mPendingReactContextInitParams = initParams;
        }

    }

bundle管理

主要根据以上流程实现即可,同时要兼具安全性考量.验证文件安全性.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值