首页 > 杂记 > 正文

我们通常认为Android开发中的路由管理主要分为两部分,Android原生页面栈和混合开发页面栈。在native原生页面中,使用最多的是四大组件之一的Activity和依托于其的Fragment。在混合开发页面中,通常又分为Activity-H5(WebView),Activity-Weex/React-Native,和Activity-Flutter这几种跨平台的页面交互方式。

1、原生之Activity的页面跳转与管理

1.1 从Activity启动模式入手

在Android开发中,在默认的情况下(Standard 标准启动模式),如果我们多次启动同一个Activity,系统会创建多个实例并把它们一一放入任务栈中。当我们点击返回键进行页面切换时,会将这些Activity实例从任务栈中逐个移除,遵循先进后出的原则。出于多次启动同一个Activity,系统创建多个实例放入任务栈中会耗费内存资源的考虑,Android为Actiivty提供了启动模式,不同的模式会影响Activity返回时的页面跳转行为。

我们知道Activity的启动模式有4种,具体如下:

Standard 标准启动模式。每启动Activity都会创建一个新的实例置于任务栈栈顶。如图当页面返回时,Activity B出栈销毁,会进入当前Activity A任务栈新的栈顶Activity。

Single Top 栈顶复用模式。当需要新建的Activity处于栈顶,则重用该Activity实例,否则新建该Activity并将其置于栈顶。该模式不会对任务栈中存在的Activity实例造成顺序上的影响,当页面返回时,会按照先进后出的顺序跳转进新的栈顶Activity。

Single Task 栈内复用模式。当需要新建的Activity想要的任务栈(通过TaskAffinity指定)不存在,则先创建该任务栈,新建该Activity实例并将其置于栈顶;若该任务栈存在,判断该Activity是否存在于栈中,若存在,则将其之上的Activity实例全部出栈,使其置于栈顶,重用该Activity实例;否则新建该Activity并将其置于栈顶。

该模式可能会对任务栈中存在的Activity实例造成顺序上的影响,若将目标Activity之上的实例全部出栈,当页面返回时,会按照先进后出的顺序跳转进剩余的任务栈实例中。

Single Instance 单例复用模式。新建一个任务栈B并新建该Activity实例并置于栈顶。当页面返回时,会返回并使用打开该Activity之前的任务栈A,按照先进后出的顺序跳转进任务栈A的栈顶Activity。

可以看到,不同的启动模式会影响Activity返回时的页面跳转行为,一些模式下会对任务栈及其内的Activity顺序产生改变,开发过程中需要根据不同场景选择不同模式,同时充分考虑其产生的对返回时页面跳转行为的影响。

在Activity页面之间的跳转管理中,对于这些Activity的创建、回退、跳转、复用等,Android提供了完备的AMS(ActivityManagerService)管理机制。

ActivityManagerService被用来管理Android四大组件,在对于Activity的管理中,主要体现在任务栈上。主要的任务栈管理模型如上图,可以从中看出ctivityRecord、TaskRecord和ActivityStack三者间的管理、包含关系。

上图是三者的UML类图。根据图中的主要关系与类方法,我们更加容易理解其主要职责:ActivityRecord 是应用层Activity组件在AMS的代表,每个启动的Activity都有一个与之对应的ActivityRecord实例。TaskRecord 是任务栈(也叫做返回栈),遵循先进后出的栈原则,栈内用来记录APP跳转过程中的ActivityRecord集合。ActivityStack 是用于管理任务栈TaskRecord而维护的集合,一般情况下栈内管理着位于前台的TaskRecord和数个后台TaskRecord。

可以看到,此三者贯穿AMS管理Activity的整个逻辑,任务栈管理模型为我们提供了灵活的Activity的创建、回退、跳转、复用等页面栈操作的实现。

1.2 Activity间的页面桥梁-Intent

当我们需要进行不同Activity之间的跳转时,需要用到启动Activity的桥梁:显式Intent & 隐式Intent。下面以两个Activity之间的跳转为例:

1
2
3
4
5
6
7
8
9
10
11
// 显示:使用构造函数传入Class对象
 Intent intent = new Intent(this, SecondActivity.class); 
 startActivity(intent);
 
