温馨提示:如果这是第一次接触《重学安卓》,可借助 这份在 GitBook 上维护的 “导读” 来快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。
截至目前,专栏已对 体系化文章 做了 1270 余次修订,数十位群友告诉我 受专栏的启发 他们也开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创的开源库 和 提供内推机会,订阅后可随时进群交流。
前言
很高兴见到你!
上周我在新项目中用上了 Jetpack Navigation,对于它的 声明式设计 十分喜欢。 😉
没想到,就在用得正爽的时候,发现它在 navigate 方法中的实现,居然是通过 replace 完成页面跳转的,除此之外,就没有 add hide 的方式。这导致了页面返回时,每次都要重绘,大概率造成转场时的卡顿。
在 google sample 的 issue 中发现一年前有人提过这个问题,但官方并没有解释这么设计的缘由。
(非常感谢读者 Darren 于 2019.11.14 与我分享了 他在使用过程中发现的 Navigation 设计为 replace 的缘由,将于文末一节中介绍,并就 replace 的情况 提供重绘的解决方案)
于是我转而去咨询今天要介绍的一位嘉宾,看看他是怎么看的这个设计,以及如果想通过 Navigation add hide,除了重写代码,还有没有别的招。
没想到,当天下午提的问题,当天晚上就收到了详尽的回复。(不要慌,关于此嘉宾以及解决方案,文末给出)😉
考虑到我在公开场合发文一贯秉持的原则是:为读者 首先明确状况、减少困扰、建立感性认识。
因而,我绝不会一上来就解释针对 replace 的问题我们该怎么做,而是从 Navigation 的存在缘由、设计依据、职责边界 开始讲,这样才能帮助更多的人首先理解状况:
为什么要存在 Navigation?
Navigation 的存在是为了解决什么问题?
之前究竟是遇到了什么问题?
Navigation 引入后又发现了什么新问题?
……

文章目录一览
Navigation 的目标主要有 3 个
在《重学安卓》的前几期中,我们分别在
《重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈》
《重学安卓:Intent 就是你的择偶标准啊!》
《重学安卓:我的碎片很听话,你的 Fragment 有自己的想法》
中深入介绍了 Activity 和 Fragment 由于生存环境、沟通目标的差异,而在 “页面管理 和 路由跳转机制 的 设计依据、职责边界、乃至相互间关系” 上 存在的区别。
相信阅读完这几篇的朋友,对 Activity 和 Fragment 不同的 页面管理、路由跳转 的知识点已形成深刻的印象。😉
而我们今天要介绍的 Navigation,正是基于上述分裂的环境,为解决 “应用内导航” 的问题而存在的。
它的目标主要有 3 个:
1.通过声明式编程,来确保 “应用内导航” 的一致性。
2.通过可视化编程,来直观地反映页面的路由关系。
3.通过抽象,来整合 Activity 和 Fragment 的路由跳转代码。
如果光是阅读了以上三点,你还是不太理解的话,那接下来我就分别介绍 99% 网文都不曾介绍的真实状况,来方便你迅速建立起感性的认识。😉
Navigation 问世前的混沌世界
在 Navigation 出现之前,想必大家用得最多的就是 YoKey 大神的 Fragmentation。
鉴于早期 Fragment 的坑防不胜防,而大家平时工作又没时间去确认状况,因而多是急忙地用上 Fragmentation “保平安”。
考虑到现如今 Fragment 的 bug 早已被修复,因而从适配 AndroidX 开始,我便转而采用原生的办法实现路由管理。例如:

那这样造成什么问题呢?
例如开发一款音乐播放器,在首页和专辑列表页 等多个源 Fragment 中 需要跳转到该页面,那么在每个源页面中 我都需要动态注入同样的 转场动画 等 样板代码,那么日后随着源页面的激增,当源页面 A 中改了转场动画,源页面 B 中忘了改转场动画,就会造成不一致的问题。

