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

相关推荐
paid槮8 小时前
MySql基础:数据类型
android·mysql·adb
用户20187928316710 小时前
AMS和app通信的小秘密
android
用户20187928316710 小时前
ThreadPoolExecutor之市场雇工的故事
android
诺诺Okami10 小时前
Android Framework-Launcher-InvariantDeviceProfile
android
Antonio91511 小时前
【音视频】Android NDK 与.so库适配
android·音视频
sun00770020 小时前
android ndk编译valgrind
android
AI视觉网奇21 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空21 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet1 天前
android组包时会把从maven私服获取的包下载到本地吗
android
catchadmin1 天前
PHP serialize 序列化完全指南
android·开发语言·php