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老皮!!!欢迎大家来找我探讨交流👀

相关推荐
HerayChen几秒前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野2 分钟前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing11234 分钟前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件29 分钟前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio
dj15402252031 小时前
group_concat配置影响程序出bug
android·bug
周全全1 小时前
MySQL报错解决:The user specified as a definer (‘root‘@‘%‘) does not exist
android·数据库·mysql
- 羊羊不超越 -2 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
wk灬丨3 小时前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
千雅爸爸3 小时前
Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)
android