在组件化项目中使用Navigation
Navigation组件目前并不能完美的支持组件化项目,主要问题是在module中声明的graph中destination不能直接被App引用到,在运行时会找不到对应的destination. 主要navigation资源的原因,目前设计中Navigation graph是独立的,graph中声明的destination是无法共享的。
虽然Navigation graph中可以include其他的navigation graph, 但是include之后的graph还是无法共享声明的destination。
官方近期更新了Navigation组件,支持了Dynamic module,新增了一个<include-dynamic/>实现,不支持deepLink. 由于国内使用不了该功能,就不再描述了。
其他实现方式
- 只有一个navigation graph
将所有模块中的fragment都声明在App的一个Navigation graph内,这样是可行的,但是这种方式解耦违反组件化的逻辑,而且只有一个navigation graph,所有destination的改动都需要修改这个graph,维护成本高。
- 通过include graph实现跳转
App中的Nav graph include其他模块的Nav graph,然后通过action跳转到下个Nav graph, 如:
<action
android:id="@+id/action_to_next_fragment"
app:destination="@navigation/nav_graph2" />
调用这个action之后是可以跳转到下个Nav graph中声明的app:startDestination对应的fragment. 如果下个nav_graph没有声明startDestination是不行的。
这个方案也有一个很大的缺陷,就是不能直接Navigation到include的graph中的非startDestination fragment. 只能在graph内部进行跳转。而且声明的全局action都跳转不过去。
-
deepLink
使用deepLink可以解决这个问题,跟方案2前面的步骤一样。然后需要在模块中对外暴露的fragment加
<deepLink />, 这样就可以直接navigate过去。 这个方案目前是最可行的方案,但是感觉deepLink被滥用,因为deepLink的真正作用是通过外部跳转进入。对于比较复杂的项目,可能好多fragment都需要添加deepLink。而且deepLink的传参数格式需要完全匹配,容易出错。 -
自动合并Nav graph
可以像官方处理AndroidManifest.xml一样,在编译时将所有module中的nav_graph文件中声明的destination都合并到app中的nav_graph文件中,理论上可以解决该问题。但是这个方案有也有很多要考虑的问题
存在的问题:
- AndroidManifest自动生成的文件,不是用户创建的,而且在其他用户生成的layout文件中有依赖,如果在编译前直接修改的话就不太优雅。合并只能合并到app的nav graph,每次编译都需要检查是否合并,合并之后的文件处理不太优雅。
若是直接合并到app的nav graph中,则需要一个备份文件,在编译完成之后是需要还原回去的,这个操作本身就不太合理。正常的操作将所有nav graph文件生成为一个新的xml文件,生成到build/generated/文件下,然后修改布局中app:navGraph中的对应的依赖,由于这些布局文件不是生成的,都是开发者自己创建的,所以不太优雅。而且需要寻找声明app:navGraph的地方,编译前修改,编译完成之后需要还原。
还有一种方案就是修改编译之后的resouce.arsc文件,这个操作比较复杂,需要解ResourceTable,找到对应的资源文件,修改生成新的饿resouce.arsc替换就行了。这个方案比较复杂,而且兼容性问题比较多,需要考虑Android系统和gradle,Android编译API的兼容性问题,开发和维护成本都比较高。
这个方案应该是最合理的方案,官方后面可能会解决这个问题。
Navigation组件与Router
在组件化项目中一般都会使用Router来导航,由于之前的Router方案都是针对Activity的,之前的fragment添加,替换,移除等操作严重依赖Activity, 所以使用Router直接跳转到对应的fragment是比较麻烦的。使用navigation组件之后就可以比较简单的实现了。
以前Router绑定的是URI和activity的class, 一般都是通过注解自动绑定。现在需要定URI, fragment或action或deepLink. 如果deepLink格式统一都不需要绑定,直接使用即可。注解需要做调整。
例如:绑定URI和Fragment, 之前的绑定的注解是这样的:
@Route(path = "/test/list")
class TestActivity : Activity {
...
}
因为navigation跳转到Fragment并不需要Fragment的class,需要的是在nav graph声明的id. 所以需要注解添加参数来绑定,可以改为:
@Route(path = "/test/list", resId = R.id.testfragment)
class TestFragment : Fragment {
...
}
或者是deepLink, 如果path与deepLink的URI一直都不用绑定
@Route(path = "/test/list", deepLink = "app://test/list")
class TestFragment : Fragment {
...
}
action不太一样,action针对的是动作,所以不应该将包含action的注解声明在Fragment上.可以声明在方法上,如:
object NaviControllerHelper{
@RouteAction(path = "/test/list")
fun navigateToDetail(navController: NavController){
navController.navigate(R.id.action_to_detail)
}
}
然后通过注解获取到方法名称,反射调用这个方法即可。
网友评论