React Native BackHandler exitApp 源码分析
概述
昨天技术交流群里有个朋友提出一个问题,在 Android 中嵌入了 React Naitve,并且想从RN层执行代码,回到上一个原生Activity。说起来比较模糊,假设他的界面执行流程如下:
ActivityA → ActivityB → RNActivityA → JS端执行代码 → 返回 ActivityB
以上面简单的跳转为例,Activity A 、B 都为 Android 原生视图界面,RNActivityA 为 React Native 视图加载界面。此时要从 RNActivityA 回到 ActivityB,并且是在 React Naitve 的 js 代码中执行,如何实现呢?
在 Android + RN 混合开发模式下,这种场景是比较常见的。早期版本中,官方在RN层为开发者提供了 BackAndroid,方便大家在 React Naitve 中使用 exitApp()实现退出 App 的功能。在后期版本中,BackAndroid 被官方废弃,统一使用 BackHandler 代替。官方文档对于 BackHandler 的描述如下:
Detect hardware button presses for back navigation. iOS: Not applicable.
检测硬件按钮按下以进行后退导航。不支持 iOS 平台设备
在解决问题之前,先来想一个问题:在 React Native 中为什么使用 BackHandler 可以实现退出 App 的功能?
带着这个疑问,进入今天的主题:BackHandler exitApp 源码分析。
BackHandler 源码分析
首先在 React Native 中自定义一个 Component 界面,添加 Text 组件,并实现单击调用 BackHandler.exitApp
export default class RNComponent extends Component {
exitApp() {
BackHandler.exitApp();
}
render() {
return (
<View style={ styles.container }>
<Text>RN的界面</Text>
<Text onPress={() => this.exitApp()}>
退出App
</Text>
</View>
);
}
}
BackHandler.android.js
跟进 BackHandler 的代码,对应的实现在 node_modules/react-native/Libraries/Utilities/BackHandler.android.js:
const DeviceEventManager = require('NativeModules').DeviceEventManager;
const BackHandler = {
exitApp: function() {
DeviceEventManager.invokeDefaultBackPressHandler();
},
addEventListener: function(eventName: BackPressEventName, handler: Function): {remove: () => void} {
_backPressSubscriptions.add(handler);
return {
remove: () => BackHandler.removeEventListener(eventName, handler),
};
},
removeEventListener: function(eventName: BackPressEventName, handler: Function): void {
_backPressSubscriptions.delete(handler);
},
};
BackHandler 作为一个常量对象,其中包含了 exitApp、addEventListener、removeEventListener 函数。在 exitApp 函数中,调用了 DeviceEventManager 的 invokeDefaultBackPressHandler 函数。DeviceEventManager 是系统定义的处理硬件反压等设备硬件事件的本机模块的实现类,对应于 node_modules/react-native/ReactAndroid/src/main/java/com.facebook.react.modules.core 目录下的 DeviceEventManagerModule.java。
DeviceEventManagerModule
package com.facebook.react.modules.core;
/**
* 处理硬件反压等设备硬件事件的本机模块.
*/
@ReactModule(name = "DeviceEventManager")
public class DeviceEventManagerModule extends ReactContextBaseJavaModule {
public interface RCTDeviceEventEmitter extends JavaScriptModule {
void emit(String eventName, @Nullable Object data);
}
private final Runnable mInvokeDefaultBackPressRunnable;
public DeviceEventManagerModule(ReactApplicationContext reactContext,
final DefaultHardwareBackBtnHandler backBtnHandler) {
super(reactContext);
// 初始化 mInvokeDefaultBackPressRunnable
mInvokeDefaultBackPressRunnable = new Runnable() {
@Override
public void run() {
UiThreadUtil.assertOnUiThread();
backBtnHandler.invokeDefaultOnBackPressed();
}
};
}
// .... 代码省略
/**
* 调用默认的后退处理程序, 如果JS不想处理背压本身,则应调用此方法.
* 在UI线程执行 mInvokeDefaultBackPressRunnable
*/
@ReactMethod
public void invokeDefaultBackPressHandler() {
getReactApplicationContext().runOnUiQueueThread(mInvokeDefaultBackPressRunnable);
}
@Override
public String getName() {
return "DeviceEventManager";
}
}
了解 Android 与 React Native 通信交互的朋友(可以看我之前写的文章:React Native与Android通信交互)看到 DeviceEventManagerModule 不会感到陌生。getName 方法返回原生 Module 模块的名称。在该模块下定义了供 JS 端调用的方法(ReactMethod注释)。mInvokeDefaultBackPressRunnable 为 Runnable 对象,在构造函数中,初始化了 mInvokeDefaultBackPressRunnable ,并在 run 方法中执行 backBtnHandler.invokeDefaultOnBackPressed(); 继续跟踪到 invokeDefaultBackPressHandler 函数,可以看到,在函数中通过获取 Application 实例,将 mInvokeDefaultBackPressRunnable 放在 UI 主线程队列中执行。从构造函数中,可以看出 backBtnHandler 是 DefaultHardwareBackBtnHandler 的实例。接下来重点来看 backBtnHandler 中做了什么。
DefaultHardwareBackBtnHandler
DefaultHardwareBackBtnHandler 的实现代码同样在node_modules/react-native/ReactAndroid/src/main/java/com.facebook.react.modules.core目录下:
package com.facebook.react.modules.core;
/**
* 用于委派硬件后退按钮事件的接口。 提供默认行为,因为它会在JS方面被触发
* 不处理背压事件。
*/
public interface DefaultHardwareBackBtnHandler {
/**
* 默认情况下,所有onBackPress()调用都不应该执行默认的反向处理程序,而应该将其传播到JS实例。
* 如果JS不想处理反压本身,它应该回调为native来调用这个应该执行默认处理程序的函数
*/
void invokeDefaultOnBackPressed();
}
DefaultHardwareBackBtnHandler 被定义为 一个接口,其中声明了 invokeDefaultOnBackPressed 函数方法。具体的实现行为交给子类来实现。现在我们就需要跟踪代码,找到 DeviceEventManagerModule 是在哪里被初始化的。还记得我们在Android中实现完JS的桥接Module模块后,需要将其添加到Package,并在Application中注册。所以,我们需要找到Package,就能找到 DeviceEventManagerModule 初始化。
CoreModulesPackage
React Native 系统的基本 Module 的 Package 为:CoreModulesPackage,路径为:node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java:
package com.facebook.react;
/**
* 这是支持React Native的基本模块。 调试模块现在位于DebugCorePackage中。
*/
@ReactModuleList(
nativeModules = {
AndroidInfoModule.class,
DeviceEventManagerModule.class,
DeviceInfoModule.class,
ExceptionsManagerModule.class,
HeadlessJsTaskSupportModule.class,
SourceCodeModule.class,
Timing.class,
UIManagerModule.class,
}
)
/* package */ class CoreModulesPackage extends LazyReactPackage implements ReactPackageLogger {
private final ReactInstanceManager mReactInstanceManager;
private final DefaultHardwareBackBtnHandler mHardwareBackBtnHandler;
CoreModulesPackage(
ReactInstanceManager reactInstanceManager,
DefaultHardwareBackBtnHandler hardwareBackBtnHandler,
boolean lazyViewManagersEnabled,
int minTimeLeftInFrameForNonBatchedOperationMs) {
mReactInstanceManager = reactInstanceManager;
mHardwareBackBtnHandler = hardwareBackBtnHandler;
mLazyViewManagersEnabled = lazyViewManagersEnabled;
mMinTimeLeftInFrameForNonBatchedOperationMs = minTimeLeftInFrameForNonBatchedOperationMs;
}
// .... 代码省略
@Override
public List<ModuleSpec> getNativeModules(final ReactApplicationContext reactContext) {
return Arrays.asList(
ModuleSpec.nativeModuleSpec(
AndroidInfoModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new AndroidInfoModule(reactContext);
}
}),
ModuleSpec.nativeModuleSpec(
DeviceEventManagerModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new DeviceEventManagerModule(reactContext, mHardwareBackBtnHandler);
}
}),
ModuleSpec.nativeModuleSpec(
ExceptionsManagerModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager());
}
}),
ModuleSpec.nativeModuleSpec(
HeadlessJsTaskSupportModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new HeadlessJsTaskSupportModule(reactContext);
}
}),
ModuleSpec.nativeModuleSpec(
SourceCodeModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new SourceCodeModule(reactContext);
}
}),
ModuleSpec.nativeModuleSpec(
Timing.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new Timing(reactContext, mReactInstanceManager.getDevSupportManager());
}
}),
ModuleSpec.nativeModuleSpec(
UIManagerModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return createUIManager(reactContext);
}
}),
ModuleSpec.nativeModuleSpec(
DeviceInfoModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new DeviceInfoModule(reactContext);
}
}));
}
// .... 代码省略
}
CoreModulesPackage 类的 getNativeModules 方法中注册了系统默认的基本 Module。其中 DeviceEventManagerModule 的初始化代码中,第二个参数传入了 mHardwareBackBtnHandler,该参数又是在 CoreModulesPackage 的构造函数中被初始化,所以继续跟踪 CoreModulesPackage 的初始化代码。在 React Native JSBundle 拆分解决方案(1): 应用启动、视图加载原理解析 中我们知道,ReactInstanceManager 作为 Android 与 JS 端的通信管理类,以及加载 React Native 视图都起到了非常重要的协调作用。并且添加了所有 自定义 Packages 以及系统的 Package。 继续跟踪 ReactInstanceManager 类中 CoreModulesPackage 的实现。
ReactInstanceManager
ReactInstanceManager 类代码较长,我们只贴核心部分:
/**
* 注册 Module
*/
synchronized (mPackages) {
PrinterHolder.getPrinter()
.logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: Use Split Packages");
mPackages.add(
new CoreModulesPackage(
this,
new DefaultHardwareBackBtnHandler() {
@Override
public void invokeDefaultOnBackPressed() {
ReactInstanceManager.this.invokeDefaultOnBackPressed();
}
},
lazyViewManagersEnabled,
minTimeLeftInFrameForNonBatchedOperationMs));
if (mUseDeveloperSupport) {
mPackages.add(new DebugCorePackage());
}
mPackages.addAll(packages);
}
/**
* 处理键盘返回事件
*/
private void invokeDefaultOnBackPressed() {
UiThreadUtil.assertOnUiThread();
if (mDefaultBackButtonImpl != null) {
mDefaultBackButtonImpl.invokeDefaultOnBackPressed();
}
}
/**
* Activity 获取焦点
*/
@ThreadConfined(UI)
public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
UiThreadUtil.assertOnUiThread();
mDefaultBackButtonImpl = defaultBackButtonImpl;
onHostResume(activity);
}
首先在 mPackages 中添加基本的Module,在初始化 CoreModulesPackage 的代码中,我们发现,在第二个参数中直接创建了DefaultHardwareBackBtnHandler 的实例,并在 invokeDefaultOnBackPressed() 方法中调用了 ReactInstanceManager 的 invokeDefaultOnBackPressed() 方法, 在 invokeDefaultOnBackPressed() 方法中 调用了 mDefaultBackButtonImpl 的 invokeDefaultOnBackPressed()。而 mDefaultBackButtonImpl 的具体实现实例是在 onHostResume 方法中传入。onHostResume 是在 Activity 获取焦点时执行的代码,而 ReactActivity 的实现依赖了 ReactActivityDelegate,所以我们来看 ReactActivityDelegate 中的 onResume 代码
ReactActivityDelegate
protected void onResume() {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onHostResume(
getPlainActivity(),
(DefaultHardwareBackBtnHandler) getPlainActivity());
}
if (mPermissionsCallback != null) {
mPermissionsCallback.invoke();
mPermissionsCallback = null;
}
}
private Activity getPlainActivity() {
return ((Activity) getContext());
}
在 onResume 方法中,可以看到 onHostResume 的第二个参数传入了 (DefaultHardwareBackBtnHandler) getPlainActivity()。getPlainActivity() 方法其实就是返回的 ReactActivity 实例。从这里可以推断,具体的实现应该是交给了加载 React Native 视图的容器类:ReactActivity。
ReactActivity
ReactActivity 类实现了两个接口:DefaultHardwareBackBtnHandler、PermissionAwareActivity,并实现了对应的方法。PermissionAwareActivity是处理权限相关的接口,此处我们不再深入赘述。来看 ReactActivity 是如何实现 DefaultHardwareBackBtnHandler 接口中 invokeDefaultOnBackPressed 方法的。
package com.facebook.react;
/**
* Base Activity for React Native applications.
*/
public abstract class ReactActivity extends Activity
implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
// .... 代码省略
@Override
public void onBackPressed() {
if (!mDelegate.onBackPressed()) {
super.onBackPressed();
}
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
invokeDefaultOnBackPressed 方法中调用了super.onBackPressed(),即调用了父类 Activity 中的 onBackPressed 函数。onBackPressed 函数的作用是在 Android 中返回上一界面的,与 react-navigation 路由导航中的 goBack功能类似。到这里,我们最终可以得出结论:exitApp() 方法就是调用了 Native 层 ReactActivity 的 onBackPress 方法。
此时也就能解答文章开始时的问题了,通过 BackHandler.exitApp() 就可以完成在RN端跳转回原生层上一个Activity界面。同样,在纯React Native应用中,因为只有一个MainActivity(继承自ReactActivity),所以在 JS 端 代码调用 BackHandler.exitApp() 会直接执行 onBackPressed() ,完成退出当前App的操作。