一行代码,完成多层视图回退功能( android) - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
xuyt

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

  •  
  •   xuyt Jan 11, 2017 14885 views
    This topic created in 3393 days ago, the information mentioned may be changed or developed.

    其实是我做了个开源项目(^__^),拿出来给大家鉴赏下,欢迎大家提意见
    项目: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()方法,获取到管理的 ragments ,从而判断 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 数据
        • 外提供日志接口
        • li>可以使用"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 replies    2017-01-13 12:02:52 +08:00
    stdying
        1
    stdying  
       Jan 11, 2017 via Android
    我不知道该怎么回答
    YzSama
        2
    YzSama  
       Jan 12, 2017
    好详细。
    KNOX
        3
    KNOX  
       Jan 12, 2017
    个人看法:对项目的侵入性有点强
    xuyt
        4
    xuyt  
    OP
       Jan 12, 2017
    @KNOX 对于侵入性,确实是很强,这个我确实没有考虑过,因为我是直接使用在公司项目中的。
    只是需要注意在使用 launchMode 的时候,需要使用 startActivity4NonBackFlow 这个方法。
    但是,对于非常繁琐的回退流程,我觉得使用这个应该是非常简便的。
    所以,我们需要权衡两者的利弊。
    pcatzj
        5
    pcatzj  
       Jan 13, 2017
    第一次看到这么长的 v 文(汗),我没有看完,但是看了开头一点,想问问,直接退出程序,设置 rootActivity 为 singleTask 或者直接杀进程实现不了吗,有什么优点吗?请不吝赐教。
    fragment 部分没有看,但是安利一个库叫做 fragmentation ,对多 fragment 管理还不错。
    KNOX
        6
    KNOX  
       Jan 13, 2017
    @xuyt 正如你说的,这个库是你们公司使用的,所以其他公司的项目只会使用自己开发的类似的库,否则到时候出问题了谁能保证别人第一时间给我解决,你的库有可取的点,但是不一定适合其他项目,如果想扩散使用我建议是减少入侵性,接入容易,撤出也容易。
    xuyt
        7
    xuyt  
    OP
       Jan 13, 2017
    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
       Jan 13, 2017
    @KNOX
    1 、这个是我自己开源的项目,所以若有任何 bug ,可以在 github 上提出来,我虽然不能保证是第一时间,但绝对是会在最短的时间中解决的。
    2 、我刚刚也解决了一个 bug ,所以,我是会持续的对这个项目进行支持的。
    所以我也希望用户若有使用的,请在遇到 bug 时,也能在 github 上提出,能有解决方案就更好了。
    3 、关于倾入性,主要是关于解决回退功能复杂度的问题,所以需要权衡两者的利弊,若是你的项目中有大量的多页面回退需求,我觉得这还是一个很好地解决方案的。
    About     Help     Advertise     Blog     API     FAQ     Solana     1030 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 64ms UTC 22:12 PVG 06:12 LAX 15:12 JFK 18:12
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86