阿里ARouter简单实现原理

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<String> 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<String,Class<? extends Activity>>  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<String> getClassNmae(String packeagename) {
        //创建一个class对象集合
        List<String> 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<String> 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


WechatIMG407.jpeg

给大家看一下我的项目结构目录
annotation是注解;
annotation_complier是注解处理器 自动生成类;

WechatIMG408.jpeg

创建好这两个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<LoginPrensenter>(), 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<String> getSupportedAnnotationTypes() {
        Set<String> 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<String, String> 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<String> 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整个项目
这时候我们会看到如图:


WechatIMG411.jpeg

我的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<String> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343