1.前言
组件化或者模块化开发模式,已逐渐成为热浪的形式,使用这些模式可以让我们程序更容易的扩展、更方便的维护
更快捷的同步开发与更简单的单独调试,而ARouter的出现就是让组件间、模块间是实现完全的独立。
ARouter是:阿里巴巴自研路由框架,主要解决组件间、模块间的 界面跳转 问题。
今天用最简单的方式讲解Arouter的实现原理。
以下是我在学习网易公开课做的笔记,想学习可以百度搜索一下,我最近刚花钱买了个微专业学习,提升自己中,自我感觉不错,哈哈,不是打广告哈(我也没什么人气...)
2.组件化搭建
这里我就不描述组件化怎么搭建了,我给大家推荐一篇文章(这是鸿阳公众号的一篇文章)手把手带你 实践组件化
3.自定义超低配ARouter
组件化搭建好后:
我们要实现不同组件的跳转,我们肯定需要一个"中间人",这里我创建了一个module,命名为arouter,这里分别定义了两个类Arouter和IArouter代码如下:
1.Arouter
package com.yike.arouter;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import dalvik.system.DexFile;
import java.util.*;
/**
* Author:wangcaiwen
* Date:2019-07-16
* Description:中间人 代理
*/
public class Arouter {
private static Arouter arouter= new Arouter();
private Context context;
private static final String TAG = "Arouter";
public void init(Application context){
this.context = context;
//这里主要是为了获取通过注解在包名:com.yike.util 下自动生成所有类,
List classNmaes = getClassNmae("com.yike.util");
for (String classNmae : classNmaes) {
Log.e(TAG, "init: "+classNmae );
try {
Class> aClass = Class.forName(classNmae);
//判断这个类是否是IRouter的实现类
//如果是IArouter的实现类
if (IArouter.class.isAssignableFrom(aClass)){
IArouter iArouter = (IArouter) aClass.newInstance();
iArouter.putActivity();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 装载所有的activity的类对象的容器
*/
private Map> activityList;
public Arouter() {
activityList = new HashMap<>();
}
public static Arouter getInstance(){
return arouter;
}
/**
* 将activity的类对象,添加进map
* @param path 这里参数就是我们定义的路由地址 格式如:(/user/login)
* @param clazz 这个就是我们需要跳转的activity 也就是说:/user/login-->LoginActivity
*/
public void putActivity(String path,Class extends Activity> clazz){
if (path!=null&&clazz!=null){
activityList.put(path, clazz);
}
}
/**
* 跳转页面调用的方法
* @param key
* @param bundle
*/
public void jumpActivity(String key, Bundle bundle){
//取出路由地址对应的activity类
Class extends Activity> aClass = activityList.get(key);
if (aClass==null){
return;
}
Intent intent = new Intent().setClass(context, aClass);
if (bundle!=null){
intent.putExtra("bundle", bundle);
}
/**
* 这里这行必须加一下
* 网上查询了一下说明如下 参考链接:
* 1.在Activity上下文之外启动Activity需要给Intent设置FLAG_ACTIVITY_NEW_TASK标志,不然会报异常。
* 2.加了该标志,如果在同一个应用中进行Activity跳转,不会创建新的Task,只有在不同的应用中跳转才会创建新的Task
*/
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.startActivity(intent);
}
private List getClassNmae(String packeagename) {
//创建一个class对象集合
List clazzs = new ArrayList<>();
String path=null;
try {
//通过包管理器 获取到应用信息类然后获取到apk的完整路径
String sourceDir = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).sourceDir;
//根据apk的完整路径获取编译后的dex文件
DexFile dexFile = new DexFile(sourceDir);
//获得编译后的dex文件中的左右class
Enumeration entries = dexFile.entries();
//然后进行遍历
while (entries.hasMoreElements()){
//通过遍历所有的class 的包名
String name = entries.nextElement();
//判断累的包名是否符合
if (name.contains(packeagename)){
//符合添加到集合中
clazzs.add(name);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return clazzs;
}
}
2.IArouter
package com.yike.arouter;
/**
* Author:wangcaiwen
* Date:2019-07-16
* Description:这个接口是所有activity工具类的基类
*/
public interface IArouter {
void putActivity();
}
完成到这里我们想一下是不是可以直接定义一个类:
/**
* Author:wangcaiwen
* Date:2019-07-16
* Description:1
*
*/
public class ActivityUtils implements IArouter {
private static final String TAG = "ActivityUtils";
@Override
public void putActivity() {
Log.e(TAG, "putActivity: " );
Arouter.getInstance().putActivity("login/login", LoginActivity.class);
}
}
通过这个类我们将路由地址和跳转的activity put进我们的集合里,然后我们再通过Arouter的jumpActivity方法是不是就可以跳转了?
但是项目中那么多activity我们不可能重复的去写 Arouter.getInstance().putActivity("login/login", LoginActivity.class);
来添加到集合中
接下来我们就会用到注解:
组件化框架搭建好后,我们再继续创建两个Module,但是这次我们选择的是java Library
给大家看一下我的项目结构目录
annotation是注解;
annotation_complier是注解处理器 自动生成类;
创建好这两个module后我们还需要在每个组件build.gradle中依赖上,如下:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
kapt rootProject.ext.dependencies.arouter_compiler
implementation project(':common_module')
implementation project(path: ':arouter')
implementation project(path: ':annotations')
//annotationProcessor 这里必须使用annotationProcessor 告诉系统这是一个注解处理器
//注意:因为我用的是kotlin所以这里用的是kapt java的话用annotationProcessor 这一块我刚 开始没注意,一直生成不了代码
kapt project(path: ':annotation_complier')
}
每个module都依赖好后,再来写annotations里的代码吧 我创建了一个命名为ARouter的类 如下:
注解的含义我做了简单的注释
package com.arouter.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Author:wangcaiwen
* Date:2019-07-16
* Description:
* @author wangcaiwen
*/
//声明这个注解是放在类上面的
@Target(ElementType.TYPE)
//声明这个注解的生命周期 source源码期-->class编译期-->runtime运行期
@Retention(RetentionPolicy.CLASS)
public @interface BindPath {
String value();//这个就是我们需要跳转路由地址
}
定义好后我们就可以在activity上添加注解了,当然这还是不够的,继续看
@BindPath("login/login")
class LoginActivity : TitleBarMvpActivity(), LoginContract.View{
}
接下来再打开annotation_compiler我们这里再创建了一个类AnnotationCompiler
这个module里我们需要再build.gradle里添加如下(谷歌提供的开源库):
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//as 3.4.1+ 用来注册注解管理器 AndroidStudio3.4.1 以上用这两个
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc3'
compileOnly 'com.google.auto.service:auto-service:1.0-rc3'
//as 3.4.1- AndroidStudio3.4.1 以下用这个
//implementation 'com.google.auto.service:auto-service:1.0-rc3'
implementation project(path: ':annotations')
}
sourceCompatibility = "7"
targetCompatibility = "7"
添加完成后我们再去写AnnotationCompiler类:
package com.arouter.annotation_complier;
import com.arouter.annotations.BindPath;
import com.google.auto.service.AutoService;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
/**
* Author:wangcaiwen
* Date:2019-07-16
* Description:注解处理器 自动生成类
*
* @author wangcaiwen
*/
@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {
//生成文件的对象
Filer filer;
/**
* 初始化
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
/**
* 声明我们这个注解处理器要处理的注解是哪些
*
* @return 返回我们要处理的注解
*/
@Override
public Set getSupportedAnnotationTypes() {
Set types = new HashSet<>();
/**
* 拿到我们注解处理器名字
* 1、getCanonicalName() 是获取所传类从java
* 语言规范定义的格式输出。
* 2、getName() 是返回实体类型名称
* 3、getSimpleName() 返回从源代码中返回实例的名称。
*/
types.add(BindPath.class.getCanonicalName());
return types;
}
/**
* 声明我们的注解处理器支持的java sdk版本号
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
/**
* 拿到父类的processingEnv 获取getSourceVersion 这些基本都是写死的
*/
return processingEnv.getSourceVersion();
}
/**
* 这个方法 是注解处理器的核心方法 写文件就放在这里面进行写
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
/**
*获取BindPath节点 通过这个Api能拿到所有这个
*模块中所有的用到了BindPath注解的节点
*比如我再LoginActivity这个类上用到了@BindPath注解
* 获取到的 Set extends Element>集合就是被@BindPath注解后的类的所有节点
*/
Set extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
Map map = new HashMap<>();
//这里我们遍历所有的节点 节点指的是:比如一个activity中我们有类,成员变量
//方法,等待这些都表示不同的节点,这里获取的就是activity类的节点
for (Element element : elementsAnnotatedWith) {
//TypeElement这个是中java中的 在Android中没有
TypeElement typeElement = (TypeElement) element;
//这个获取的就是存放activity集合 map中的key
String key = typeElement.getAnnotation(BindPath.class).value();
String value = typeElement.getQualifiedName().toString();
map.put(key, value);
}
//开始写文件,写文件其实就是写上文我们说到ActivityUtils,我们不可能一个一个去写,所有就通过注解处理器去自动生成
if (map.size() > 0) {
Writer writer = null;
//创建类名 加上时间戳为了防止生成相同的类名
String utilName = "ActivityUtils" + System.currentTimeMillis();
//创建源码文件
try {
JavaFileObject sourceFile = filer.createSourceFile("com.yike.util." + utilName);
writer = sourceFile.openWriter();
/**
* ActivityUtils复制过来的 注意修改包名相同 ,类名替换成utilName
*/
writer.write("package com.yike.util;\n" +
"\n" +
"import android.util.Log;\n" +
"import com.yike.arouter.Arouter;\n" +
"import com.yike.arouter.IArouter;\n" +
"import com.yike.login.ui.LoginActivity;\n" +
"\n" +
"/**\n" +
" * Author:wangcaiwen\n" +
" * Date:2019-07-16\n" +
" * Description:1\n" +
" *\n" +
" */\n" +
"public class "+utilName+" implements IArouter {\n" +
" private static final String TAG = \"ActivityUtils\";\n" +
" @Override\n" +
" public void putActivity() {\n" +
" Log.e(TAG, \"putActivity: \" );");
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
String value = map.get(key);
writer.write(" Arouter.getInstance().putActivity(\"" + key + "\", " + value + ".class);\n");
}
writer.write(" }\n" +
"}\n");
} catch (IOException e) {
e.printStackTrace();
}finally {
if (writer!=null){
try {
//这里注意要close
writer.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
return false;
}
}
上面写完后我们可以运行一下,记得是运行app整个项目
这时候我们会看到如图:
我的login module下build->generated->source->kapt自动下生成了ActiviUtils+时间戳这个类,具体内容如下
注意:kotlin因为用的kapt所以在kapt这个文件下,java应该是在apt下面
package com.yike.util;
import android.util.Log;
import com.yike.arouter.Arouter;
import com.yike.arouter.IArouter;
import com.yike.login.ui.LoginActivity;
/**
* Author:wangcaiwen
* Date:2019-07-16
* Description:1
*
*/
public class ActivityUtils1563359074359 implements IArouter {
private static final String TAG = "ActivityUtils";
@Override
public void putActivity() {
Log.e(TAG, "putActivity: " ); Arouter.getInstance().putActivity("login/login", com.yike.login.ui.LoginActivity.class);
}
}
这时候基本流程就差不多了
我们再回看到Arouter这个类里面init初始化方法里有这么一个逻辑
public void init(Application context){
this.context = context;
List classNmaes = getClassNmae("com.yike.util");
for (String classNmae : classNmaes) {
Log.e(TAG, "init: "+classNmae );
try {
Class> aClass = Class.forName(classNmae);
//判断这个类是否是IRouter的实现类
//如果是IArouter的实现类
if (IArouter.class.isAssignableFrom(aClass)){
IArouter iArouter = (IArouter) aClass.newInstance();
iArouter.putActivity();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
这个方法需要在application里初始化一下
Arouter.getInstance().init(this)
这样我们每次启动app都会走这个逻辑,同时调用put方法将路由和activity添加到集合中,然后在调用jumpActivity方法就可以跳转了
项目地址:
组件化&自定义ARouter