Android多回退栈实践(一)

Android应用中,我们可以通过点击设备实体返回按键,或者应用左上角标题栏返回按钮进行返回。

点击系统按钮返回
device-2022-04-29-153234 00_00_00-00_00_30.gif
点击应用左上角返回按钮
device-2022-04-29-153250 00_00_00-00_00_30.gif
从用户角度来讲,返回操作是一个类似栈的操作。点击返回时,之前的一系列界面,按照退栈的形式,依次退回。
从开发角度来讲,这样一系列的回退操作,称之为回退栈。

在还未出现Fragment的早期应用,我们一般是不需要关心回退栈的。用户使用如何进入,就如何退出。
在使用Fragment之后,事情开始变得麻烦起来,开发者大多数采用了非常流程的单Activity、多Fragment的UI形式,而随着产品设计的不断迭代,原始的回退策略,已经不能满足我们的要求的,期间存在很长一段时间,我们开发者自己管理回退栈逻辑,在应用层写入自己的Fragment切换逻辑,自己监听回退事件,切换到适当的Fragment。但是这样管理,非常容易出现bug,Fragment的生命周期,也变得混乱起来。这样做也不能完全逃避Android自带的回退栈管理方式,只不过从UI效果上来讲,还能满足需求。

直到2018年5月8日,谷歌正式发布了Navigation,开发者似乎从回退栈中解放了出来,它把FragmentActivity以及Dialog都当做Navigation的一个目的地,通过一种单向图的形式串联起来,使开发者可以在不同的目的地之间进行跳转。开发者不需要关心回退栈的具体细节,我们只需要设定好NavigationGraph即可,当我们需要跳转的时候,我们使用Graph中的某个ActionNavigation会帮我们准备好一切并完成跳转。

一切看起来都是那么完美,但是,如果我们想有多个回退栈呢?

Screenshot_20220429_160637.png

我们现在有这样一个用例,我们的应用首页底部有一个导航栏,这三个按钮可以进入三个不同的分类中,分别是_Music_,FavoriteCollection。我们希望这三个不同的分类中,它们的回退逻辑是单独的:假设从_Music_进入到_MusicDetail_,当我们切换到_Favorite_,然后再切回来的时候,界面显示的是_MusicDetail_。

device-2022-04-29-161838 00_00_00-00_00_30.gif

在官方库androidx.fragment:fragment-ktx:1.4.0-alpha01中,正式支持了多回退栈的特性。
我们来看下,关于多回退栈,官方是如何支持的。

fragment-ktx库中,添加了两个关键的API:

public void saveBackStack(@NonNull String name) 
public void restoreBackStack(@NonNull String name) 

我们先来看saveBackStack的解释:

Save the back stack. While this functions similarly to popBackStack(String, int), it does not throw away the state of any fragments that were added through those transactions. Instead, the back stack that is saved by this method can later be restored with its state in tact.
This function is asynchronous – it enqueues the request to pop, but the action will not be performed until the application returns to its event loop.

参照文档,这个函数的作用有点类似popBackStack函数,但是saveBackStack并不会完全像popBackStack那样(popBackStack会退栈,同时事务里面的状态会丢掉,回退到的Fragment会执行状态恢复),saveBackStack也会执行退栈,但是他会把退栈的这些事务保存起来,用于后面的函数restoreBackStack执行。

简单解释下:

假设现在添加三个Fragment进去:

supportFragmentManager.commit {
    setReorderingAllowed(true)
    replace<AFragment>(R.id.container)
}

supportFragmentManager.commit {
    setReorderingAllowed(true)
    replace<BFragment>(R.id.container)
    addToBackStack("B")
}

supportFragmentManager.commit {
    setReorderingAllowed(true)
    replace<CFragment>(R.id.container)
    addToBackStack("C")
} 

Snipaste_2022-04-29_17-23-11.png

现在,当我调用saveBackStack时:

supportFragmentManager.saveBackStack("B") 

Snipaste_2022-04-29_18-15-31.png

FragmentManager会将当前保存的回退栈,存入到一个MapBackStackState中,同时我们回退栈栈顶就变成了A

于是,此时如果我们再次进行进栈操作的话:

supportFragmentManager.commit {
    setReorderingAllowed(true)
    replace<DFragment>(R.id.container)
    addToBackStack("D")
}

supportFragmentManager.commit {
    setReorderingAllowed(true)
    replace<FFragment>(R.id.container)
    addToBackStack("F")
} 

Snipaste_2022-04-29_18-24-47.png

它会在当前回退栈的基础上进栈,不影响BackStackState表。

当然,我们依然可以在此时继续调用saveBackStack

supportFragmentManager.saveBackStack("D") 

此时,FragmentManger所管理的栈,就会变成:

Snipaste_2022-04-29_18-30-43.png

BackStackState表中,会有两个保存的栈,一个是_B_,一个是_D_。
这里在使用saveBackStack函数的时候,有一个需要注意的地方:

  • saveBackStack函数传入的参数,是保存栈的名称,该名必须是通过addToBackStack添加进去的一个已有的栈名,同时在保存到BackStackState表时,会使用该参数作为保存栈的Key

如图所示,BackStackState表可以存入多个保存栈。

现在,当我们使用restoreBackStack时,FragmentManger会帮助我们做栈恢复的操作:

supportFragmentManager.restoreBackStack("B") 

Snipaste_2022-04-29_18-41-49.png

Excellent!
我们可以将已经保存的栈,恢复到当前的回退栈。这样当用户进行回退操作的时候,便会按照_C->B->A_的顺序,进行回退了。

回到我们提到的需求,我们现在有六个页面。
MusicFavoriteCollectionMusicDetailFavoriteDetailCollectionDetail
我们希望_Music->MusicDetail_,Favorite->FavoriteDetailCollection->CollectionDetail,分别占用三个不互相影响的栈。具体思路就是:在切换菜单项的时候,先保存上一次的栈,然后恢复当前栈。代码会在文章最后给出,仅供参考。

借助于fragment-ktx库,我们可以通过多回退栈来实现复杂的产品需求,让开发者从回退栈的具体事务中抽出身来,更加专注业务代码。

当然,这还不是完全体,下一篇文章,我们将借助于Navigation,更快速的实现该用例。

下一篇:[Android多回退栈实践(二) - 掘金 (juejin.cn)]

完整代码:

class MultiStackPage : AppCompatActivity() {


    private var mSelectId = -1

    private var musicSaved = false
    private var favoriteSaved = false
    private var collectSaved = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_multi_stack)

        findViewById<BottomNavigationView>(R.id.bottom_nav).let { nav ->
            nav.setOnItemSelectedListener {

                if (mSelectId == it.itemId) {
                    return@setOnItemSelectedListener true
                }

                when (it.itemId) {
                    R.id.action_music -> {
                        if (mSelectId != -1) {
                            when (mSelectId) {
                                R.id.action_favorite -> {
                                    supportFragmentManager.saveBackStack(STACK_FAVORITE)
                                }

                                R.id.action_collection -> {
                                    supportFragmentManager.saveBackStack(STACK_COLLECTION)
                                }
                            }
                        }

                        if (!musicSaved) {
                            supportFragmentManager.commit {
                                setReorderingAllowed(true)
                                replace<MusicFragment>(R.id.container)
                                addToBackStack(STACK_MUSIC)
                            }
                            musicSaved = true
                        } else {
                            supportFragmentManager.restoreBackStack(STACK_MUSIC)
                        }
                    }
                    R.id.action_favorite -> {
                        if (mSelectId != -1) {
                            when (mSelectId) {
                                R.id.action_music -> {
                                    supportFragmentManager.saveBackStack(STACK_MUSIC)
                                }

                                R.id.action_collection -> {
                                    supportFragmentManager.saveBackStack(STACK_COLLECTION)
                                }
                            }
                        }

                        if (!favoriteSaved) {
                            supportFragmentManager.commit {
                                setReorderingAllowed(true)
                                replace<FavoriteFragment>(R.id.container)
                                addToBackStack(STACK_FAVORITE)
                            }
                            favoriteSaved = true
                        } else {
                            supportFragmentManager.restoreBackStack(STACK_FAVORITE)
                        }
                    }
                    R.id.action_collection -> {
                        if (mSelectId != -1) {
                            when (mSelectId) {
                                R.id.action_music -> {
                                    supportFragmentManager.saveBackStack(STACK_MUSIC)
                                }

                                R.id.action_favorite -> {
                                    supportFragmentManager.saveBackStack(STACK_FAVORITE)
                                }
                            }
                        }

                        if (!collectSaved) {
                            supportFragmentManager.commit {
                                setReorderingAllowed(true)
                                replace<CollectionFragment>(R.id.container)
                                addToBackStack(STACK_COLLECTION)
                            }
                            collectSaved = true
                        } else {
                            supportFragmentManager.restoreBackStack(STACK_COLLECTION)
                        }
                    }
                }

                mSelectId = it.itemId

                true
            }
            nav.selectedItemId = R.id.action_music
        }
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {

        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (supportFragmentManager.backStackEntryCount == 1) {
                finish()
            }
        }

        return super.onKeyDown(keyCode, event)
    }

    companion object {
        const val STACK_MUSIC = "music"
        const val STACK_FAVORITE = "favorite"
        const val STACK_COLLECTION = "collection"
    }
}

class MusicFragment : Fragment() {

    private lateinit var root: LayoutThingBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        root = LayoutThingBinding.inflate(inflater)
        return root.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        root.thing.load("https://s3.bmp.ovh/imgs/2022/04/28/a7dd490908b29ef0.jpg")
        root.thing.setOnClickListener {
            parentFragmentManager.commit {
                setReorderingAllowed(true)
                replace<MusicDetailFragment>(R.id.container)
                addToBackStack("Music_detail")
            }
        }
    }
}

class MusicDetailFragment : Fragment() {
    private lateinit var root: LayoutThingDetailBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        root = LayoutThingDetailBinding.inflate(inflater)
        return root.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        root.thing.load("https://s3.bmp.ovh/imgs/2022/04/28/a7dd490908b29ef0.jpg")
        root.detail.text =
            "Music is the art of arranging sounds in time through the elements of melody, harmony, rhythm, and timbre.[1][2] It is one of the universal cultural aspects of all human societies. General definitions of music include common elements such as pitch (which governs melody and harmony), rhythm (and its associated concepts tempo, meter, and articulation), dynamics (loudness and softness), and the sonic qualities of timbre and texture (which are sometimes termed the color of a musical sound). Different styles or types of music may emphasize, de-emphasize or omit some of these elements. Music is performed with a vast range of instruments and vocal techniques ranging from singing to rapping; there are solely instrumental pieces, solely vocal pieces (such as songs without instrumental accompaniment) and pieces that combine singing and instruments. The word derives from Greek μουσική, mousiké, '(art) of the Muses'."
    }
}

class FavoriteFragment : Fragment() {

    private lateinit var root: LayoutThingBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        root = LayoutThingBinding.inflate(inflater)
        return root.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        root.thing.load("https://s3.bmp.ovh/imgs/2022/04/28/5b3b1b7a018e2c8e.jpg")
        root.thing.setOnClickListener {
            parentFragmentManager.commit {
                setReorderingAllowed(true)
                replace<FavoriteDetailFragment>(R.id.container)
                addToBackStack("Favorite_detail")
            }
        }
    }
}

class FavoriteDetailFragment : Fragment() {
    private lateinit var root: LayoutThingDetailBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        root = LayoutThingDetailBinding.inflate(inflater)
        return root.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        root.thing.load("https://s3.bmp.ovh/imgs/2022/04/28/5b3b1b7a018e2c8e.jpg")
        root.detail.text =
            "A favourite (British English) or favorite (American English) was the intimate companion of a ruler or other important person. In post-classical and early-modern Europe, among other times and places, the term was used of individuals delegated significant political power by a ruler. It was especially a phenomenon of the 16th and 17th centuries, when government had become too complex for many hereditary rulers with no great interest in or talent for it, and political institutions were still evolving. From 1600 to 1660 there were particular successions of all-powerful minister-favourites in much of Europe, particularly in Spain, England, France and Sweden."
    }
}

class CollectionFragment : Fragment() {

    private lateinit var root: LayoutThingBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        root = LayoutThingBinding.inflate(inflater)
        return root.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        root.thing.load("https://s3.bmp.ovh/imgs/2022/04/28/182757a445a09fd0.jpg")
        root.thing.setOnClickListener {
            parentFragmentManager.commit {
                setReorderingAllowed(true)
                replace<CollectionDetailFragment>(R.id.container)
                addToBackStack("Collection_detail")
            }
        }
    }
}

class CollectionDetailFragment : Fragment() {
    private lateinit var root: LayoutThingDetailBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        root = LayoutThingDetailBinding.inflate(inflater)
        return root.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        root.thing.load("https://s3.bmp.ovh/imgs/2022/04/28/182757a445a09fd0.jpg")
        root.detail.text =
            "The Collection is the county museum and gallery for Lincolnshire in England. It is an amalgamation of the Usher Gallery and the City and County Museum. The museum part of the enterprise is housed in a new, purpose-built building close by the Usher Gallery in the city of Lincoln."
    }
} 

文末

要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值