// 隐式:通过Category、Action、Data设置
 Intent intent = new Intent(); 
 intent.addCategory(Intent.CATEGORY_DEFAULT); 
 intent.setAction("com.test.action"); 
 startActivity(intent);
 
 new Intent(Intent.ACTION_VIEW, Uri.parse(Url))

可以看到,显式调用需要明确指定被启动对象的组件信息,包括包名和类名。一般是在同一个应用程序内部使用的。隐式调用通过Intent Filter来实现,Android系统会根据在隐式意图中设置的动作(action)、类别(category)、Data(URI和数据类型)找到合适的组件来处理这个意图。一般用于不同的应用程序之间。

从启动对象来看,显式Intent通过明确启动对象的组件信息使得有固定的接收方,隐式Intent通过Intent Filter过滤匹配合适的启动对象;从使用场景上看,在同一项目下的页面跳转可以使用显式Intent,跨项目的页面跳转官方推荐使用隐式Intent;对于同一个Intent既有显式又有隐式调用,则以显式调用为主。

2、原生之Fragment的页面跳转与管理

2.1 Fragment与Activity间的页面跳转

Fragment 的发明是为了灵活的布局和复用布局,比如在屏幕较大的 Pad 上,可以一个 Activity 左边呈现 A,右边呈现 B。下图是其生命周期:

考虑到Fragment与Activity之间的页面跳转,无非在于以下四种:

1. activity1_fragment1 -> fragment2

2. activity1_fragment1 -> activity2

3. activity1_fragment1 -> activity2_fragment2

4. activity1 -> activity2_fragment2

可以看到包含单Activity多Fragment跳转、多Activity多Fragment跳转,以及Activity与Fragment相互跳转。由于Fragment 没有继承 View,是被添加到 Activity 的某个 ViewGroup 中,并且具有完整的生命周期,其生命周期受宿主 Activity 生命周期影响。可以简单地理解,Fragment 是具有类似于 Activity 生命周期和返回栈的视图容器。所以对于Fragment与Activity之间进行页面跳转时,只需要理清其生命周期的对应关系和依赖关系,处于复杂情况下仍然万变不离其宗。

2.2 Navigation路由框架

Navigation是一个页面路由导航框架,简化了单Activity多Fragment之间的跳转,本质上是封装的一套跳转逻辑,我们在使用时只要将所有的需要跳转的Fragment全部放到布局里面后通过简单的调用即可。

Navigation和Flutter的路由有一定的相似性,这里是将frament作为跳转点,在开发时,可以清晰地看到每个界面的跳转路径。同时,Navigation 组件提供管理所有返回堆栈的功能,堆栈的顶部为当前屏幕,堆栈中记录着访问的目的地顺序,堆栈的底部是应用的起始地,同时提供了相关更改返回栈的方法,使得我们可以灵活在不同Fragment之间实现页面跳转。

其实现页面栈跳转的原理主要是:

3、混合开发的页面跳转与管理

跨平台层作为前端与Native的中间混合层,主要目标是为Hybrid/Weex/Fultter/RN(或者其他跨平台方案)提供更好的服务能力或者互动能力(比如获取地理位置信息或者设置容器导航标题与按钮等等)。

Web技术:主要依赖于WebView的技术,功能支持受限(如在需要频繁拖拽且显示动画的场景下流畅度下降),比如PhoneGap、Cordova、小程序。

原生渲染:使用JavaScript做为编程语言,经过中间层转化为原生控件来渲染UI界面,比如React Native、Weex。

自渲染技术:自行实现一套渲染框架,可经过调用skia等方式完成自渲染,而不依赖于原生控件,比如Flutter、Unity。

3.1 Activity-H5(webview)

我们知道在Android原生控件与WebView的混合开发中,Activity通过在布局内置WebView控件来加载目标H5;WebView通过显式/隐式调用Intent实现跳转到native页面,WebView本身可以通过常见的工具类如WebSettings、WebViewClient、WebChromeClient实现配置、加载与请求处理。

