Android 组件化 ARouter 的使用
背景
随着我们的项目的内容的增加模块的增加导致项目层次不够分明,并且有时候debug一下改一下东西可能我们需要几分钟来运行一个项目这明显不是我们想要的,所以这个时候就把组件化引入到我们的项目中来了。
顺道提一下 “组件化” 和 “模块化” 的区别
组件化: 组件化更注重业务逻辑,可以单独编译成 APP, 负责单一业务,具备自身的生命周期,将白了就是一个可以独立运行的APP。(后面会有体现)
模块化: 模块化更注重的是某一块功能,无关业务,例如网络请求模块,图片加载模块,支付模块等等。
好了进入正题,讲到组件化我们不得不讲到 ARouter (当然也可以是其他的路由跳转库 mzule ActivityRouter 等等)这里我们就以 ARouter 为例子。
ARouter 是什么
一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
为什么要用 ARouter
ARouter框架不仅提供了强大的路由跳转功能,还有其他的能力。该框架对模块解耦,组件化设计提供了强有力的支持。
ARouter框架提供的具体功能包括Native页面跳转,URL页面跳转,获取Fragment,提供能力接口,拦截器等。
组件化如何使用 ARouter
按照惯例我们先引入 ARouter 库,在 app 的 build.gradle 文件加入以下内容
android {
//... 省略其他
defaultConfig {
//... 省略其他
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
}
dependencies {
//... 省略其他
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
api 'com.alibaba:arouter-api:1.4.1'
}
初始化 ARouter
新建 MyApplication.java 去初始化 ARouter,记得要在 AndroidManifest.xml 引用上 MyApplication
package com.cassie.aroutertest;
import android.app.Application;
import com.alibaba.android.arouter.launcher.ARouter;
/**
* @aouther: Administrator
* @version: V1.0.0
* @description
* @date: 2019/12/6
*/
public class MyApplicaiton extends Application {
@Override
public void onCreate() {
super.onCreate();
initARouter();
}
private void initARouter() {
if (BuildConfig.DEBUG) {
ARouter.openDebug();
ARouter.openLog();
}
ARouter.init(this);
}
@Override
public void onTerminate() {
super.onTerminate();
ARouter.getInstance().destroy();
}
}
单组件使用
我们先在单一组件里面用 ARouter 进行跳转,打开我们的 MainActivity.java 设置点击事件,跳到 HelloActivity 页面。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tvGoFirst = findViewById(R.id.tv_to_first);
tvGoFirst.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ARouter.getInstance()//获取实例
.build("/test/helloActivity")//跳到对应此路径的 Activity 必须要>=2个层级
.navigation();//跳转
}
});
}
}
HelloActivity.java
//这里的路径和 MainActivity 的对应
//通过这个注解在编译的时候会生成对应的 ARouter 相关文件
//路径 build/generated/ap_generated_sources/debug/out/com.alibaba.android.arouter.routes
@Route(path = "/test/helloActivity")
public class HelloActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hello);
}
}
OK,这就是 ARouter 的简单使用(后面在组件化配置完之后再来讲更多的使用)
组件化 ARouter
- 新建一个 base 的 Android Library 以及 first 和 second 的 Phone & Tablet Module
- 在 base 模块里引入 ARouter 的相关依赖
android {
//... 省略其他
defaultConfig {
//... 省略其他
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
}
dependencies {
//... 省略其他
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
api 'com.alibaba:arouter-api:1.4.1'
}
- 修改项目的 gradle.properties 添加 isModule=true 用来控制 first 和 second 组件能不能单独编译成APP
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#isModule=false
isModule=true
如果 isModule 为 true 则 first 和 second 不可单独运行,但其他项目可以引入。当 isModule 为 false 的时候 first 和 second 可以单独运行,但是不可以被其他项目引入。
- 分别修改 first 和 second 组件的 build.gradle
//如果不能单独运行就是一个library
if (isModule.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
android {
compileSdkVersion 28
defaultConfig {
//如果是一个 application 的话需要设置好id
if (!isModule.toBoolean()) {
applicationId "com.cassie.first"
}
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
//这个也是每个组件/模块都需要加入
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
sourceSets {
main {
// library 和 application 对应的 AndroidManifest 文件
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/buildModule/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/buildApplicaiton/AndroidManifest.xml'
}
}
}
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//noinspection GradleCompatible
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//这里的注解依赖需要加入(如果需要用到 ARouter 的注释都是需要加入这个的)
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
//引入 base 的依赖
implementation project(':base')
//引入 second 依赖
if (isModule.toBoolean()) {
implementation project(':second')
}
}
- 新建 buildModule/AndroidManifest.xml 以及 buildApplicaiton/AndroidManifest.xml
buildModule/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cassie.first">
<!--只需要保留这几个值 其他无关的删掉 theme 也可以根据自身情况确定是否保留-->
<application
android:allowBackup="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".FirstActivity">
</activity>
</application>
</manifest>
buildApplicaiton/AndroidManifest.xml 这里就是原先的 main/AndroidManifest.xml 文件一模一样无需改动
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cassie.first">
<application
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/AppTheme">
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
注意:需要把原来的 main/AndroidManifest.xml 删除
- 最后,修改 app 的 build.gradle 文件
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.cassie.aroutertest"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//noinspection GradleCompatible
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
//修改成 引入base
implementation project(':base')
//如果不作为 application 就引入这两个组件
if (isModule.toBoolean()) {
implementation project(':first')
implementation project(':second')
}
}
- 在 base 模块里新建一个 JumpUtils.java 存到所有页面的跳转路径统一管理
/**
* @aouther: Administrator
* @version: V1.0.0
* @description
* @date: 2019/12/6
*/
public class JumpUtil {
public static final String MainActivity = "/app/MainActivity";
public static final String FirstActivity = "/first/FirstActivity";
public static final String SecondActivity = "/second/SecondActivity";
}
开始使用
其实组件化的 ARouter 和 单组件的 ARouter 使用是一样的。
我们就编辑代码,从 app 的 MainActivity 跳到 first 的 FirstActivity 再跳到 seconde 的 SecondActivity 再跳到 app 的 MainActivity
//MainActivity
@Route(path = JumpUtil.MainActivity)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tvGoFirst = findViewById(R.id.tv_to_first);
tvGoFirst.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ARouter.getInstance().build(JumpUtil.FirstActivity).navigation();
}
});
}
}
//FirstActivity
@Route(path = JumpUtil.FirstActivity)
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
TextView tvFirst = findViewById(R.id.tv_first);
tvFirst.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ARouter.getInstance().build(JumpUtil.SecondActivity).navigation();
}
});
}
}
//SecondActivity
@Route(path = JumpUtil.SecondActivity)
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
TextView tv_second = findViewById(R.id.tv_second);
tv_second.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ARouter.getInstance().build(JumpUtil.MainActivity).navigation();
}
});
}
}
OK,这只是个简单的调用,这个时候如果我们要传参的话咋整?
传参
ARouter 里面有很多的 withXXX 方法可以传参,基本类型,Object(需要一个转换器),Serializable,Parcelable 以及 Bundle 等。
普通传参
String,Double 等类型
ARouter.getInstance()
.build(JumpUtil.FirstActivity)
.withString("key1", "hellol")
.navigation();
有三种获取传参的方法(伪代码)
//1. 通过getIntent()获取
getIntent().getStringExtra("key1");
//2. 通过 @Autowired(name="XXXX")
@Autowired(name = "key1")
String string;
//3. 通过 @Autowired 不指定名字 这个时候变量的名字就相当于他的name
@Autowired
String key1;
@Override
protected void onCreate(Bundle savedInstanceState) {
//...省略其他
//如果用 @Autowired 注解的话一定调用这个注入方法(才会去生成对应的获取参数值的方法)
//具体可以看 build/generated/ap_generated_sources/debug/out/包/FirstActivity$$ARouter$$Autowired
//这句话最好放在 BaseActivity 基类Activity中 因为很有可能很多 Activity 都需要 不需要的页面加上这句也不影响
ARouter.getInstance().inject(this);
System.out.println("key1= " + key1);
System.out.println("string= " + text);
System.out.println("intent= " + getIntent().getStringExtra("key1"));
}
输出信息如下:
传Object
直接传递 Object 这个是我们的 Intent 做不到的,而 ARouter 可以。我们在 base 模块新建 JsonServiceImpl.java (如果不是用 Gson 用其他原理是一样的)
package com.cassie.base;
import android.content.Context;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alibaba.android.arouter.facade.service.SerializationService;
import com.google.gson.Gson;
import java.lang.reflect.Type;
/**
* @aouther: Administrator
* @version: V1.0.0
* @description
* @date: 2019/12/7
*/
@Route(path = "/base/json")
public class JsonServiceImpl implements SerializationService {
private Gson gson;
@Override
public void init(Context context) {
gson = new Gson();
}
@Override
public <T> T json2Object(String text, Class<T> clazz) {
return gson.fromJson(text, clazz);
}
@Override
public String object2Json(Object instance) {
return gson.toJson(instance);
}
@Override
public <T> T parseObject(String input, Type clazz) {
return gson.fromJson(input, clazz);
}
public void checkJson() {
if (gson == null) {
gson = new Gson();
}
}
}
这样我们就可以直接传递参数例如我们新建一个 Student 的类然后去传递这个类
伪代码如下
//MainActivity
ARouter.getInstance()
.build(JumpUtil.FirstActivity)
.withString("key1", "hellol")
.withObject("students", new Student("Cassie", 23, "Android Software"))
.navigation();
//FirstActivity
@Autowired
Student student;
@Override
protected void onCreate(Bundle savedInstanceState) {
//...省略其他
System.out.println(student.toString());
}
输出如下:
**注意:如果你的 Student 类去继承 Serializable 或 Parcelable 就不能用 withObject() 去传递参数,而是要用对应的 withParcelable() 和 withSerializable() **
如果这个时候我要从 FirstActivity 跳到 SecondActivity 后关闭 SecondActivity 并携带某些数据返回 FirstActivity 要咋整?
获取页面返回值
在 ARouter 带 RequestCode 跳转页面也很简单,并且可以一个跳转的回调
ARouter.getInstance().build(JumpUtil.SecondActivity)
.navigation(context, requestCode,callback);
**NavigationCallback **
public interface NavigationCallback {
/**
* 找到目标回调
*/
void onFound(Postcard postcard);
/**
* 找不到目标返回
*/
void onLost(Postcard postcard);
/**
* 跳转成功回调
*/
void onArrival(Postcard postcard);
/**
* 回调被拦截返回,未跳转完成
*/
void onInterrupt(Postcard postcard);
}
哦吼,上面看到被拦截器回调,也就是说 ARouter 有拦截器咯? 那是当然啦~
拦截器
在我们的实际开发中总有一些需求类似说“需要登录才能跳到这个页面”,那我们总不能每次跳转都要判断用户到底有没有登录,这无疑增加了大量的重复代码。
LoginInterceptorImpl
//priority 拦截器的优先级 数字越小优先级越高
@Interceptor(name = "loginInterceptor", priority = 3)
public class LoginInterceptorImpl implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
boolean isLogin = false;
if (isLogin) {
//继续执行
callback.onContinue(postcard);
} else {
if (JumpUtil.SecondActivity.equals(postcard.getPath()))
callback.onInterrupt(new Exception("用户未登录"));
else callback.onContinue(postcard);
}
}
@Override
public void init(Context context) {
//初始化 只会调用一次
System.out.println("初始化成功");
}
}
ARouter.getInstance().build(JumpUtil.SecondActivity)
.navigation(FirstActivity.this,
new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
}
@Override
public void onLost(Postcard postcard) {
}
@Override
public void onArrival(Postcard postcard) {
}
@Override
public void onInterrupt(Postcard postcard) {
//拦截器回调
Log.i("onInterrupt", "onInterrupt: "+postcard);
}});
ARouter 的一些常用的东西大概就是这样了。
总结:其实 ARouter 和 Butterknife 和 Dargger2 一样都是通过 APT 在编译期生成对应的文件,和我前面的绕过微信包名限制对接微信登录和支付原理都是一样的~
源码链接:
百度云
链接:https://pan.baidu.com/s/1UKF8489XQ2cltuBbLcVhjg
提取码:edcx