美文网首页
手撸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