前言 现有原生Android应用集成RN(React Native Android)开发环境 RN环境 Android原生和JS交互Android RN
两篇博客中介绍了RN集成原生app和原生和js互动的基本原理。其中第一篇现有原生Android应用集成RN(React Native Android)开发环境 里面的Activity的例子是从官网上直接copy过来的,能运行起来但是一知半解。这篇blog记录一下RN界面是怎么加载到我们原生Activity上的。
正文 在这个例子中现有原生Android应用集成RN(React Native Android)开发环境 ,范例Activity使用的是自己原生的纯纯的Activity来加载RN界面。其实RN官方给我们提供了一个com.facebook.react.ReactActivity来让我们继承使用,它继承自24k纯Activity,ReactActivity里面包含了怎么加载RN界面的方法。分析一下这个类,就能知道原生Activity怎么加载RN界面的了。
老规矩,成员要先上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 package com.facebook.react;import javax.annotation.Nullable;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.view.KeyEvent;import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;import com.facebook.react.modules.core.PermissionAwareActivity;import com.facebook.react.modules.core.PermissionListener;public abstract class ReactActivity extends Activity implements DefaultHardwareBackBtnHandler , PermissionAwareActivity { private final ReactActivityDelegate mDelegate; protected ReactActivity () { mDelegate = createReactActivityDelegate(); } protected @Nullable String getMainComponentName () { return null ; } protected ReactActivityDelegate createReactActivityDelegate () { return new ReactActivityDelegate(this , getMainComponentName()); } @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); mDelegate.onCreate(savedInstanceState); } @Override protected void onPause () { super .onPause(); mDelegate.onPause(); } @Override protected void onResume () { super .onResume(); mDelegate.onResume(); } @Override protected void onDestroy () { super .onDestroy(); mDelegate.onDestroy(); } @Override public void onActivityResult (int requestCode, int resultCode, Intent data) { mDelegate.onActivityResult(requestCode, resultCode, data); } @Override public boolean onKeyUp (int keyCode, KeyEvent event) { return mDelegate.onKeyUp(keyCode, event) || super .onKeyUp(keyCode, event); } @Override public void onBackPressed () { if (!mDelegate.onBackPressed()) { super .onBackPressed(); } } @Override public void invokeDefaultOnBackPressed () { super .onBackPressed(); } @Override public void onNewIntent (Intent intent) { if (!mDelegate.onNewIntent(intent)) { super .onNewIntent(intent); } } @Override public void requestPermissions ( String[] permissions, int requestCode, PermissionListener listener) { mDelegate.requestPermissions(permissions, requestCode, listener); } @Override public void onRequestPermissionsResult ( int requestCode, String[] permissions, int [] grantResults) { mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); } protected final ReactNativeHost getReactNativeHost () { return mDelegate.getReactNativeHost(); } protected final ReactInstanceManager getReactInstanceManager () { return mDelegate.getReactInstanceManager(); } protected final void loadApp (String appKey) { mDelegate.loadApp(appKey); } }
非常干净的一个类,它做了什么?
构造函数里生成一个com.facebook.react.ReactActivityDelegate对象,并把自己(this)和MainComponentName传给这个委托对象
在Activity的生命周期回调里,都调用了委托对象的对应方法。换句话说就是,把Activity的生命周期都委托的这个对象处理。
对外公开了几个mDelegate的方法来获取ReactInstanceManager,ReactNativeHost。提供了一个loadApp方法,同样,也是调用了mDelegate的loadApp方法
实现了两个接口DefaultHardwareBackBtnHandler,PermissionAwareActivity。
DefaultHardwareBackBtnHandler 用提供默认的回退键处理方式
PermissionAwareActivity 提供了回调权限相关的东西,android从api23(6.0),权限就改了及用机申请的模式
典型的委托模式,所有东西都交给了ReactActivityDelegate来处理。不但简化的Activity的逻辑,而且可以把这个委托对象复用给Fragment。几乎所有逻辑都在ReactActivityDelegate里面了,下面来看下ReactActivityDelegate。
1 2 3 4 5 public ReactActivityDelegate (Activity activity, @Nullable String mainComponentName) { mActivity = activity; mMainComponentName = mainComponentName; mFragmentActivity = null ; }
ReactActivityDelegate持有Activity的引用和一个字符串类型的mainComponentName 先看oncreate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 protected void onCreate (Bundle savedInstanceState) { boolean needsOverlayPermission = false ; if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(getContext())) { needsOverlayPermission = true ; Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getContext().getPackageName())); FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE); Toast.makeText(getContext(), REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); ((Activity) getContext()).startActivityForResult(serviceIntent, REQUEST_OVERLAY_PERMISSION_CODE); } } if (mMainComponentName != null && !needsOverlayPermission) { loadApp(mMainComponentName); } mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer(); }
前面的代码都是一些调试用的界面加载,关键代码是loadApp(mMainComponentName);
1 2 3 4 5 6 7 8 9 10 11 protected void loadApp (String appKey) { if (mReactRootView != null ) { throw new IllegalStateException("Cannot loadApp while app is already running." ); } mReactRootView = createRootView(); mReactRootView.startReactApplication( getReactNativeHost().getReactInstanceManager(), appKey, getLaunchOptions()); getPlainActivity().setContentView(mReactRootView); }
先是创建了一个ReactRootView对象,然后调用ReactRootView对象的startReactApplication方法。再然后,使用持有的activity直接setContentView,搞定!这样就把RN界面加载到activity上了。核心点还是这个ReactRootView,它其实是一个FrameLayout,所以能直接使用setContentView设置到布局上,其实在xml写上它。findbyid出来,然后调用startReactApplication初始化一下,其实是一样的。所以!关键还在startReactApplication上。 看下它的三个参数
1 2 3 4 public void startReactApplication ( ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle launchOptions)
ReactInstanceManager对象由自己的建造者模式生成,可以设置一些属性。 moduleName就是moduleName,不知道怎么用言语表达。 看下这个方法都干了啥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public void startReactApplication ( ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle launchOptions) { UiThreadUtil.assertOnUiThread(); if (mReactInstanceManager != null && mReactInstanceManager.hasStartedCreatingInitialContext()){ mReactInstanceManager.recreateReactContextInBackground(); return ; } mReactInstanceManager = reactInstanceManager; mJSModuleName = moduleName; mLaunchOptions = launchOptions; if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { mReactInstanceManager.createReactContextInBackground(); } if (mWasMeasured) { attachToReactInstanceManager(); } }
这个方法先用UiThreadUtil.assertOnUiThread()断言的形式判断了一下是不是在UI线程,如果不是就抛异常中断执行,在RN整个代码中,有大量的断言用法,看到直接忽略就可以了,就是一些数据校验和判断。初始化过程中也都保持在UI线程中执行。方法中先判断持有的mReactInstanceManager为不为空,如果不为空,调用recreateReactContextInBackground,为空的话,再去看看是否已经初始化过了,没有的话调用createReactContextInBackground要去初始化。方法的最后先看了下这个view有没有经过Measured,注释说的很清楚,只有经过Measured的view才能调用attachToReactInstanceManager把自己关联到reactInstanceManager上。ReactRootView的onMeasure方法中其实也有调用attachToReactInstanceManager的代码。 上面说了,如果初始化过了会调用recreateReactContextInBackground,没有初始化调用createReactContextInBackground来初始化。其实这两个方法最终都会调到recreateReactContextInBackgroundInner,只不过里面的断言条件不一样。这两个方法都是manager的,自此就进入了mReactInstanceManager的代码里面了,recreateReactContextInBackgroundInner里面前半部分都是调试模式下的代码,最后又调到了recreateReactContextInBackgroundFromBundleLoader。 recreateReactContextInBackgroundFromBundleLoader里面只有一句代码,调用了recreateReactContextInBackground方法,它有两个参数。
1 2 3 private void recreateReactContextInBackground ( JavaScriptExecutor.Factory jsExecutorFactory, JSBundleLoader jsBundleLoader)
第一个参数是为了互传数据,互相调用用的,里面是native代码(该native指的是c/c++库),为了加载js生成的Bundle文件用的。首先我们要注意一点,RN是用JS开发的,但是JS相当于RN的源代码。我们原生应用加载的是Bundle文件,Bundle文件相当于RN的源代码(JS代码)编译后的可执行加载的代码。还要注意版本差异,不同版本编译出来的Bundle文件,在android这边使用不同版本的RN库加载Bundle文件会有差异。最好编译版本和加载版本一致。下面说下recreateReactContextInBackground的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void recreateReactContextInBackground ( JavaScriptExecutor.Factory jsExecutorFactory, JSBundleLoader jsBundleLoader) { UiThreadUtil.assertOnUiThread(); ReactContextInitParams initParams = new ReactContextInitParams(jsExecutorFactory, jsBundleLoader); if (mReactContextInitAsyncTask == null ) { mReactContextInitAsyncTask = new ReactContextInitAsyncTask(); mReactContextInitAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, initParams); } else { mPendingReactContextInitParams = initParams; } }
看代码它对参数进行了一层封装,就是ReactContextInitParams对象。然后调用过了ReactContextInitAsyncTask的执行方法。ReactContextInitAsyncTask是一个AsyncTask。至此,UI线程中的任务就结束了,后面交给了子线程。去看下ReactContextInitAsyncTask中是怎么执行的。ReactContextInitAsyncTask中的onPreExecute是一些清理工作,直接看doInBackground。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override protected Result<ReactApplicationContext> doInBackground (ReactContextInitParams... params) { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); Assertions.assertCondition(params != null && params.length > 0 && params[0 ] != null ); try { JavaScriptExecutor jsExecutor = params[0 ].getJsExecutorFactory().create(); return Result.of(createReactContext(jsExecutor, params[0 ].getJsBundleLoader())); } catch (Exception e) { return Result.of(e); } }
小知识:子线程运行的第一句代码设置为Process.setThreadPriority(Process.xxx);可以调整线程运行级别,xxx为内置级别。请尽量把线程设置的优先级降低,避免抢占UI线程占用CPU资源而造成卡顿。 看下doInBackground都做了什么。从参数0获取之前创建的JsExecutorFactory,然后create一个JavaScriptExecutor,调用createReactContext(jsExecutor, params[0].getJsBundleLoader())返回。这里使用了Result.of这样的返回方式。Result.of是一个封装类,封装了结果和异常信息,主要处理逻辑还在createReactContext里面。 下面的代码用注释的形式一一解答
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 private ReactApplicationContext createReactContext ( JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) { FLog.i(ReactConstants.TAG, "Creating react context." ); ReactMarker.logMarker(CREATE_REACT_CONTEXT_START); final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext); NativeModuleRegistryBuilder nativeModuleRegistryBuilder = new NativeModuleRegistryBuilder( reactContext, mLazyNativeModulesEnabled); JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder(); if (mUseDeveloperSupport) { reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager); } ReactMarker.logMarker(PROCESS_PACKAGES_START); Systrace.beginSection( TRACE_TAG_REACT_JAVA_BRIDGE, "createAndProcessCoreModulesPackage" ); try { CoreModulesPackage coreModulesPackage = new CoreModulesPackage( this , mBackBtnHandler, mUIImplementationProvider, mLazyViewManagersEnabled); processPackage(coreModulesPackage, nativeModuleRegistryBuilder, jsModulesBuilder); } finally { Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); } for (ReactPackage reactPackage : mPackages) { Systrace.beginSection( TRACE_TAG_REACT_JAVA_BRIDGE, "createAndProcessCustomReactPackage" ); try { processPackage(reactPackage, nativeModuleRegistryBuilder, jsModulesBuilder); } finally { Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); } } ReactMarker.logMarker(PROCESS_PACKAGES_END); ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_START); Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry" ); NativeModuleRegistry nativeModuleRegistry; try { nativeModuleRegistry = nativeModuleRegistryBuilder.build(); } finally { Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END); } NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null ? mNativeModuleCallExceptionHandler : mDevSupportManager; CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault()) .setJSExecutor(jsExecutor) .setRegistry(nativeModuleRegistry) .setJSModuleRegistry(jsModulesBuilder.build()) .setJSBundleLoader(jsBundleLoader) .setNativeModuleCallExceptionHandler(exceptionHandler); ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START); Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance" ); final CatalystInstance catalystInstance; try { catalystInstance = catalystInstanceBuilder.build(); } finally { Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END); } if (mBridgeIdleDebugListener != null ) { catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener); } reactContext.initializeWithInstance(catalystInstance); catalystInstance.runJSBundle(); return reactContext; }
核心方法调用了catalystInstance的runJSBundle方法。它内部又调用了之前准备好的mJSBundleLoader的loadScript方法。回到最初的起点,我们创建ReactInstanceManager对象的时候,会然让你设置一个setBundleAssetName或者setJSBundleFile,它在build出ReactInstanceManager对象的时候,会通过JSBundleLoader的静态方法createFileLoader或者createAssetLoader创建出一个JSBundleLoader,参数就是我们的bundle文件的路径(这里路径描述不准确,应该是asset文件夹下的文件名或者文件的全路径)。上面就是用这个JSBunldeLoader对象调用的runJSBundle。来看下runJSBundle的实现。
1 2 3 private native void jniSetSourceURL (String sourceURL) ; private native void jniLoadScriptFromAssets (AssetManager assetManager, String assetURL) ; private native void jniLoadScriptFromFile (String fileName, String sourceURL) ;
当当当!jni的,跟不下去了。结束~打完收工。
对了,还有最后的,RN的View是怎么挂载到ReactRootView上的。Debug了一下,FaceBook使用了Choreographer机制来传递addview的消息。从Choreographer.doFrame回调到了ChoreographerCompat的内部类FrameCallback中。具体FrameCallback是什么时候注册到Choreographer上去的,还没去看。FrameCallback的实现类为ReactChoreographerDispatcher,这个大哥里面有个callback队列,收到一个doFrame消息,就会执行一遍队列的全部回调,回调的实现在UIViewOperationQueue->DispatchUIFrameCallback中。DispatchUIFrameCallback中会调用flushPendingBatches方法来顺序执行UIViewOperationQueue持有的mDispatchUIRunnables队列的所有runnale,每一个runnable都是一个UIViewOperationQueue对象,一个UIViewOperationQueue对象代表一个事件,持有一些视图啊之类的东西,最后调用mNativeViewHierarchyManager.manageChildren把持有的视图添加到了之前我们准备好的ReactRootView上。呼呼~~具体UIViewOperationQueue对象何时生成,又何时添加到队列中的,这个之前初始化的时候就已经和c/c++代码绑定上了,应该是c/c++那边添进来的。具体时机不详。
总结
Activity需要加载的RN的View是ReactRootView,所以,把ReactRootView当成普通的View玩弄即可
ReactRootView需要调用startReactApplication激活一下。
基本上所有关于RN配置都在ReactInstanceManager里面,使用ReactInstanceManager的build建造者模式可以构造想要的ReactInstanceManager,一个ReactInstanceManager对应一个bundler文件。在build的时候设置。一个ReactRootView对应一个ReactInstanceManager
知道这些基本就可以开撸了,剩下无非就是下载bundler文件和一些封装调用
Facebook的编码风格看起来还是很统一的,大量的build,对象包裹,委托和断言充斥的整个库,最最重要的是他们的过程比较清晰,如果滥用设计模式的话,很有可能比较简单的代码弄得无比繁琐和绕圈子。不能为设计而设计,为了分层而分层。TODO用的也很合理,但是不知道后面的版本到底DO不DO
完 最后想说如何看待百度要求内部全面停止使用 React / React Native? 跟我们有什么关系呢?真上升要去告FaceBook的地步,还是老老实实的用别的技术或者原生吧。小厂还是尽情的用吧。而且,这是前段的事情,俺们一个不沾惹前段的人还是不叽歪了。