Android基础之--Navigation + ARouter

Navigation是一套用于实现应用内部页面跳转和参数传递的组件,旨在简化Android应用的导航逻辑并推荐使用单Activity架构。

Navigation组件主要由3个部分组成:

  1. NavGraph导航图:包含所有导航相关信息的 XML 资源,其中包含所有目的地和操作。该图表会显示应用的所有导航路径(跳转关系);
  2. NavHostFragment导航宿主:导航宿主是一个空容器,用户在应用中导航时,目的地会在该容器中交换进出(可以理解为存放fragment的view容器);
  3. NavController:是用来编程式或声明式地控制应用内的导航流程(可以理解为用作fragment跳转的工具类);
    1. 首先导入Navigation依赖;
kt 复制代码
    implementation 'androidx.navigation:navigation-runtime-ktx:2.3.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
    1. 创建显示在Activity中的多个Fragment(我创建了A、B 两个Fragment);
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".AFragment">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="this is A fragment"
        android:textSize="40dp"/>

    <Button
        android:id="@+id/a_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="a-btn"/>
    
</LinearLayout>
    1. 创建导航图;
      在res中新建navigation的资源文件夹
    1. 在NavGraph中添加Fragment,连接跳转关系;
      只需在右侧添加并连接Fragment即可,左侧代码会根据右侧的状态自动生成。
    1. 在需要添加Fragment的Activity中添加fragment控件;
kt 复制代码
<fragment
    android:id="@+id/fragment_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    // 指定了要使用的Fragment类是NavHostFragment
    android:name="androidx.navigation.fragment.NavHostFragment"
    // 指定了要使用的导航图(Navigation Graph)
    app:navGraph="@navigation/navigation_main"
    app:defaultNavHost="true"/>
    1. 给Fragment的Button控件添加点击事件,实现点击按钮跳转Fragment;
kt 复制代码
a_btn.setOnClickListener {
    // 使用Navigation.findNavController()方法来获取当前活动的NavController实例
    // 然后调用navigate()方法来执行导航操作
    Navigation.findNavController(requireActivity(), R.id.fragment_view).navigate(R.id.BFragment)
}

Navigation的基本使用完成,运行后点击按钮即可完成主Activity中两个Fragment之间相互切换。

ARouter概述

项目开发使用的原生路由方案一般是通过显示Intent或隐式Intent方式实现Activity 和 Fragment的跳转,随着项目的组件化、模块化开发,业务解耦,各个module不会进行相互依赖,如果moduleA和moduleB中的Activity需要相互跳转,使用传统的startActivity(intent)来进行通信是不能实现的。当然也可以通过其他方法去实现,比如隐式跳转或者反射机制实现跳转,但隐式跳转需要在Manifest中进行大量的过滤配置,不利于维护;通过反射跳转可能会对性能造成影响。因此我们来学习下路由框架ARouter。

ARouter的大致原理

ARouter使用@Route注解,在编译时期通过APT技术(Annotation Processing Tool)生成类文件用于存储path和activityClass的映射关系。在app进程启动的时候会拿到这些类文件,把里面存储的映射关系数据读到内存里,保存在路由表map中。 在进行路由跳转时,通过ARouter的build()方法传入要到达页面的路由地址,ARouter在路由表中找到路由地址对应的activityClass,然后new Intent(),通过ARouter的withString()方法传入携带参数,内部调用intent.putExtra(),通过ARouter的navigation()跳转,内部调用startActivity(intent)。

ARouter的基本使用

1. 添加依赖

在project下的<build.gradle>中添加ARouter的依赖

kt 复制代码
android {
    ...

    defaultConfig {
        ... 

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }

    ...
}

dependencies {

    implementation 'androidx.annotation:annotation:1.3.0'
    implementation 'com.alibaba:arouter-api:1.5.2'
    annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'

    ...
}
  • 导入依赖遇到的问题

Q1: 导入ARouter依赖同步成功,编译时会报错;

kt 复制代码
    Manifest merger failed with multiple errors, see logs

A1: 在project中的<gradle.properties>中添加配置,将项目迁移到AndroidX中;

kt 复制代码
    android.useAndroidX=true    //在编译时自动使用AndroidX库替代旧的Support库
    android.enableJetifier=true    //自动将项目中使用的第三方库迁移到AndroidX

Q2: 添加javaCompileOptions配置会报错;

kt 复制代码
    ARouter::Compiler >>> No module name, for more information, look at gradle log.

A2: arguments中键名不对,将moduleName 改为AROUTER_MODULE_NAME就可以编译成功了;

kt 复制代码
原:
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName : project.getName()]
            }
        }

改:
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
2. 初始化ARouter

官方建议在Application里面进行ARouter的初始化,并将初始化ARouter的Application配置到Androidmanifest.xml文件中;

  • 创建初始化ARouter的Application类<InitApplication.kt>;
kt 复制代码
class InitApplication : Application() {

    private var isDebugARouter = true

    override fun onCreate() {
        super.onCreate()

        if (isDebugARouter){
            // 写在init之前才会生效
            ARouter.openLog()
            ARouter.openDebug()
        }

        //初始化ARouter
        ARouter.init(this)
    }

}
  • 在Androidmanifest中配置InitApplication ;
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.arouterdemo">

    <application
        android:name="com.example.arouterdemo.InitApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ARouterDemo">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
3. ARouter的使用

这里先简要介绍下ARouter的使用流程,由于ARouter主要用于解决组件间和模块间的界面跳转问题,在接下来的4. ARouter模块跳转中会以模块为单位进行跳转。

kt 复制代码
1. 在即将跳转到的Activity注解中指明该Activity的路径
  【 @Route(path = "/xxx/xxx") 】:

    @Route(path = "/main/first")
    class FirstActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
           setContentView(R.layout.activity_first)
        }
    }


