React Native 監(jiān)聽android 物理返回鍵
根據(jù)文檔摇肌,安卓back鍵的處理主要就是一個事件監(jiān)聽:
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('再按一次退出應(yīng)用', ToastAndroid.SHORT);
return true;
}else {
return false;
}
}else {
return false;
}
};
導(dǎo)航使用的是react-navigation歧蒋, 在注冊 StackNavigator 路由的時候醉锄,同時注冊BackHandler監(jiān)聽,也即很多時候是在程序入口文件App.js 中访得。
原生代碼需要注意的是,監(jiān)聽back鍵有兩個方法龙亲,但是不能同時使用,否則會出現(xiàn)監(jiān)聽不到的情況。
/**
* 監(jiān)聽Back鍵按下事件,方法1:
* 注意:
* super.onBackPressed()會自動調(diào)用finish()方法,關(guān)閉
* 當(dāng)前Activity.
* 若要屏蔽Back鍵盤,注釋該行代碼即可
*/
@Override
public void onBackPressed() {
super.onBackPressed();
System.out.println("按下了back鍵 onBackPressed()");
}
/**
* 監(jiān)聽Back鍵按下事件,方法2:
* 注意:
* 返回值表示:是否能完全處理該事件
* 在此處返回false,所以會繼續(xù)傳播該事件.
* 在具體項(xiàng)目中此處的返回值視情況而定.
*/
@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();
}
這一步是如何實(shí)現(xiàn)的呢迎膜?先說結(jié)論:
exitApp() 方法就是調(diào)用了 Native 層 ReactActivity 的 onBackPress 方法泥技。
進(jìn)入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 函數(shù)榕订。在 exitApp 函數(shù)中店茶,調(diào)用了 DeviceEventManager 的 invokeDefaultBackPressHandler 函數(shù)。
DeviceEventManager 是系統(tǒng)定義的處理硬件反壓等設(shè)備硬件事件的本機(jī)模塊的實(shí)現(xiàn)類劫恒,對應(yīng)于 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 端調(diào)用的方法(ReactMethod注釋)丛楚。
mInvokeDefaultBackPressRunnable 為 Runnable 對象,在構(gòu)造函數(shù)中憔辫,初始化了 mInvokeDefaultBackPressRunnable 趣些,并在 run 方法中執(zhí)行 backBtnHandler.invokeDefaultOnBackPressed();繼續(xù)跟蹤到 invokeDefaultBackPressHandler 函數(shù),可以看到贰您,在函數(shù)中通過獲取 Application 實(shí)例坏平,將 mInvokeDefaultBackPressRunnable 放在 UI 主線程隊(duì)列中執(zhí)行。
從構(gòu)造函數(shù)中锦亦,可以看出 backBtnHandler 是 DefaultHardwareBackBtnHandler 的實(shí)例舶替。接下來重點(diǎn)來看 backBtnHandler 中做了什么。
DefaultHardwareBackBtnHandler 的實(shí)現(xiàn)代碼同樣在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
* 默認(rèn)情況下孽亲,所有onBackPress()調(diào)用都不應(yīng)該執(zhí)行默認(rèn)的反向處理程序坎穿,而應(yīng)該將其傳播到JS實(shí)例。
* 如果JS不想處理反壓本身返劲,它應(yīng)該回調(diào)為native來調(diào)用這個應(yīng)該執(zhí)行默認(rèn)處理程序的函數(shù)
*/
void invokeDefaultOnBackPressed();
}
DefaultHardwareBackBtnHandler 被定義為 一個接口玲昧,其中聲明了 invokeDefaultOnBackPressed 函數(shù)方法。具體的實(shí)現(xiàn)行為交給子類來實(shí)現(xiàn)±郝蹋現(xiàn)在我們就需要跟蹤代碼孵延,找到 DeviceEventManagerModule 是在哪里被初始化的。還記得我們在Android中實(shí)現(xiàn)完JS的橋接Module模塊后亲配,需要將其添加到Package尘应,并在Application中注冊惶凝。所以,我們需要找到Package犬钢,就能找到 DeviceEventManagerModule 初始化玷犹。
React Native 系統(tǒng)的基本 Module 的 Package 為:CoreModulesPackage,路徑為:node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java:
CoreModulesPackage
package com.facebook.react;
/**
* 這是支持React Native的基本模塊坯屿。 調(diào)試模塊現(xiàn)在位于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);
}
}));
}
// .... 代碼省略
}
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 獲取焦點(diǎn)
*/
@ThreadConfined(UI)
public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
UiThreadUtil.assertOnUiThread();
mDefaultBackButtonImpl = defaultBackButtonImpl;
onHostResume(activity);
}
首先在 mPackages 中添加基本的Module,在初始化 CoreModulesPackage 的代碼中劫拢,我們發(fā)現(xiàn)强胰,在第二個參數(shù)中直接創(chuàng)建了DefaultHardwareBackBtnHandler 的實(shí)例,并在 invokeDefaultOnBackPressed() 方法中調(diào)用了 ReactInstanceManager 的 invokeDefaultOnBackPressed() 方法熟吏, 在 invokeDefaultOnBackPressed() 方法中 調(diào)用了 mDefaultBackButtonImpl 的 invokeDefaultOnBackPressed()玄窝。而 mDefaultBackButtonImpl 的具體實(shí)現(xiàn)實(shí)例是在 onHostResume 方法中傳入。onHostResume 是在 Activity 獲取焦點(diǎn)時執(zhí)行的代碼帽氓,而 ReactActivity 的實(shí)現(xiàn)依賴了 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 的第二個參數(shù)傳入了 (DefaultHardwareBackBtnHandler) getPlainActivity()势腮。getPlainActivity() 方法其實(shí)就是返回的 ReactActivity 實(shí)例捎拯。從這里可以推斷,具體的實(shí)現(xiàn)應(yīng)該是交給了加載 React Native 視圖的容器類:ReactActivity座菠。
ReactActivity
ReactActivity 類實(shí)現(xiàn)了兩個接口:DefaultHardwareBackBtnHandler、PermissionAwareActivity留凭,并實(shí)現(xiàn)了對應(yīng)的方法升略。PermissionAwareActivity是處理權(quán)限相關(guān)的接口屡限,此處我們不再深入贅述钧大。來看 ReactActivity 是如何實(shí)現(xiàn) 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 方法中調(diào)用了super.onBackPressed()眶诈,即調(diào)用了父類 Activity 中的 onBackPressed 函數(shù)瓜饥。onBackPressed 函數(shù)的作用是在 Android 中返回上一界面的乓土,與 react-navigation 路由導(dǎo)航中的 goBack功能類似。到這里狡相,我們最終可以得出結(jié)論:exitApp() 方法就是調(diào)用了 Native 層 ReactActivity 的 onBackPress 方法谣光。
此時也就能解答文章開始時的問題了,通過 BackHandler.exitApp() 就可以完成在RN端跳轉(zhuǎn)回原生層上一個Activity界面芬为。同樣蟀悦,在純React Native應(yīng)用中,因?yàn)橹挥幸粋€MainActivity(繼承自ReactActivity)氧敢,所以在 JS 端 代碼調(diào)用 BackHandler.exitApp() 會直接執(zhí)行 onBackPressed() 日戈,完成退出當(dāng)前App的操作。
感謝:https://blog.csdn.net/u013718120/article/details/84345566