如果你对 “一致性” 的概念不太理解的话,那么请回顾一下大学时期 数据库课程中提到的 “存储过程”,它就是 典型的 “一致性” 封装:将一套连贯的 SQL 序列封装在一个 “API” 中,外部人员只需调用这个 “API”,而无需知道内部具体发生了什么。
这避免了程序员 A 修改了某 SQL 语句,而程序员 B 没跟上。并且,“API” 的管理人只需一处修改,就能做到处处生效。
此外,一个项目的 Fragment 可能会很多(例如在上周的新项目中,我新建了多达 25 个页面),日后接手项目的同事,在缺乏文档的情况下,很难第一时间掌握项目状况、并定位到问题所在页面:
顺藤摸瓜、通过 manifest 和 布局文件找到程序入口和对应的 Fragment,并不总是最优的办法。
再者,如上一节提到的,Activity 和 Fragment 的生存环境不同,Activity 因为是组件,可被允许和其他 App 的组件通信,而在设计之初就考虑到 是面向跨进程通信的组件化管理,那么无论是任务栈、返回栈管理,还是路由跳转的设计,都和 Fragment 有着天壤之别。
因而,就应用内的导航来说,如果你要允许 Activity 也来专职视图控制器,那么它同样也会遇到上述 “样板代码” 的问题。并且,将 “应用内导航” 的工作托管给两套 API 显然是不方便的。
Navigation 正是为解决上述三个问题而存在。
Navigation 为什么能解决这三个问题?
在 《你用不惯 RxJava,只因缺了这把钥匙》 中我们介绍到,RxJava 操作符同 SQL 一样,本质上是声明式编程,也即你 只需告诉后台要做什么,而无需告诉后台怎么做。
“怎么做” 的逻辑已经在后台统一封装好,你只需遵从协议(编程语言的本质就是一种协议,操作符是一种协议,SQL 是一种协议,Navigation Graph 也是种协议)来定义你的声明、并在恰当的地方调用即可,后台会自动根据声明来执行相应的代码。
正因为是声明式编程,所以你可以用统一的 XML 声明,去匹配不同的 Java 实现。所以这让 Activity 和 Fragment 的路由管理的抽象成为了可能。
并且,正因为是声明式编程,所以更方便要求你填写特定的属性,以便让后台支持如 “可视化编程” 这样功能的展现。
那 Navigation 具体是依赖什么机制运作的呢?
首先,既然要声明式编程,那么第一步要做的当然是
1.定义声明式编程协议
我们不妨站在源码设计师的角度来想一想,路由跳转从抽象意义上讲,都包含哪些必要的元素呢?
—— 源地址,目标地址,携带参数,转场动画,启动模式。
主要是上述这 5 个部分,因而我们先定义出
fragment、action、argument 这三种元素。
其中,fragment 元素对应着一个源 Fragment,
需要属性 name 来指明源地址;
需要属性 id 来帮助其他 Fragment 的 action 元素链接到自己、从而帮助后台找到自己。
同时,每个 fragment 需要包含 action 元素 和 argument 元素,来分别描述 fragment 可以执行跳转的目标,以及携带的参数。
在 action 元素中,我们
需要属性 destination 来指明前往的目标 id;
需要 popUpTo 来指明需要跨级返回的源 id;
需要 诸如 launchSingleTop 的属性来表明页面的启动模式;
需要 anim 属性来声明转场动画;
需要属性 id 来帮助后台发现和执行这个 action。
在 argument 元素中,我们
需要 name 属性来描述参数名;
需要 argType 属性来描述参数类型;
需要 defaultValue 属性来描述默认值,当没有传参的时候。

看,这样梳理一下,是不是很简单呢。
同时,不要忘了,为了支持 “可视化编程”,我们还需在 fragment 元素中定义 tools:layout 属性,这样我们就可以在 Android Studio 中预览这些 fragment 的关系。