2. 在主动跳转的Activity中指明要跳转去的Activity的路径:
  【 ARouter.getInstance().build("/xxx/xxx").navigation() 】

    class SecondActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_second)

            initView()
        }

        fun initView(){
            btn.setOnClickListener {
                ARouter.getInstance().build("/main/first").navigation()
            }
        }
    }
  • 遇到的问题

**Q:**ARouter简单使用的apk点击跳转按钮提示没有匹配路径;

kt 复制代码
ARouter::: ARouter::There is no route match the path [/xxx/xxx], in group [xxx][ ] 

**A:**由于Android开发使用的是kotlin语言编写的,因此需要使用kapt依赖,而ARouter使用的是annotationProcessor;

kt 复制代码
原:

android {
    ...

    defaultConfig {
        ... 

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }

    ...
}

dependencies {

    implementation 'androidx.annotation:annotation:1.3.0'
    implementation 'com.alibaba:arouter-api:1.5.2'
    annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'

    ...
}


改:

apply plugin: 'kotlin-kapt'

android {
    ...

    defaultConfig {
        ... 

        kapt {
            arguments {
                arg("AROUTER_MODULE_NAME", project.getName())
            }
        }
    }

    ...
}

dependencies {

    implementation 'androidx.annotation:annotation:1.3.0'
    implementation 'com.alibaba:arouter-api:1.5.2'
    kapt 'com.alibaba:arouter-compiler:1.5.2'

    ...
}
4. ARouter模块跳转
  • 创建模块
    首先创建一个Base模块用来存放项目模块的路由路径;

project根目录 ---> new ---> module ---> Android Library ---> 设置module名称 ---> Next ---> Finish

  • 导入ARouter依赖

    只要是使用ARouter的所有模块,都需要在module的build.gradle中添加ARouter依赖;

  • 创建路由路径对象

    根据实际项目创建的模块来编写路由路径,路径至少2级,不同模块的1级路径不相同,写成一样的可能会出问题;

kt 复制代码
object RouterPath {

    // 主页面
    const val MAIN = "/app/main"

    // 设置主页
    const val SETTING = "/setting/main"

    // 消息页面
    const val MESSAGE = "/message/main"

}
  • 根据路由路径封装跳转api
kt 复制代码
object Router {

    fun navMain(){
        ARouter.getInstance().build(RouterPath.MAIN).navigation()
    }

    fun navSetting(){
        ARouter.getInstance().build(RouterPath.SETTING).navigation()
    }

    fun navMessage(){
        ARouter.getInstance().build(RouterPath.MESSAGE).navigation()
    }

}
  • 模块间依赖
    由于module-base模块中有整个项目的路由路径及跳转api,属于项目公用的模块,所以其他所有模块都需要在build.gradle中依赖module-base模块;
kt 复制代码
以app模块为例:

dependencies {
    ...

    implementation project(path: ':module-base')
    implementation project(path: ':module-setting')
    implementation project(path: ':module-message')
}

以上仅为公用Base模块的创建,除此之外还创建了module-message和module-setting模块用来测试模块间跳转,流程相同,①创建模块,②导入ARouter依赖,③创建主Activity。

  • module-message模块的代码

在页面上添加两个按钮,分别用来点击跳转到另外两个模块主页面,在点击监听中添加ARouter跳转逻辑即可(三个模块的代码几乎一样,区别在于主模块app导入其他所有模块的依赖,而其他模块只需要导入module-base模块的依赖);

xml 复制代码
activity_message.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MessageActivity">


    <Button
        android:id="@+id/to_setting"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="setting"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <Button
        android:id="@+id/to_main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="main"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Message Activity"
        android:textSize="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
kt 复制代码
MessageActivity.kt

@Route(path = RouterPath.MESSAGE)
class MessageActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_message)

        initView()
    }

    private fun initView(){
        to_setting.setOnClickListener {
            Router.navSetting()
        }
        to_main.setOnClickListener {
            Router.navMain()
        }
    }
}

使用ARouter实现Fragment跳转

部分项目中使用的是该方法,但我没有写demo,大致流程如下:

  1. 创建Fragment的Route<MainRoute.kt>
kt 复制代码
object MainRoute {
    const val FRAGMENT_A = "/app/afragment"
    const val FRAGMENT_B = "/app/bfragment"
    const val FRAGMENT_C = "/app/cfragment"
}
  1. 创建导航图<MainNavGraph.kt>
    这段代码有问题,不知道是否因为导入的navigation版本问题,这里先讲实现思路;
kt 复制代码
object MainNavGraph {

    fun create(navController: NavController, startDest : String = MainRoute.FRAGMENT_A){
        navController.apply{
            graph = createGraph(
                route = "app",
                startDestination = startDest
            ){
                // 创建Fragment实例,并将其添加到MainRoute路由中
                fragment<AFragment>(route = MainRoute.FRAGMENT_A)

                fragment<BFragment>(route = MainRoute.FRAGMENT_B)

                fragment<CFragment>(route = MainRoute.FRAGMENT_C)
            }
        }
    }

}

导航图主要是把fragment和route路由路径对应起来,当根据路由路径进行跳转时,实际上是跳转到对应的Fragment中,而Fragment就不需要添加route注释@Route来绑定路由路径了。

  1. Fragment跳转实现

在Fragment所在的Activity中需要绑定NavHostFragment和navController,然后通过navController和route路径来实现跳转;

java 复制代码
class MainActivity : AppCompatActivity() {

    private lateinit var navController : NavController

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

        val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_view) as NavHostFragment
        navController = navHostFragment.navController
        MainNavGraph.create(navController)

        to_setting.setOnClickListener {
            navController.navigate(MainRoute.FRAGMENT_B)
        }
    }
}

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui