React Native BackHandler exitApp

作者: _海角_ | 来源:发表于2019-02-14 14:44 被阅读2次

    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<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 获取焦点
     */
    @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

    相关文章

      网友评论

        本文标题:React Native BackHandler exitApp

        本文链接:https://www.haomeiwen.com/subject/mypseqtx.html