navigation-design-graph-top-level.png
2.抽象和封装控制器代码
就像页面的布局文件,在 Activity/Fragment 启动时,被统一的控制器遍历和加载,Navigation 也是类似的道理。
为了能够基于声明式编程,我们需要完成的工作如下:
1.为了管理所有的目标,我们需要 装载声明、创建目标、导航管理。
2.为了在不同的作用域下管理目标,我们还需要 管理作用域。
3.为了实现 “无差别” 的路由导航,我们需要 抽象导航者,以便整合 Activity 和 Fragment 的导航。
温馨提示:如果这是第一次接触《重学安卓》,可借助 这份在 GitBook 上维护的 “导读” 来快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。
截至目前,专栏已对 体系化文章 做了 1270 余次修订,数十位群友告诉我 受专栏的启发 他们也开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创的开源库 和 提供内推机会,订阅后可随时进群交流。
前言
很高兴见到你!
上周我在新项目中用上了 Jetpack Navigation,对于它的 声明式设计 十分喜欢。 😉
没想到,就在用得正爽的时候,发现它在 navigate 方法中的实现,居然是通过 replace 完成页面跳转的,除此之外,就没有 add hide 的方式。这导致了页面返回时,每次都要重绘,大概率造成转场时的卡顿。
在 google sample 的 issue 中发现一年前有人提过这个问题,但官方并没有解释这么设计的缘由。
(非常感谢读者 Darren 于 2019.11.14 与我分享了 他在使用过程中发现的 Navigation 设计为 replace 的缘由,将于文末一节中介绍,并就 replace 的情况 提供重绘的解决方案)
于是我转而去咨询今天要介绍的一位嘉宾,看看他是怎么看的这个设计,以及如果想通过 Navigation add hide,除了重写代码,还有没有别的招。
没想到,当天下午提的问题,当天晚上就收到了详尽的回复。(不要慌,关于此嘉宾以及解决方案,文末给出)😉
考虑到我在公开场合发文一贯秉持的原则是:为读者 首先明确状况、减少困扰、建立感性认识。
因而,我绝不会一上来就解释针对 replace 的问题我们该怎么做,而是从 Navigation 的存在缘由、设计依据、职责边界 开始讲,这样才能帮助更多的人首先理解状况:
为什么要存在 Navigation?
Navigation 的存在是为了解决什么问题?
之前究竟是遇到了什么问题?
Navigation 引入后又发现了什么新问题?
……

文章目录一览
Navigation 的目标主要有 3 个
在《重学安卓》的前几期中,我们分别在
《重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈》
《重学安卓:Intent 就是你的择偶标准啊!》
《重学安卓:我的碎片很听话,你的 Fragment 有自己的想法》
中深入介绍了 Activity 和 Fragment 由于生存环境、沟通目标的差异,而在 “页面管理 和 路由跳转机制 的 设计依据、职责边界、乃至相互间关系” 上 存在的区别。
相信阅读完这几篇的朋友,对 Activity 和 Fragment 不同的 页面管理、路由跳转 的知识点已形成深刻的印象。😉
而我们今天要介绍的 Navigation,正是基于上述分裂的环境,为解决 “应用内导航” 的问题而存在的。
它的目标主要有 3 个:
1.通过声明式编程,来确保 “应用内导航” 的一致性。
2.通过可视化编程,来直观地反映页面的路由关系。
3.通过抽象,来整合 Activity 和 Fragment 的路由跳转代码。
如果光是阅读了以上三点,你还是不太理解的话,那接下来我就分别介绍 99% 网文都不曾介绍的真实状况,来方便你迅速建立起感性的认识。😉
Navigation 问世前的混沌世界
在 Navigation 出现之前,想必大家用得最多的就是 YoKey 大神的 Fragmentation。
鉴于早期 Fragment 的坑防不胜防,而大家平时工作又没时间去确认状况,因而多是急忙地用上 Fragmentation “保平安”。
考虑到现如今 Fragment 的 bug 早已被修复,因而从适配 AndroidX 开始,我便转而采用原生的办法实现路由管理。例如:

那这样造成什么问题呢?
例如开发一款音乐播放器,在首页和专辑列表页 等多个源 Fragment 中 需要跳转到该页面,那么在每个源页面中 我都需要动态注入同样的 转场动画 等 样板代码,那么日后随着源页面的激增,当源页面 A 中改了转场动画,源页面 B 中忘了改转场动画,就会造成不一致的问题。

