标题有点夸大了,arouter的那么多功能怎么可能都去实现呢?实际上只实现了一个功能:模块间Activity的跳转
文章的目的:去了解arouter是如何实现的
运行软件和环境:
- android Studio 3.5.1
- jdk1.8
项目结构
QQ图片20200209133901.png说明:
1.annotion模块:是一个javalibrary 里面定义了一个注解,用于标记Activity
2.annotion_compiler:是一个javalibrary,注解处理器,拿到我们注解标记的内容,做对应的处理
3.app、login、和mine为 3个android模块
4.arouter为各个组件的公共引入的模块(activity的跳转逻辑这里实现)
技术难点
我们知道,传统Activity之间的跳转,需要通过startActivity(intent),而在组件化项目中,2个moduel之前没有依赖关系,比如这里login怎么跳转到mine模块的Activity里面去呢?
解决
给组件之间连接一个公共的管理模块,通过这个管理模块去管理所有组件间的跳转
结构图
QQ图片20200210104637.png
开始写代码:
1.先将login和mine改成library,让app可以引入
1.1 在gradle.properties中去添加变量
# 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
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# 配置统一的版本
COMPILE_SDK_VERSION=29
BUILD_TOOLS_VERSION=29.0.3
MIN_SDK_VERSION=19
TARGET_SDK_VERSION=29
# login标记
LOGIN_IS_APPLICATION=false
#mine标记
MINE_IS_APPLICATION=false
1.2library和Application的清单文件是不能一样的所以要给library添加一个清单文件
1.3同时修改build.gradle
if (LOGIN_IS_APPLICATION.toBoolean()) {
apply plugin:'com.android.application'
} else {
apply plugin:'com.android.library'
}
android{
compileSdkVersion COMPILE_SDK_VERSION.toInteger()
buildToolsVersion BUILD_TOOLS_VERSION
defaultConfig{
if (LOGIN_IS_APPLICATION.toBoolean()) {
applicationId"com.example.login"
}
minSdkVersion MIN_SDK_VERSION.toInteger()
targetSdkVersion TARGET_SDK_VERSION.toInteger()
versionCode1
versionName"1.0"
testInstrumentationRunner"androidx.test.runner.AndroidJUnitRunner"
}
buildTypes{
release{
minifyEnabledfalse
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),'proguard-rules.pro'
}
}
sourceSets {
main {
if (LOGIN_IS_APPLICATION.toBoolean()) {
//单独运行
manifest.srcFile('src/main/AndroidManifest.xml')
} else {
//不是单独运行
manifest.srcFile('src/main/manifest/AndroidManifest.xml')
}
}
}
}
dependencies {
implementation fileTree(dir:'libs',include:['*.jar'])
implementation project(path:':arouter')
implementation project(path:':annotion')
annotationProcessor project(path:':annotion_compiler')
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
以上是login模块的,mine模块也是同样的这里我就不贴出来了
1.4 最后将login和mine添加到app模块中
app模块的build.gradle
dependencies {
implementation fileTree(dir:'libs',include:['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation project(path:':arouter')
implementation project(path:':annotion')
annotationProcessor project(path:':annotion_compiler')
if (!MINE_IS_APPLICATION.toBoolean()) {
implementation project(path:':mine')
}
if (!LOGIN_IS_APPLICATION.toBoolean()) {
implementation project(path:':login')
}
}
2.将所有模块的Activity添加到一个同一个容器中
解决:
我们可以利用编译注解的特性,新增一个注解,给每个需要通过url打开的activity加上此注解。在注解处理器中获取所有被注解的类,动态生成映射关系表,然后在app启动时把所生成的映射关系load到内存(其实就是读到一个map中)
2.1 创建容器
在arouter模块中
package com.example.arouter;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import dalvik.system.DexFile;
/**
* 中间人
* 容器对象 装载所有的Activity对象
*/
public class Arouter {
private static Arouter fragment = new Arouter();
private Map<String, Class<? extends Activity>> map;
private Context context;
public static Arouter newInstance() {
return fragment;
}
public void init(Context context) {
this.context = context;
List<String> className = getClassName("com.luuuzi.util");
for (String s : className) {
//通过类名获取到类对象
try {
Class<?> aClass = Class.forName(s);
//判断类对象是否是arouter的子类
if (IRouter.class.isAssignableFrom(aClass)) {
IRouter iRouter= (IRouter) aClass.newInstance();
//调用方法
iRouter.putActivity();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private Arouter() {
map=new HashMap<>();
}
/**
* 将Activity添加到map容器中
*
* @param key
* @param clz
*/
public void addActivity(String key, Class<? extends Activity> clz) {
if (TextUtils.isEmpty(key) || clz == null) {
return;
}
map.put(key, clz);
}
public Class<? extends Activity> getActivity(String key) {
if (TextUtils.isEmpty(key)) {
return null;
}
return map.get(key);
}
/**
* 跳转Activity
*
* @param key
* @param bundle
*/
public void jumpActivity(String key, Bundle bundle) {
Class<? extends Activity> aClass = map.get(key);
if (aClass == null) {
return;
}
Intent intent = new Intent(context, aClass);
if (bundle != null) {
intent.putExtras(bundle);
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
/**
* 通过包名获取这个包下面的所有类名
*
* @param packageName 包名
* @return
*/
private List<String> getClassName(String packageName) {
ArrayList<String> classList = new ArrayList<>();
String path = null;
try {
//通过包管理器,获取到应用信息类然后获取到apk的完整路径
path = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).sourceDir;
DexFile dexFile = new DexFile(path);
Enumeration<String> entries = dexFile.entries();
//遍历
while (entries.hasMoreElements()) {
String name = entries.nextElement();
//判断包名是否符合com.luuuzi.util
if (name.contains(packageName)) {
classList.add(name);
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return classList;
}
}
2.2 在创建一个接口,用于添加Activity
package com.example.arouter;
/**
* 所有Activity的工具类
*/
public interface IRouter {
void putActivity();
}
2.3 在annotion模块中定义一个注解
这个注解是用来标记模块中所有的Activity
package com.example.annotion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface BindPath {
String value();
}
2.4 在annotion_compiler模块中通过BindPath注解拿到对应的Activity,然后去生成java文件
引入
dependencies {
implementation fileTree(dir:'libs',include:['*.jar'])
// Google提供的auto-service库
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
compileOnly'com.google.auto.service:auto-service:1.0-rc3'
//引入annotion
implementation project(path:':annotion')
}
注解处理器生成java文件
package com.example.annotion_compiler;
import com.example.annotion.BindPath;
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
/**
* 注解处理器
*/
@AutoService(Processor.class)//注册注解处理器
public class AnnotationCompiler extends AbstractProcessor {
//生成java文件的对象
Filer filer;
//初始化1
// 初始化好filer
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
/**
* 初始化2
* 声明 注解 处理器支持的java版本
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
/**
* 初始化3
* 声明注解处理器:要处理的注解
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindPath.class.getCanonicalName());//拿到名字
return types;
}
/**
* 写文件
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//1.获取到整个模块用所有用到了bindpath注解的节点(包括类节点,方法节点,成员变量节点)
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
//2.结构化数据的容器,即实现如下代码 :Arouter.newInstance().addActivity("login",LoginActivity.class);
Map<String, String> map = new HashMap<>();
for (Element element : elementsAnnotatedWith) {//遍历所有的节点
//因为bindpath注解放在类节点上 所以我们这里获取到的都是类节点
TypeElement typeElement = (TypeElement) element;
//通过类节点获取到它上面的注解
BindPath annotation = typeElement.getAnnotation(BindPath.class);
String key = annotation.value();//获取到注解的值,即login/login
//获取类对象的包名加类名
String activityName = typeElement.getQualifiedName().toString();
map.put(key, activityName);
}
//3.写文件
if (map.size() > 0) {
//创建一个文件名 app,login,mine每个模块都会执行一次,即一共执行3次,要保证每个文件名不重复
String utilName = "ActivityUtil" + System.currentTimeMillis();
Writer writer = null;
try {
//生成文件
//创建源码目录
JavaFileObject sourceFile = filer.createSourceFile("com.luuuzi.util." + utilName);
writer = sourceFile.openWriter();
writer.write("package com.luuuzi.util;\n" +
"\n" +
"import com.example.arouter.Arouter;\n" +
"import com.example.arouter.IRouter;\n" +
"//将loginActivity添加进去\n" +
"public class " + utilName + " implements IRouter {\n" +
" @Override\n" +
" public void putActivity() {");
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
String value = map.get(key);
writer.write("Arouter.newInstance().addActivity(\"" + key + "\"," + value + ".class);\n");
}
writer.write("}\n}");
} catch (IOException e) {
e.printStackTrace();
}finally {
if (writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
}
这样就算完成了,接下来就是使用
3 使用
给login模块的Activity添加bindpath注解
//登录模块
@BindPath("login/login")
public class LoginActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
}
}
然后运行项目
就会在这个目录下生成一个java文件
QQ图片20200210112511.png这里只是创建了代码,并没有调用方法(没有添加到容器里面去)
接来下添加到容器里面去
package com.example.demo007;
import android.app.Application;
import com.example.arouter.Arouter;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Arouter.newInstance().init(this);
}
}
添加成功之后调用方法进行Activity间的跳转就可了
//app模块
@BindPath("main/main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Arouter.newInstance().jumpActivity("login/login",null);
}
});
}
}
大功告成
项目地址:demo007
参考资料
网友评论