原文出处: 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 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 callingSettings.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管理

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

更多相关文章

  1. [Android]Android开发艺术探索第13章笔记
  2. android之动态新建txt文件与读取
  3. android学习记录2(日志、上下文、android下数据存储、xml基础)
  4. android mk file 描述
  5. Android(安卓)Jetpack(二)ViewModel 组件原理剖析
  6. android scrollview 滑动到顶端或者指定位置
  7. Android(安卓)完全退出当前应用程序
  8. android studio导出apk步骤
  9. Android(安卓)4高级编程(第3版)

随机推荐

  1. Android中可自由移动悬浮窗口的Demo
  2. Android(安卓)机型适配之百分比适配 Cons
  3. android 开发 简单的页面布局
  4. Android下按扭的使用方法
  5. Android获取statusBar和navigationBar高
  6. Android官方教程翻译(2)——运行第一个程序
  7. Android 关于显示键盘,布局错乱网上顶的问
  8. android登录简单窗口
  9. Android UISegmentedControl Fragment切
  10. Android 媒体:网络视频播放器的基本设计