安卓Back键的处理·基本+高级篇




  • 基本用法

    根据文档,安卓back键的处理主要就是一个事件监听:

    BackAndroid.addEventListener('hardwareBackPress', this.onBackPressed);
    BackAndroid.removeEventListener('hardwareBackPress', this.onBackPressed);
    

    starter-kit里,我们在App这一级别,实现了按back键回退导航栈的功能:

    class App extends React.Component {
      componentWillMount() {
        if (Platform.OS === 'android') {
          BackAndroid.addEventListener('hardwareBackPress', this.onBackAndroid);
        }
      }
      componentWillUnmount() {
        if (Platform.OS === 'android') {
          BackAndroid.removeEventListener('hardwareBackPress', this.onBackAndroid);
        }
      }
      onBackAndroid = () => {
        const nav = this.navigator;
        const routers = nav.getCurrentRoutes();
        if (routers.length > 1) {
          nav.pop();
          return true;
        }
        return false;
      };
      ……
    }
    

    注意这里为了方便后续removeEventListener,采用了用绑定this的函数属性的方法来创建回调函数,而非箭头函数或者bind(this),这一点参考之前的博文

    代码中,当componentWillMount的时候挂接事件。对于应用根组件来说,这个生命周期就基本和我们应用的生命周期一致了。当back键被按下的时候,首先检查当前的导航栈,如果多余一个页面,则退回顶部的页面。

    说明:BackAndroid在iOS平台下是一个空实现,所以理论上不做这个Platform.OS === 'android'判断也是安全的。

    使用默认行为/退出应用

    back键的默认行为就是退出应用了。我们通常需要判断某些条件,并最后决定是否要退出应用。上文中的例子就使用了第一种调用默认行为的方法:

    如果所有事件监听函数中,没有任何一个返回真值,就会默认调用默认行为

    如果你只挂接了一个监听函数,那么你的返回值就决定了是否要调用默认行为:true为不调用,false为调用

    在上文代码中,我们如果导航栈多于一个页面,就不调用默认行为,而如果只有一个页面,则调用默认界面。

    例子:“再按一次退出应用”

    常有这种需求:按下back键以后,弹出一个toast,然后在一定时间内再按一次,才退出应用。这个代码就可以这样写:

      onBackAndroid = () => {
        if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
          //最近2秒内按过back键,可以退出应用。
          return false;
        }
        this.lastBackPressed = Date.now();
        ToastAndroid.show('再按一次退出应用');
        return true;
      };
    

    还有一种情况,我们在监听函数中不能决定是否要调用默认行为,要等待一个异步操作之后才调用默认行为,此时可以通过第二种办法:

    使用BackAndroid.exitApp()来退出应用。

    例子:在退出应用之前保存数据

    写法1:

      onBackAndroid = () =>{
        saveData().then(()=>{
          BackAndroid.exitApp();
        });
        return true;
      }
    

    在监听函数中,我们开始异步事件,并直接return true。此时默认行为不会被调用。当保存完毕后,我们调用exitApp(),触发默认行为,退出应用。

    写法2:

      onBackAndroid = async () =>{
        await saveData();
        BackAndroid.exitApp();
      }
    

    这里我们用了async函数,async 函数总是返回一个Promise,Promise作为一个对象,也被认为是一个“真值”,所以这种情况下默认行为总是不会被调用。当保存完毕后,我们调用exitApp(),触发默认行为,退出应用。

    根据当前界面决定作何动作

    有时候我们有这样的需求:当用户处于某些界面下时,back键要做特殊的动作,如:提示用户是否要保存数据,或者解锁界面禁止back键返回等等。此时,最佳实践是在route或route中对应的Component上保存关于如何处理back键的信息:

      onBackAndroid = () => {
        const nav = this.navigator;
        const routers = nav.getCurrentRoutes();
        if (routers.length > 1) {
          const top = routers[routers.length - 1];
          if (top.ignoreBack || top.component.ignoreBack){
            // 路由或组件上决定这个界面忽略back键
            return true;
          }
          const handleBack = top.handleBack || top.component.handleBack;
          if (handleBack) {
            // 路由或组件上决定这个界面自行处理back键
            return handleBack();
          }
          // 默认行为: 退出当前界面。
          nav.pop();
          return true;
        }
        return false;
      };
    


  • @tdzl2003 说:

    const nav = this.navigator;

    这边是否应该是this.props.navigator?



  • @绯色流火 参见starter-kit,这段代码是在最外面一级,Navigator是通过ref访问到的



  • @tdzl2003 哦,那如果不按您那么写。我用top.component.prototype.ignoreBack,top.component.prototype.handleBack这样写似乎也可以实现,问下有没有什么弊处?



  • @tdzl2003 好吧,发现执行handle时,this.setState执行不了,打印出来的this是undefined,不知道是不是我这么写的问题……



  • @绯色流火 你好
    关于android返回键的监听this的问题你怎么解决的



  • 请问handback方法是怎么传递过来的



  • nav.push({
    handback: () => alert(11111111)
    })

    具体也可以参考:http://bbs.reactnative.cn/topic/800/reactnative导航设计与实现



  • @cnsnake11 如何把alert(11111)换成当前界面处理back键的一个方法 换了当前界面方法以后在方法里怎么获得this



  • @DTT 箭头函数的话 this会自定绑定当前的 不需要你处理

    nav.push({

    handback: () =>{
    alert('这里的this是你调用的nav.push时候的对象了');
    // 以下为伪代码,假如你调用nav.push时候对象有sayHello方法,直接用就好
    this.sayHello();
    }

    })



  • @cnsnake11

    比如B界面有个pressBack()的方法
    这个时候我从A界面push到B界面 如下:
    nav.push({
    component:B界面,
    handleBack:() =>{

              }
            }
          });
    

    这个时候handleBack里如何放置B界面的pressBack方法并能保证这个pressBack方法能获得到B界面的this'



  • @DTT 这种情况就不能用nav.push里的route来注册了,因为push的时候B界面还未初始化

    在B界面的初始化方法里,使用如下代码获得当前页面的route对象

    let routes = nav.getCurrentRoutes(); //nav是导航器对象
    let lastRoute = routes[routes.length - 1]; // 当前页面对应的route对象
    

    然后在这样注册即可: lastRoute. handleBack = () => {alert('在B注册的handleBack')}



  • @cnsnake11
    非常感谢 问题已解决



  • 此回复已被删除!


  • @tdzl2003 你好 请问 BackAndroid.exitApp() 这个是直接退出应用 而不是后台运行
    如何在adnroid下后台运行呢



  • @woainbadc 这个功能原生开发都没有办法做到。你只能选择退出Activity,如果有需要后台执行的任务,通过编写Service来实现。



  • @tdzl2003 说:

    this.lastBackPressed

    这个是需要自己定义吗,直接用报undefined is not an object(evaluating 'this.lastBackPressed')



  • @jin 这是因为你绑定回调的时候没有绑定this,具体参考这篇帖子的 把方法作为回调传递 一节


登录后回复