如果你对 “一致性” 的概念不太理解的话,那么请回顾一下大学时期 数据库课程中提到的 “存储过程”,它就是 典型的 “一致性” 封装:将一套连贯的 SQL 序列封装在一个 “API” 中,外部人员只需调用这个 “API”,而无需知道内部具体发生了什么。
这避免了程序员 A 修改了某 SQL 语句,而程序员 B 没跟上。并且,“API” 的管理人只需一处修改,就能做到处处生效。
此外,一个项目的 Fragment 可能会很多(例如在上周的新项目中,我新建了多达 25 个页面),日后接手项目的同事,在缺乏文档的情况下,很难第一时间掌握项目状况、并定位到问题所在页面:
顺藤摸瓜、通过 manifest 和 布局文件找到程序入口和对应的 Fragment,并不总是最优的办法。
再者,如上一节提到的,Activity 和 Fragment 的生存环境不同,Activity 因为是组件,可被允许和其他 App 的组件通信,而在设计之初就考虑到 是面向跨进程通信的组件化管理,那么无论是任务栈、返回栈管理,还是路由跳转的设计,都和 Fragment 有着天壤之别。
因而,就应用内的导航来说,如果你要允许 Activity 也来专职视图控制器,那么它同样也会遇到上述 “样板代码” 的问题。并且,将 “应用内导航” 的工作托管给两套 API 显然是不方便的。
Navigation 正是为解决上述三个问题而存在。
Navigation 为什么能解决这三个问题?
在 《你用不惯 RxJava,只因缺了这把钥匙》 中我们介绍到,RxJava 操作符同 SQL 一样,本质上是声明式编程,也即你 只需告诉后台要做什么,而无需告诉后台怎么做。
“怎么做” 的逻辑已经在后台统一封装好,你只需遵从协议(编程语言的本质就是一种协议,操作符是一种协议,SQL 是一种协议,Navigation Graph 也是种协议)来定义你的声明、并在恰当的地方调用即可,后台会自动根据声明来执行相应的代码。
正因为是声明式编程,所以你可以用统一的 XML 声明,去匹配不同的 Java 实现。所以这让 Activity 和 Fragment 的路由管理的抽象成为了可能。
并且,正因为是声明式编程,所以更方便要求你填写特定的属性,以便让后台支持如 “可视化编程” 这样功能的展现。
那 Navigation 具体是依赖什么机制运作的呢?
首先,既然要声明式编程,那么第一步要做的当然是
1.定义声明式编程协议
我们不妨站在源码设计师的角度来想一想,路由跳转从抽象意义上讲,都包含哪些必要的元素呢?
—— 源地址,目标地址,携带参数,转场动画,启动模式。
主要是上述这 5 个部分,因而我们先定义出
fragment、action、argument 这三种元素。
其中,fragment 元素对应着一个源 Fragment,
需要属性 name 来指明源地址;
需要属性 id 来帮助其他 Fragment 的 action 元素链接到自己、从而帮助后台找到自己。
同时,每个 fragment 需要包含 action 元素 和 argument 元素,来分别描述 fragment 可以执行跳转的目标,以及携带的参数。
在 action 元素中,我们
需要属性 destination 来指明前往的目标 id;
需要 popUpTo 来指明需要跨级返回的源 id;
需要 诸如 launchSingleTop 的属性来表明页面的启动模式;
需要 anim 属性来声明转场动画;
需要属性 id 来帮助后台发现和执行这个 action。
在 argument 元素中,我们
需要 name 属性来描述参数名;
需要 argType 属性来描述参数类型;
需要 defaultValue 属性来描述默认值,当没有传参的时候。

看,这样梳理一下,是不是很简单呢。
同时,不要忘了,为了支持 “可视化编程”,我们还需在 fragment 元素中定义 tools:layout 属性,这样我们就可以在 Android Studio 中预览这些 fragment 的关系。

