使用React-navigation時候 Android物理返回鍵&BackHandler exitApp 源碼分析

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浙炼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子唯袄,更是在濱河造成了極大的恐慌弯屈,老刑警劉巖恋拷,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件资厉,死亡現(xiàn)場離奇詭異,居然都是意外死亡宴偿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門诀豁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舷胜,“玉大人烹骨,你說我怎么就攤上這事≌姑ィ” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵簿废,是天一觀的道長空入。 經(jīng)常有香客問我,道長族檬,這世上最難降的妖魔是什么歪赢? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮单料,結(jié)果婚禮上埋凯,老公的妹妹穿的比我還像新娘点楼。我一直安慰自己,他們只是感情好白对,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布掠廓。 她就那樣靜靜地躺著,像睡著了一般甩恼。 火紅的嫁衣襯著肌膚如雪蟀瞧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天条摸,我揣著相機(jī)與錄音悦污,去河邊找鬼。 笑死钉蒲,一個胖子當(dāng)著我的面吹牛切端,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播子巾,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼帆赢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了线梗?” 一聲冷哼從身側(cè)響起椰于,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仪搔,沒想到半個月后瘾婿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烤咧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年偏陪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煮嫌。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡笛谦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昌阿,到底是詐尸還是另有隱情饥脑,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布懦冰,位于F島的核電站灶轰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏刷钢。R本人自食惡果不足惜笋颤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望内地。 院中可真熱鬧伴澄,春花似錦赋除、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至清焕,卻和暖如春并蝗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秸妥。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工滚停, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粥惧。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓键畴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親突雪。 傳聞我的和親對象是個殘疾皇子起惕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容