當前位置:
首頁 > 知識 > React Native BackHandler exitApp 源碼分析

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 stylex={ 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的操作。

React Native BackHandler exitApp 源碼分析

打開今日頭條,查看更多精彩圖片
喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

頁面跳轉的兩種方式(轉發和重定向)區別及應用場景分析
8個好用的在線Linux終端和發行版

TAG:程序員小新人學習 |