navigation-design-graph-top-level.png
2.抽象和封装控制器代码
就像页面的布局文件,在 Activity/Fragment 启动时,被统一的控制器遍历和加载,Navigation 也是类似的道理。
为了能够基于声明式编程,我们需要完成的工作如下:
1.为了管理所有的目标,我们需要 装载声明、创建目标、导航管理。
2.为了在不同的作用域下管理目标,我们还需要 管理作用域。
3.为了实现 “无差别” 的路由导航,我们需要 抽象导航者,以便整合 Activity 和 Fragment 的导航。
温馨提示:如果这是第一次接触《重学安卓》,可借助 这份在 GitBook 上维护的 “导读” 来快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。
截至目前,专栏已对 体系化文章 做了 1270 余次修订,数十位群友告诉我 受专栏的启发 他们也开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创的开源库 和 提供内推机会,订阅后可随时进群交流。
前言
很高兴见到你!
上周我在新项目中用上了 Jetpack Navigation,对于它的 声明式设计 十分喜欢。 😉
没想到,就在用得正爽的时候,发现它在 navigate 方法中的实现,居然是通过 replace 完成页面跳转的,除此之外,就没有 add hide 的方式。这导致了页面返回时,每次都要重绘,大概率造成转场时的卡顿。
在 google sample 的 issue 中发现一年前有人提过这个问题,但官方并没有解释这么设计的缘由。
(非常感谢读者 Darren 于 2019.11.14 与我分享了 他在使用过程中发现的 Navigation 设计为 replace 的缘由,将于文末一节中介绍,并就 replace 的情况 提供重绘的解决方案)
于是我转而去咨询今天要介绍的一位嘉宾,看看他是怎么看的这个设计,以及如果想通过 Navigation add hide,除了重写代码,还有没有别的招。
没想到,当天下午提的问题,当天晚上就收到了详尽的回复。(不要慌,关于此嘉宾以及解决方案,文末给出)😉
考虑到我在公开场合发文一贯秉持的原则是:为读者 首先明确状况、减少困扰、建立感性认识。
因而,我绝不会一上来就解释针对 replace 的问题我们该怎么做,而是从 Navigation 的存在缘由、设计依据、职责边界 开始讲,这样才能帮助更多的人首先理解状况:
为什么要存在 Navigation?
Navigation 的存在是为了解决什么问题?
之前究竟是遇到了什么问题?
Navigation 引入后又发现了什么新问题?
……

文章目录一览
Navigation 的目标主要有 3 个
在《重学安卓》的前几期中,我们分别在
《重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈》
《重学安卓:Intent 就是你的择偶标准啊!》
《重学安卓:我的碎片很听话,你的 Fragment 有自己的想法》
中深入介绍了 Activity 和 Fragment 由于生存环境、沟通目标的差异,而在 “页面管理 和 路由跳转机制 的 设计依据、职责边界、乃至相互间关系” 上 存在的区别。
相信阅读完这几篇的朋友,对 Activity 和 Fragment 不同的 页面管理、路由跳转 的知识点已形成深刻的印象。😉
而我们今天要介绍的 Navigation,正是基于上述分裂的环境,为解决 “应用内导航” 的问题而存在的。
它的目标主要有 3 个:
1.通过声明式编程,来确保 “应用内导航” 的一致性。
2.通过可视化编程,来直观地反映页面的路由关系。
3.通过抽象,来整合 Activity 和 Fragment 的路由跳转代码。
如果光是阅读了以上三点,你还是不太理解的话,那接下来我就分别介绍 99% 网文都不曾介绍的真实状况,来方便你迅速建立起感性的认识。😉
Navigation 问世前的混沌世界
在 Navigation 出现之前,想必大家用得最多的就是 YoKey 大神的 Fragmentation。
鉴于早期 Fragment 的坑防不胜防,而大家平时工作又没时间去确认状况,因而多是急忙地用上 Fragmentation “保平安”。
考虑到现如今 Fragment 的 bug 早已被修复,因而从适配 AndroidX 开始,我便转而采用原生的办法实现路由管理。例如:

那这样造成什么问题呢?
例如开发一款音乐播放器,在首页和专辑列表页 等多个源 Fragment 中 需要跳转到该页面,那么在每个源页面中 我都需要动态注入同样的 转场动画 等 样板代码,那么日后随着源页面的激增,当源页面 A 中改了转场动画,源页面 B 中忘了改转场动画,就会造成不一致的问题。

