随着我们的项目的内容的增加模块的增加导致项目层次不够分明,并且有时候debug一下改一下东西可能我们需要几分钟来运行一个项目这明显不是我们想要的,所以这个时候就把组件化引入到我们的项目中来了。
顺道提一下 “组件化” 和 “模块化” 的区别
模块化: 模块化更注重业务逻辑,可以单独编译成 APP, 负责单一业务,具备自身的生命周期,说白了就是一个可以独立运行的APP。(后面会有体现)
组件化: 组件化更注重的是某一块功能,无关业务,例如网络请求模块,图片加载模块,支付模块等等。
好了进入正题,讲到组件化我们不得不讲到 ARouter。
一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
ARouter框架不仅提供了强大的路由跳转功能,还有其他的能力。该框架对模块解耦,组件化设计提供了强有力的支持。
ARouter框架提供的具体功能包括Native页面跳转,URL页面跳转,获取Fragment,提供能力接口,拦截器等。
按照惯例我们先引入 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'
}
新建 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 的简单使用(后面在组件化配置完之后再来讲更多的使用)
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'
}
# 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 可以单独运行,但是不可以被其他项目引入。
//如果不能单独运行就是一个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
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cassie.first">
<application
android:allowBackup="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".FirstActivity">
activity>
application>
manifest>
buildApplicaiton/AndroidManifest.xml 这里就是原先的 main/AndroidManifest.xml 文件一模一样无需改动
<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 删除
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')
}
}
/**
* @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 这个是我们的 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