V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xuyt
V2EX  ›  Android

一行代码,完成多层视图回退功能( android)

  •  
  •   xuyt · 2017-01-11 21:22:42 +08:00 · 6270 次点击
    这是一个创建于 2633 天前的主题,其中的信息可能已经有所发展或是发生改变。

    其实是我做了个开源项目(^__^),拿出来给大家鉴赏下,欢迎大家提意见
    项目:https://github.com/xuyt11/androidBackFlow 欢迎关注和 star 。
    功能:一个控制 Android 视图( activity 与 fragment )回退的工具。
    tip :这是一个回退工具,不是跳转工具

    现在的开发同学应该对产品的需求有蛮多体会,就是好好的业务流程,在经过多次发版之后,从树形变成了网状,就算是自己写的,只要时间隔久了也要仔细的去阅读代码,才能再次小心翼翼的去修改。

    现在的痛点

    业务流程的回退功能:

    • 需要提前 finish 或者 finish 几个页面。
    • 在逻辑判断的时候,要去设置状态值,用 setResult 来 finish 多个页面。...

    各种各样的多视图回退,造成了复杂的视图跳转逻辑。且这样造成了在回退过程中的多个视图,都有状态的判断逻辑。
    所以,如果有一个工具,能够在我们需要回退的时候,就能回退到目标视图,将会降低代码的复杂及提高我们开发的速度。

    解决思路

    1. 原来的回退视图功能,我们主要利用的是 startActivityForResult 、 onActivityResult 、 setResult 与 finish(activity)来进行实现的。
      • 所以,如果我们每一次都是调用的 startActivityForResult 方法,而不是 startActivity ,那我们不就可以使用 onActivityResult 、 setResult 与 finish(activity),来进行链式的视图回退了!
    2. 如何回退到指定的 activity 或 fragment 呢!
      我们可以知道回退的 activity 与 fragment 的 class 类型,所以只要我们可以在 Activity 的 onActivityResult 方法中判断当前 activity 与其中管理的 fragments ,就能够在指定视图不再执行 finish 方法,从而让指定视图显示出来
      • activity 的判断:
        • 在 Activity 的 onActivityResult 方法中,我们能判断当前 Activity 的 Class 类型;
      • activity 中 fragment 的判断:
        • 在 Activity 的 onActivityResult 方法中,我们能通过 getSupportFragmentManager().getFragments()方法,获取到管理的 fragments ,从而判断 fragment 的的 Class 类型;
      • fragment 中的 fragments 的判断:
        • 这些 fragment 也能通过 getChildFragmentManager().getFragments()方法获取到。
    3. 若在固定顺序的业务流程中,我想要按照 activity 界面的 position 来进行回退呢!
      在固定顺序的业务流程中,每个 activity 都有固定的 position ,所以只要计算 position 的差值(即 backActivityCount ),我们可以在 onActivityResult 方法中,回退数量为 backActivityCount 个的 Activity ,就可以了。

    如何优雅的退出 App ?这样优雅的退出 App !

    BackFlow.finishTask(activity | fragment)
    

    当然,这是有限制的(。﹏。),只是退出当前的 task 而已!
    finish_task.gif

    下面是 androidBackFlow 的详细介绍

    简介

    1. 这是一个链式回退多层视图的工具。
    2. 这是一个在single task && single process环境中使用的工具。
    3. 若 app 中有多个 task 或 process ,则只能在 task 或 process 之中使用,不能超过其中任何一个的范围。
    4. task 与 task , task 与 process , process 与 process 之间的回退功能,需要自己或系统去控制。
    5. [若在 task 中有消耗过 onActivityResult 方法的情况,则 BackFlow 会失效。](#backflow 不能使用的情况或不能回退到目标位置)

    快速使用

    1. 使用前:
      • 将 App 中所有的 activity 与 fragment 都继承于两个基础类( BaseBackFlowActivity 与 BaseBackFlowFragment )
      • 或将 app 的基础类继承于两个基础类( BaseBackFlowActivity 与 BaseBackFlowFragment )
      • 或在自己的基础类中 @override startActivity 与 onActivityResult ,并添加 startActivity4NonBackFlow 方法;
    2. 结束该 activity 所属的 task :
      • 若该 App 是单 task 的,则有结束 App 中所有的 activity 效果( finish 该 task 中所有的 activity ,即退出了 App )
      • 若在整个回退流程流程中,没有匹配到目标,也相当于 finish_task 的功能。
      • 若中间有 onActivityResult 方法被消耗,则会停留在最后一个被消耗的 activity (因为 setResult 已无效)。
      • 代码
      BackFlow.finishTask(activity | fragment)
      
      or
      
      BackFlow.build(BackFlowType.finish_task, activity | fragment).create().request()
      
      • 效果
        finish task.gif
    3. 返回到指定的 activity (回退到指定的 activity ),若有多个 activity 实例,则只会回退到第一个匹配;
      • 代码
      BackFlow.request(activity | fragment, @NonNull Class<? extends Activity> atyClass)
      
      or
      
      BackFlow.build(BackFlowType.back_to_activity, activity | fragment)....create().request()
      
    4. 返回到指定的 fragment 列(回退到第一个匹配该 fragment 顺序列的 activity ,会调用 fragments 中最后一个 fragment 的 onActivityResult )
      • 代码
      BackFlow.request(activity | fragment, @NonNull Class<? extends Fragment>... fragmentClazzs)
      
      or
      
      BackFlow.build(BackFlowType.back_to_fragments, activity | fragment)....create().request()
      
      • 效果
        request_fragments.gif
    5. 返回到 activity 和 fragment 列都一致的 activity (回退到包含了该 fragment 顺序列的 activity ,会调用 fragments 中最后一个 fragment 的 onActivityResult )
      • 代码
      BackFlow.request(activity | fragment, @NonNull Class<? extends Activity> atyClass, @NonNull Class<? extends Fragment>... fragmentClazzs)
      
      or
      
      BackFlow.build(BackFlowType.back_to_activity_fragments, activity | fragment)....create().request()
      
      • 效果
        request_activity_fragments.gif
    6. 回退数量为 backActivityCount 个的 Activity
      • 代码
      BackFlow.request(activity | fragment, backActivityCount)
      
      or
      
      BackFlow.build(BackFlowType.back_activity_count, activity | fragment).setBackActivityCount(...).create().request()
      
    7. 若有额外参数,可以使用带 Bundle 参数的 request 方法
      • 传入额外参数
      BackFlow.request(activity | fragment, @NonNull Bundle extra, @NonNull Class<? extends Activity> atyClass)
      
      • 判断是否有额外参数
      BackFlow.hasExtra(Intent data)
      
      • 获取额外参数
      BackFlow.getExtra(Intent data)
      
    8. 也可以自己去使用 Builder 去构建 BackFlow request
      • 代码
      BackFlow.builder(BackFlowType.back_to_fragments, activity | fragment)....create().request()
      

    tip

    • fragment 的 sub fragment manager 必须要是 getChildFragmentManager
    • BackFlow 内部的 Fragment 是 support-v4 包中的,若 app 中使用的不是 android.support.v4.app.Fragment ,则可以将其替换为你自己的 Fragment 类型
    • 若你内部的 Fragment 有多个基础类型(android.support.v4.app.Fragment, android.app.Fragment),那你需要统一
    • 若跳转目标 View 是 Fragment ,则该 Fragment 的 ParentFragment 是不会调用到 onActivityResult 方法的

    内部实现

    1. 利用 startActivityForResult 、 onActivityResult 、 setResult 与 finish(activity)4 四个方法,进行实现的;
    2. 需要有两个基础类: BaseBackFlowActivity 与 BaseBackFlowFragment ,所有的 activity 与 fragment 都需要继承于他们;
    3. 需要 @Override App 中 BaseBackFlowActivity 与 BaseBackFlowFragment 两个类的 startActivity 方法,
      • 在内部实现中调用 startActivityForResult 方法,使得在 BackFlow 操作时,能串行链式的回退;
      @Override
      public void startActivity(Intent intent) {
         startActivityForResult(intent, BackFlow.REQUEST_CODE);
      }
      
    4. 需要 @Override App 中 BaseBackFlowActivity 的 onActivityResult(requestCode, resultCode, data)方法,并在内部调用 BackFlow.handle(this, resultCode, data)来进行回退操作的管理,并在目标位置结束继续调用 onActivityResult 方法;
      • tip: 不需要 @Override BaseBackFlowFragment
      @Override
      protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (BackFlow.handle(this, getSupportFragmentManager().getFragments(), requestCode, resultCode, data)) {
             return;
         }
         super.onActivityResult(requestCode, resultCode, data);
      }
      
    5. 调用 BackFlow 方法执行回退操作;
      • BackFlow 操作内部会调用 setResult 与 finish(activity)方法,用于链式回退;
      static void request(@NonNull Activity activity, @NonNull Intent backFlowData) {
          activity.setResult(RESULT_CODE, backFlowData);
          activity.finish();
      }
      
      • 例如:退出当前的 activity 所属的 task ( finish 该 task 中所有的 activity )
        • tip :有些情况会有影响: [BackFlow 不能使用的情况或不能回退到目标位置](#backflow 不能使用的情况或不能回退到目标位置)
      BackFlow.finishTask(activity | fragment)
      

    代码简介

    1. 主功能: BackFlowType
      • 共五个分类: error , finish_task , back_to_activity , back_to_fragments , back_to_activity_fragments
        • finish_task
          • 结束 task :若该 App 是单 task ,则有结束 App 中所有的 activity 效果( finish 该 task 中所有的 activity )
          • 若在整个回退流程流程中,没有匹配到目标,也相当于 finish_task 的功能。
          • 若中间有 onActivityResult 方法被消耗,则会停留在最后一个被消耗的 activity (因为 setResult 已无效)。
        • back_to_activity
          • 返回到指定的 activity (回退到指定的 activity ),若有多个 activity 实例,则只会回退到第一个匹配;
        • back_to_fragments
          • 返回到指定的 fragment 列(回退到第一个匹配该 fragment 顺序列的 activity )
        • back_to_activity_fragments
          • 返回到 activity 和 fragment 列都一致的 activity (回退到包含了该 fragment 顺序列的 activity )
        • back_activity_count
          • 回退数量为 backActivityCount 个的 Activity
          • 适用于固定顺序的业务流程中,每个 activity 界面都能有固定的 position
          • 两个 activity position 的差值,即为 backActivityCount
        • error: 异常情况
          • onActivityResult 方法参数 data 中 data.getIntExtra(BACK_FLOW_TYPE, ERROR_BACK_FLOW_TYPE),异常类型都返回该类型,且直接抛出异常;
    2. 调用类: BackFlow
      • BackFlowType 类的请求执行与处理的包装器,方便使用
      • 设置了默认 RESULT_CODE 值( Integer.MAX_VALUE );
        • 这是回退功能的核心结构,所以其他业务操作的 resultCode 不能与其一样,否则会有错误;
      • 设置了默认的 REQUEST_CODE 值( 0x0000ffff );
        • override startActivity 方法时调用的,防止不能触发 onActivityResult 方法
        • 其他的 requestCode ,不能与其一样,否则 App 内部业务逻辑可能有异常情况
        • tip: Can only use lower 16 bits for requestCode
      • request back flow :执行 BackFlow 操作的方法组
      • builder request param and get extra : builder BackFlow 操作与 BackFlow 的额外数据
      • handle back flow :判断与处理 BackFlow 操作
      • back flow log
        • 打印 Intent 数据
        • 外提供日志接口
        • 可以使用"BackFlow-->"来进行日志过滤,查看 BackFlow 的数据流转
        • 也可以设置一个统一的日志开关,用于开启、禁止 BackFlow 日志
    3. 基础类: BaseBackFlowActivity 与 BaseBackFlowFragment
      • 所有的 activity 与 fragment 都需要继承于他们;
      • 或者实现两个类的功能:
        • @Override 两个类的 startActivity(intent)方法,并且在内部实现中调用 startActivityForResult(intent, requestCode)方法,使得在 BackFlow 操作时,能串行的回退;
        startActivityForResult(intent, BackFlow.REQUEST_CODE);
        
        • @Override BaseBackFlowActivity 的 onActivityResult(requestCode, resultCode, data)方法
          • 并在内部调用 BackFlow.handle(this, getSupportFragmentManager().getFragments(), requestCode, resultCode, data)来进行回退操作的管理,
          • 并在目标位置结束继续调用 BackFlow.request(activity, data)
    4. BackFlow 参数类: BackFlowParam
      • 执行 BackFlow 操作的参数类,共有 6 个参数
        • BackFlowType type :该 BackFlow 的类型,共有 5 个,其中 error 类型是不能使用的
        • Activity 与 backFlowData(Intent):
          • 在执行 BackFlow 时需要这两个参数
          public void request() {
              BackFlow.request(activity, backFlowData);
          }
          
          • Activity :在执行 BackFlow 时需要
          static void request(@NonNull Activity activity, @NonNull Intent backFlowData) {
              activity.setResult(RESULT_CODE, backFlowData);
              activity.finish();
          }
          
          • backFlowData(Intent):执行 BackFlow 的数据,由四个参数组成
            • type , atyClass , fragmentClazzs , extra
        • Class<? extends Activity> atyClass
          • BackFlow 回退的目标 activity
        • List<Class<? extends Fragment>> fragmentClazzs
          • 回退到该 fragment 的顺序列表, fragments 顺序列中的目标 fragment(最后一个 fragment)
        • backActivityCount
          • 回退 Activity 界面的数量,每一次回退都会--backActivityCount,
          • 当 currbackActivityCount 为 0(ACTIVITY_COUNT_OF_STOP_BACK_FLOW),不再回退
          • 若 backActivityCount 设置为 1 ,则只 finish 当前的 activity
        • Bundle extra :额外的附加数据
      • Builder : Builder 模式,减少创建 backFlowData 的复杂度
    5. BackFlow Intent 工具类: BackFlowIntent
      • 组装与解析 BackFlow Intent 的工具类,共有四个参数, app 中其他的 key 不能与他们的 key 相同
        • BACK_FLOW_TYPE :回退功能的类型( BackFlowType.type )
          • type is int
          • ERROR_BACK_FLOW_TYPE: 异常错误类型的 type 值( BackFlowType.error.type )
          • 判断是否为 BackFlow 类型 onActivityResult
          private static boolean canHandle(int resultCode, Intent data) {
              return resultCode == RESULT_CODE && BackFlowType.isBackFlowType(data);
          }
          
        • BACK_FLOW_ACTIVITY :回退功能中指定的 activity
          • type is String
        • BACK_FLOW_FRAGMENTS :回退功能中指定的 fragment 顺序列
          • type is String
          • 使用 json 进行格式化
        • BACK_ACTIVITY_COUNT :回退 Activity 界面的数量
          • type is int
          • 每一次回退都会--backActivityCount,当 currBackActivityCount 为 0 的时候,不再回退
          • 若设置为 1 ,则只 finish 当前的 activity
        • BACK_FLOW_EXTRA :回退功能中用户带入的额外数据
          • type is String
          • 可以外带额外数据给目标的 Activity 或 Fragment
      • Builder : Builder 模式,减少创建 BackFlow Intent 的复杂度
    6. BackFlow 视图工具类: BackFlowViewHelper
      • 匹配 BackFlow 中目标 Activity 与 Fragment 的工具类
      • isTargetActivity 方法:是否为回退功能的目标 activity
      • findTargetFragment 方法:找到回退功能中 fragments 顺序列中的目标 fragment(最后一个 fragment)
      • tip: fragment 的 sub fragment manager 必须要是 getChildFragmentManager

    BackFlow 不能使用的情况或不能回退到目标位置

    1. 若在回退链中间有任何一个 XXXActivity 消耗过 onActivityResult 方法,则会停留在该 XXXActivity ,不能继续回退
      • 因为整个回退功能都是依赖于 setResult 方法将回退数据,链式的传递给前一个 activity 的 onActivityResult 方法,而在 activity 消耗了 onActivityResult 方法之后,是不会再调用该方法的。
    2. 现在发现的消耗 onActivityResult 方法的情况有:
      • 切换 task ;
      • 切换 process ;
      • 在 startActivity 时,调用了 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    3. launchMode
      • 不同的 android 版本,有不同的区别; 4.4 会消耗, 5.0.2 与 6.0 不会消耗
      • singleInstance
        • 会启用一个新的栈结构,将 Acitvity 放置于这个新的栈结构中,并保证不再有其他 Activity 实例进入
        • 4.4 会切换 task
        • 5.0.2 与 6.0 startActivityForResult 时不会切换 task ,所以仍然可以使用,不过这时 launchMode 将变为 standard
      • singleTask
        • 4.4 会消耗 onActivityResult 方法
        • 5.0.2 与 6.0 startActivityForResult 时不会回调 onActivityResult ,所以仍然可以使用,不过 startActivityForResult 方法,这时由于 launchMode 将变为 standard

    tips and limitations (提示与限制)

    1. launchMode: startActivityForResult 启动 singleTop, singleTask, singleInstance 的 XXXActivity
      • 5.0 之后的系统,则 XXXActivity 的 launchMode 设置失效,变为 standard launchMode
      • 5.0 之前的系统,只有 singleTop 模式失效
      • 所以,若有需求的话,则可以使用startActivity4NonBackFlow方法,不过这时候 BackFlow 将失效,将会停留在该处不再回退
    2. startActivityForResult + Intent.FLAG_ACTIVITY_NEW_TASK + singleInstance ,会启动一个新 task ,所以 BackFlow 将失效,将会停留在该处不再回退
    8 条回复    2017-01-13 12:02:52 +08:00
    stdying
        1
    stdying  
       2017-01-11 23:56:37 +08:00 via Android
    我不知道该怎么回答
    YzSama
        2
    YzSama  
       2017-01-12 09:36:14 +08:00
    好详细。
    KNOX
        3
    KNOX  
       2017-01-12 09:58:03 +08:00
    个人看法:对项目的侵入性有点强
    xuyt
        4
    xuyt  
    OP
       2017-01-12 18:13:08 +08:00
    @KNOX 对于侵入性,确实是很强,这个我确实没有考虑过,因为我是直接使用在公司项目中的。
    只是需要注意在使用 launchMode 的时候,需要使用 startActivity4NonBackFlow 这个方法。
    但是,对于非常繁琐的回退流程,我觉得使用这个应该是非常简便的。
    所以,我们需要权衡两者的利弊。
    pcatzj
        5
    pcatzj  
       2017-01-13 10:11:41 +08:00
    第一次看到这么长的 v 文(汗),我没有看完,但是看了开头一点,想问问,直接退出程序,设置 rootActivity 为 singleTask 或者直接杀进程实现不了吗,有什么优点吗?请不吝赐教。
    fragment 部分没有看,但是安利一个库叫做 fragmentation ,对多 fragment 管理还不错。
    KNOX
        6
    KNOX  
       2017-01-13 11:33:10 +08:00
    @xuyt 正如你说的,这个库是你们公司使用的,所以其他公司的项目只会使用自己开发的类似的库,否则到时候出问题了谁能保证别人第一时间给我解决,你的库有可取的点,但是不一定适合其他项目,如果想扩散使用我建议是减少入侵性,接入容易,撤出也容易。
    xuyt
        7
    xuyt  
    OP
       2017-01-13 11:48:32 +08:00
    1 、设置 rootActivity 为 singleTask 有这样一个问题:
    即在用户按下 home 键退出当前的 task 后,当再进入 task 时,在其上的所有 activity 都将被摧毁,
    所以,你的业务直接就全部销毁了,只剩下一个 rootActivity 了。
    最后,像手机中的电话 App 就是这个需求的,除非你的业务需求可以这样。
    2 、关于直接杀进程:
    可以看看这个: http://blog.csdn.net/u011277123/article/details/53579269
    《 Android 疑难杂症之 KillProcess 和 System.exit 无效》讲的很详细

    3 、我这个开源项目,其实是一个 Android 的视图回退工具,“退出程序”只是其中的一个功能类型(BackFlowType.finish_task),他还有其他 4 个功能类型。

    4 、最后,欢迎大家多提意见
    项目: https://github.com/xuyt11/androidBackFlow 欢迎关注和 star 。
    xuyt
        8
    xuyt  
    OP
       2017-01-13 12:02:52 +08:00
    @KNOX
    1 、这个是我自己开源的项目,所以若有任何 bug ,可以在 github 上提出来,我虽然不能保证是第一时间,但绝对是会在最短的时间中解决的。
    2 、我刚刚也解决了一个 bug ,所以,我是会持续的对这个项目进行支持的。
    所以我也希望用户若有使用的,请在遇到 bug 时,也能在 github 上提出,能有解决方案就更好了。
    3 、关于倾入性,主要是关于解决回退功能复杂度的问题,所以需要权衡两者的利弊,若是你的项目中有大量的多页面回退需求,我觉得这还是一个很好地解决方案的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5345 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 05:49 · PVG 13:49 · LAX 22:49 · JFK 01:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.