使用React-navigation时候 Android物理返回键&BackHandler exitApp 源码分析
React Native 监听android 物理返回键
根据文档,安卓back键的处理主要就是一个事件监听:
componentDidMount() { BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid); }componentWillUnmount() { BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPressAndroid); } onBackButtonPressAndroid = () => { if (this.state.currentStateRoute.length ==1) { let array = this.state.currentStateRoute[0].routes; if (array.length ==1) { if (lastBackPressed && lastBackPressed + 2000 >= Date.now()) { BackHandler.exitApp(); return false; } lastBackPressed = Date.now(); ToastAndroid.show('再按一次退出应用', ToastAndroid.SHORT); return true; }else { return false; } }else { return false; } };
导航使用的是react-navigation, 在注册 StackNavigator 路由的时候,同时注册BackHandler监听,也即很多时候是在程序入口文件App.js 中。
原生代码需要注意的是,监听back键有两个方法,但是不能同时使用,否则会出现监听不到的情况。
/** * 监听Back键按下事件,方法1: * 注意: * super.onBackPressed()会自动调用finish()方法,关闭 * 当前Activity. * 若要屏蔽Back键盘,注释该行代码即可 */ @Override public void onBackPressed() { super.onBackPressed(); System.out.println("按下了back键 onBackPressed()"); } /** * 监听Back键按下事件,方法2: * 注意: * 返回值表示:是否能完全处理该事件 * 在此处返回false,所以会继续传播该事件. * 在具体项目中此处的返回值视情况而定. */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK)) { System.out.println("按下了back键 onKeyDown()"); return false; }else { return super.onKeyDown(keyCode, event); } }
至此,back键就可以正常返回了,
遇到的另一个问题是,BackHandler.exitApp()无法正常退出App
原因是
@Override public void invokeDefaultOnBackPressed() { super.onBackPressed(); }
这一步是如何实现的呢?先说结论:
exitApp() 方法就是调用了 Native 层 ReactActivity 的 onBackPress 方法。
进入BackHandler 源码,路径:node_modules/react-native/Libraries/Utilities/BackHandler.android.js:
BackHandler
var DeviceEventManager = require('NativeModules').DeviceEventManager; var BackHandler = { exitApp: function() { DeviceEventManager.invokeDefaultBackPressHandler(); }, /** * Adds an event handler. Supported events: * * - `hardwareBackPress`: Fires when the Android hardware back button is pressed or when the * tvOS menu button is pressed. */ addEventListener: function ( eventName: BackPressEventName, handler: Function ): {remove: () => void} { _backPressSubscriptions.add(handler); return { remove: () => BackHandler.removeEventListener(eventName, handler), }; }, /** * Removes the event 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
@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 = new Runnable() { @Override public void run() { UiThreadUtil.assertOnUiThread(); backBtnHandler.invokeDefaultOnBackPressed(); } }; } /** * Sends an event to the JS instance that the hardware back has been pressed. */ public void emitHardwareBackPressed() { getReactApplicationContext() .getJSModule(RCTDeviceEventEmitter.class) .emit("hardwareBackPress", null); } /** * Sends an event to the JS instance that a new intent was received. */ public void emitNewIntentReceived(Uri uri) { WritableMap map = Arguments.createMap(); map.putString("url", uri.toString()); getReactApplicationContext() .getJSModule(RCTDeviceEventEmitter.class) .emit("url", map); } /** * Invokes the default back handler for the host of this catalyst instance. This should be invoked * if JS does not want to handle the back press itself. */ @ReactMethod public void invokeDefaultBackPressHandler() { getReactApplicationContext().runOnUiQueueThread(mInvokeDefaultBackPressRunnable); } @Override public String getName() { return "DeviceEventManager"; }}
了解 Android 与 React Native 通信交互的朋友 看到 DeviceEventManagerModule 不会感到陌生。getName 方法返回原生 Module 模块的名称。在该模块下定义了供 JS 端调用的方法(ReactMethod注释)。
mInvokeDefaultBackPressRunnable 为 Runnable 对象,在构造函数中,初始化了 mInvokeDefaultBackPressRunnable ,并在 run 方法中执行 backBtnHandler.invokeDefaultOnBackPressed();继续跟踪到 invokeDefaultBackPressHandler 函数,可以看到,在函数中通过获取 Application 实例,将 mInvokeDefaultBackPressRunnable 放在 UI 主线程队列中执行。
从构造函数中,可以看出 backBtnHandler 是 DefaultHardwareBackBtnHandler 的实例。接下来重点来看 backBtnHandler 中做了什么。
DefaultHardwareBackBtnHandler 的实现代码同样在node_modules/react-native/ReactAndroid/src/main/java/com.facebook.react.modules.core目录下:
DefaultHardwareBackBtnHandler
package com.facebook.react.modules.core;/** * Interface used by {@link DeviceEventManagerModule} to delegate hardware back button events. It's * suppose to provide a default behavior since it would be triggered in the case when JS side * doesn't want to handle back press events. */public interface DefaultHardwareBackBtnHandler { /** * By default, all onBackPress() calls should not execute the default backpress handler and should * instead propagate it to the JS instance. If JS doesn't want to handle the back press itself, * it shall call back into native to invoke this function which should execute the default handler * 默认情况下,所有onBackPress()调用都不应该执行默认的反向处理程序,而应该将其传播到JS实例。 * 如果JS不想处理反压本身,它应该回调为native来调用这个应该执行默认处理程序的函数 */ void invokeDefaultOnBackPressed();}
DefaultHardwareBackBtnHandler 被定义为 一个接口,其中声明了 invokeDefaultOnBackPressed 函数方法。具体的实现行为交给子类来实现。现在我们就需要跟踪代码,找到 DeviceEventManagerModule 是在哪里被初始化的。还记得我们在Android中实现完JS的桥接Module模块后,需要将其添加到Package,并在Application中注册。所以,我们需要找到Package,就能找到 DeviceEventManagerModule 初始化。
React Native 系统的基本 Module 的 Package 为:CoreModulesPackage,路径为:node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java:
CoreModulesPackage
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 getNativeModules(final ReactApplicationContext reactContext) { return Arrays.asList( ModuleSpec.nativeModuleSpec( AndroidInfoModule.class, new Provider() { @Override public NativeModule get() { return new AndroidInfoModule(reactContext); } }), ModuleSpec.nativeModuleSpec( DeviceEventManagerModule.class, new Provider() { @Override public NativeModule get() { return new DeviceEventManagerModule(reactContext, mHardwareBackBtnHandler); } }), ModuleSpec.nativeModuleSpec( ExceptionsManagerModule.class, new Provider() { @Override public NativeModule get() { return new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()); } }), ModuleSpec.nativeModuleSpec( HeadlessJsTaskSupportModule.class, new Provider() { @Override public NativeModule get() { return new HeadlessJsTaskSupportModule(reactContext); } }), ModuleSpec.nativeModuleSpec( SourceCodeModule.class, new Provider() { @Override public NativeModule get() { return new SourceCodeModule(reactContext); } }), ModuleSpec.nativeModuleSpec( Timing.class, new Provider() { @Override public NativeModule get() { return new Timing(reactContext, mReactInstanceManager.getDevSupportManager()); } }), ModuleSpec.nativeModuleSpec( UIManagerModule.class, new Provider() { @Override public NativeModule get() { return createUIManager(reactContext); } }), ModuleSpec.nativeModuleSpec( DeviceInfoModule.class, new Provider() { @Override public NativeModule get() { return new DeviceInfoModule(reactContext); } })); } // .... 代码省略 }
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的操作。
感谢:https://blog.csdn.net/u013718120/article/details/84345566
更多相关文章
- Android Repo 超时的解决方法
- android的sqlite主键设置方法。
- Android中处理代码未捕获异常
- java中Arraylist复制方法
- Android中完全退出APP的方法
- android中动态给EditText获得焦点并弹起键盘的方法
- Android时间互换代码
- Android开源项目:捕鱼达人游戏源代码