需要关注的是,当由Activity跳转进入WebView,伴随着从Activity任务栈进入H5任务栈,如果我们希望接下来在H5内做页面前进或后退页面跳转,如按下返回键后不返回Activity任务栈,而是实现WebView任务栈的后退,则需要根据WebView提供的一些判断网页是否可以前进后退的api,拦截对于返回键的监听以实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
// WebView提供api
Webview.canGoBack //判断是否可以后退
Webview.goBack //后退网页
Webview.canGoForward //判断是否可以前进
Webview.goForward //前进网页
 
// 拦截返回键,实现WebView返回的页面跳转
public boolean onKeyDown(int keyCode, KeyEvent event) {
  if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack) {
    mWebView.goBack;
    return true;
  }
  return super.onKeyDown(keyCode, event);
}

3.2 Activity-Weex、React-Native

Weex和React-Native经过中间层转化为原生控件来渲染UI界面(通过一套规则,映射到原生控件)。Activity-Weex之间的页面跳转和Activity-React Native原理上是类似的。

我们知道Android的页面跳转是通过Intent、RN是通过路由,而两者直接的页面互相跳转是需要原生借助JS暴露接口给RN来实现。在Android原生页面与RN之间的页面管理中,主要分为三类:

1.以Intent实现的原生跳转到RN,此时页面栈交由Activity任务栈管理;

2.以路由Navigation实现的RN跳转到RN,此时页面栈交由路由导航中的堆栈管理;

3.以及RN跳转到原生,主要包含三步:定义Module类,继承ReactContextBaseJavaModule、定义Package类,实现接口ReactPackage、定义Application类,继承android的Application,并实现ReactApplication接口,其实是在原生端创建Module类通过桥接的方式导出到JS端供JS代码调用原生端代码来实现的。

由此可知,对于更加复杂的如RN-RN-原生-RN-原生-原生页面间跳转等情况,都可拆分为由任务栈管理、由Navigation路由管理、以及由桥接方式实现路由管理。

3.3 Activity-Flutter

简单地来说,Flutter是使用跨平台的图形渲染引擎在view上画控件,Activity-Flutter之间的页面跳转和Activity-React Native原理大体上是类似的。

我们知道Android的页面跳转是通过Intent、Flutter是通过Widget进行路由管理,在Android原生页面与Flutter之间的页面管理如图所示。更多的关于Flutter Widget、Channel的内容可以在后续系列文章的该部分进行查看。

由此可知,对于更加复杂的如Flutter-Flutter-原生-Flutter-原生-原生页面间跳转等情况,同样可拆分为由任务栈管理、由Widget路由管理、以及由Channel方式实现路由管理。

4、小结

通过上述对于Android开发中的路由管理的介绍,可以看出Android原生页面栈和混合开发页面栈的相关实现在实际应用中极具灵活性。

在原生页面中,通过理解AMS,重点关注Activity的启动模式、Fragment的Navigation路由框架以及两者之间涉及到的页面栈跳转方式;在混合开发页面中,从native方-跨平台方-双方交互这三个角度简化路由管理,分别梳理了native-H5(WebView)、native-Weex/React-Native、native-Flutter这几种常见的跨平台的页面交互方式,使得在更加复杂的页面管理下仍可万变不离其宗。

至此,我们了解到了Android端是如何去实现路由管理的,那么,就请期待我们下一篇文章《大前端开发中的路由管理之四:iOS篇》吧,下篇文章将为大家揭秘iOS端是如何去做路由管理的。

参考资料

[1] Activity启动模式与页面管理

https://www.jianshu.com/p/32938446e4e0

[2] Fragment页面管理

https://www.jianshu.com/p/fef256de6364

[3] 从AMS理解Android任务栈

https://www.jianshu.com/p/fdd80e9a0bdc

[4] Navigation路由框架

https://www.jianshu.com/p/95796d895cfc

[5] 混合开发发展史

https://juejin.cn/post/6856414014948900877

[6] Flutter原生混合开发

https://juejin.cn/post/6844903929625444359#heading-12

版权声明:部分文章、图片等内容为用户发布或互联网整理而来,仅供学习参考。如有侵犯您的版权,请联系我们,将立刻删除。