美文网首页
手撸arouter——自己实现router功能

手撸arouter——自己实现router功能

作者: 慢游的海牛 | 来源:发表于2020-02-10 12:07 被阅读0次

标题有点夸大了,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添加一个清单文件

QQ图片20200210111207.png

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

参考资料

Android组件化开发,组件间的Activity页面跳转

动手撸一个ARouter (ARouter源码分析)

相关文章

网友评论

      本文标题:手撸arouter——自己实现router功能

      本文链接:https://www.haomeiwen.com/subject/vjkaxhtx.html