如果你对 “一致性” 的概念不太理解的话,那么请回顾一下大学时期 数据库课程中提到的 “存储过程”,它就是 典型的 “一致性” 封装:将一套连贯的 SQL 序列封装在一个 “API” 中,外部人员只需调用这个 “API”,而无需知道内部具体发生了什么。
这避免了程序员 A 修改了某 SQL 语句,而程序员 B 没跟上。并且,“API” 的管理人只需一处修改,就能做到处处生效。
此外,一个项目的 Fragment 可能会很多(例如在上周的新项目中,我新建了多达 25 个页面),日后接手项目的同事,在缺乏文档的情况下,很难第一时间掌握项目状况、并定位到问题所在页面:
顺藤摸瓜、通过 manifest 和 布局文件找到程序入口和对应的 Fragment,并不总是最优的办法。
再者,如上一节提到的,Activity 和 Fragment 的生存环境不同,Activity 因为是组件,可被允许和其他 App 的组件通信,而在设计之初就考虑到 是面向跨进程通信的组件化管理,那么无论是任务栈、返回栈管理,还是路由跳转的设计,都和 Fragment 有着天壤之别。
因而,就应用内的导航来说,如果你要允许 Activity 也来专职视图控制器,那么它同样也会遇到上述 “样板代码” 的问题。并且,将 “应用内导航” 的工作托管给两套 API 显然是不方便的。
Navigation 正是为解决上述三个问题而存在。
Navigation 为什么能解决这三个问题?
在 《你用不惯 RxJava,只因缺了这把钥匙》 中我们介绍到,RxJava 操作符同 SQL 一样,本质上是声明式编程,也即你 只需告诉后台要做什么,而无需告诉后台怎么做。
“怎么做” 的逻辑已经在后台统一封装好,你只需遵从协议(编程语言的本质就是一种协议,操作符是一种协议,SQL 是一种协议,Navigation Graph 也是种协议)来定义你的声明、并在恰当的地方调用即可,后台会自动根据声明来执行相应的代码。
正因为是声明式编程,所以你可以用统一的 XML 声明,去匹配不同的 Java 实现。所以这让 Activity 和 Fragment 的路由管理的抽象成为了可能。
并且,正因为是声明式编程,所以更方便要求你填写特定的属性,以便让后台支持如 “可视化编程” 这样功能的展现。
那 Navigation 具体是依赖什么机制运作的呢?
首先,既然要声明式编程,那么第一步要做的当然是
1.定义声明式编程协议
我们不妨站在源码设计师的角度来想一想,路由跳转从抽象意义上讲,都包含哪些必要的元素呢?
—— 源地址,目标地址,携带参数,转场动画,启动模式。
主要是上述这 5 个部分,因而我们先定义出
fragment、action、argument 这三种元素。
其中,fragment 元素对应着一个源 Fragment,
需要属性 name 来指明源地址;
需要属性 id 来帮助其他 Fragment 的 action 元素链接到自己、从而帮助后台找到自己。
同时,每个 fragment 需要包含 action 元素 和 argument 元素,来分别描述 fragment 可以执行跳转的目标,以及携带的参数。
在 action 元素中,我们
需要属性 destination 来指明前往的目标 id;
需要 popUpTo 来指明需要跨级返回的源 id;
需要 诸如 launchSingleTop 的属性来表明页面的启动模式;
需要 anim 属性来声明转场动画;
需要属性 id 来帮助后台发现和执行这个 action。
在 argument 元素中,我们
需要 name 属性来描述参数名;
需要 argType 属性来描述参数类型;
需要 defaultValue 属性来描述默认值,当没有传参的时候。

看,这样梳理一下,是不是很简单呢。
同时,不要忘了,为了支持 “可视化编程”,我们还需在 fragment 元素中定义 tools:layout 属性,这样我们就可以在 Android Studio 中预览这些 fragment 的关系。

navigation-design-graph-top-level.png
2.抽象和封装控制器代码
就像页面的布局文件,在 Activity/Fragment 启动时,被统一的控制器遍历和加载,Navigation 也是类似的道理。
为了能够基于声明式编程,我们需要完成的工作如下:
1.为了管理所有的目标,我们需要 装载声明、创建目标、导航管理。
2.为了在不同的作用域下管理目标,我们还需要 管理作用域。
3.为了实现 “无差别” 的路由导航,我们需要 抽象导航者,以便整合 Activity 和 Fragment 的导航。