React Native BackHandler exitApp 原始碼分析

Songlcy發表於2018-11-22

 

歡迎大家關注【跨平臺開發那些事】公眾號,定期推送跨平臺開發技術實踐。

概述

昨天技術交流群裡有個朋友提出一個問題,在 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的操作。

相關文章