Android组件化系列之手写组件路由架构篇(下)

上一篇对整个框架结构进行了简单的介绍,本篇将较为细致的介绍下实现细节。

一、router_annotation模块

主要有两个注解Route和Extra,以一个RouteMeta类;

Route注解用来声明路由路径,路径至少是两级,目的是为了将不同module的跳转路径分到不同的路由分组中:

//元注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
    //路由的路径,标识一个路由节点
    String path();
    //将路由节点进行分组,可以实现按组动态加载
    String group() default "";
}

Extra注解用来声明额外信息,实现数据或ISERVICE对象(自定义的接口)在模块间的传递:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Extra {
    String name() default "";
}

RouteMeta用来保存路由信息,通过解析Route注解获取。路由页面有两种类型:1.Activity,代表需要路由的Activity;2.ISERVICE,代表可以传递的ISERVICE对象,实现了ISERVICE的接口,都可以通过路由来传递。

public class RouteMeta {
    //路由页面的类型:Activity或者IService
    public enum Type {
        ACTIVITY,
        ISERVICE
    }

    private Type type;
    //节点 (Activity)
    private Element element;
    //注解使用的类对象
    private Class<?> destination;
    //路由地址
    private String path;
    //路由分组
    private String group;
    ...
}

注意:annotation模块与compiler模块的中文注释使用GBK格式,否则编译时会报错

二、router_compiler模块

compiler模块使用Javax的Processor来处理注解,使用google的AutoService来自动编译Processor类,使用javapoet来生成Java类。Javax是Java的库,Android不支持,所以compiler模块是Java的library模块。

没用过javapoet的同学,可以参考https://github.com/square/javapoet

1.添加配置

使用前,需要先在compiler模块的build.gradle中导入框架:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'

    implementation 'com.squareup:javapoet:1.11.1'

    implementation project(':router_annotation')

}

// java控制台输出中文乱码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

sourceCompatibility = "7"
targetCompatibility = "7"

2.处理Route注解

声明一个RouteProcessor继承Processor,用来处理Route注解。先给RouteProcessor添加注解,设置处理的参数:

//自动编译Processor类
@AutoService(Processor.class)
//处理器接收的参数 替代 {@link AbstractProcessor#getSupportedOptions()} 函数
@SupportedOptions(Consts.ARGUMENTS_NAME)
//指定使用的Java版本 替代 {@link AbstractProcessor#getSupportedSourceVersion()} 函数
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//注册给哪些注解的  替代 {@link AbstractProcessor#getSupportedAnnotationTypes()} 函数
@SupportedAnnotationTypes({Consts.ANN_TYPE_ROUTE})
public class RouteProcessor extends AbstractProcessor {

然后,初始化RouteProcessor,从ProcessingEnvironment中获取需要的处理工具:

/**
 * 初始化 从 {@link ProcessingEnvironment} 中获得一系列处理器工具
 *
 * @param processingEnvironment
 */
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    super.init(processingEnvironment);
    //获得apt的日志输出
    log = Log.newLog(processingEnvironment.getMessager());
    //获取节点
    elementUtils = processingEnvironment.getElementUtils();
    //获取类型
    typeUtils = processingEnvironment.getTypeUtils();
    //获取文件生成器
    filerUtils = processingEnvironment.getFiler();
    //参数是模块名 为了防止多模块/组件化开发的时候 生成相同的 xx$$ROOT$$文件
    Map<String, String> options = processingEnvironment.getOptions();
    if (!Utils.isEmpty(options)) {
        moduleName = options.get(Consts.ARGUMENTS_NAME);
    }
    log.i("RouteProcessor Parmaters:" + moduleName);
    if (Utils.isEmpty(moduleName)) {
        throw new RuntimeException("Not set Processor Parmaters.");
    }
}

然后,在process方法(注解的处理方法)中遍历被Route注解的节点,先判断节点的类型,再使用RouteMeta保存节点信息,然后验证节点的路由地址是否符合规则,然后使用categories() 根据分组名来保存节点信息到groupMap中;

然后,遍历groupMap中的节点信息,使用javapoet工具,根据分组名生成一个继承IRouteGroup接口的Java类,叫做分组信息类,用来保存每个分组的路由信息,比如:

public class DNRouter$$Group$$main implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/main/test", RouteMeta.build(RouteMeta.Type.ACTIVITY,SecondActivity.class, "/main/test", "main"));
    atlas.put("/main/service1", RouteMeta.build(RouteMeta.Type.ISERVICE,TestServiceImpl1.class, "/main/service1", "main"));
    atlas.put("/main/service2", RouteMeta.build(RouteMeta.Type.ISERVICE,TestServiceImpl2.class, "/main/service2", "main"));
  }
}

注意:不同的模块,不能使用相同的分组名。因为相同的分组名会生成相同的分组信息类,打包成APK时只保留一个。

然后,遍历所有的分组信息类,使用javapoet工具,根据模块名生成一个继承IRouteRoot接口的Java类,叫做表信息类,用来保存所有的分组信息类,比如:

public class DNRouter$$Group$$module1 implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/module1/test", RouteMeta.build(RouteMeta.Type.ACTIVITY,Module1Activity.class, "/module1/test", "module1"));
    atlas.put("/module1/service", RouteMeta.build(RouteMeta.Type.ISERVICE,TestServiceImpl.class, "/module1/service", "module1"));
  }
}

处理Route注解的生成类,到这里,Route注解就处理玩了。

3.处理Extra注解

声明一个ExtraProcessor继承Processor,用来处理Extra注解。基本流程与RouteProcessor一致,需要注意的是

Route注解只支持Activity和IService两种类型,Extra注解支持更多的类型,包括基本类型、数组、String、Object等,所以需要做更多的类型判断:

if (type == TypeKind.BOOLEAN.ordinal()) {
    statement += "getBooleanExtra($S, " + defaultValue + ")";
} else if (type == TypeKind.BYTE.ordinal()) {
    statement += "getByteExtra($S, " + defaultValue + ")";
} else if (type == TypeKind.SHORT.ordinal()) {
    statement += "getShortExtra($S, " + defaultValue + ")";
} else if (type == TypeKind.INT.ordinal()) {
    statement += "getIntExtra($S, " + defaultValue + ")";
} else if (type == TypeKind.LONG.ordinal()) {
    statement += "getLongExtra($S, " + defaultValue + ")";
} else if (type == TypeKind.CHAR.ordinal()) {
    statement += "getCharExtra($S, " + defaultValue + ")";
} else if (type == TypeKind.FLOAT.ordinal()) {
    statement += "getFloatExtra($S, " + defaultValue + ")";
} else if (type == TypeKind.DOUBLE.ordinal()) {
    statement += "getDoubleExtra($S, " + defaultValue + ")";
} else {
    //数组类型
    if (type == TypeKind.ARRAY.ordinal()) {
        addArrayStatement(statement, fieldName, extraName, typeMirror, element);
    } else {
        //Object
        addObjectStatement(statement, fieldName, extraName, typeMirror, element);
    }
    return;
}

然后根据类名+$$Extra,使用javapoet生成一个继承IExtra的Extra信息类,用来给添加了Extra注解的成员变量复制,例如:

public class Module2Activity$$Extra implements IExtra {
  @Override
  public void loadExtra(Object target) {
    Module2Activity t = (Module2Activity)target;
    t.msg = t.getIntent().getStringExtra("msg");
  }
}

如果使用Extra注解的成员变量是TestService类型(自定义的接口,用来传递对象),并且Extra注解中设置了路由路径,则会生成下面的类:

public class SecondActivity$$Extra implements IExtra {
  @Override
  public void loadExtra(Object target) {
    SecondActivity t = (SecondActivity)target;
    t.a = t.getIntent().getStringExtra("a");
    t.b = t.getIntent().getIntExtra("b", t.b);
    t.c = t.getIntent().getShortExtra("c", t.c);
    t.d = t.getIntent().getLongExtra("d", t.d);
    t.e = t.getIntent().getFloatExtra("e", t.e);
    t.f = t.getIntent().getDoubleExtra("f", t.f);
    t.g = t.getIntent().getByteExtra("g", t.g);
    t.h = t.getIntent().getBooleanExtra("h", t.h);
    t.i = t.getIntent().getCharExtra("i", t.i);
    t.aa = t.getIntent().getStringArrayExtra("aa");
    t.bb = t.getIntent().getIntArrayExtra("bb");
    t.cc = t.getIntent().getShortArrayExtra("cc");
    t.dd = t.getIntent().getLongArrayExtra("dd");
    t.ee = t.getIntent().getFloatArrayExtra("ee");
    t.ff = t.getIntent().getDoubleArrayExtra("ff");
    t.gg = t.getIntent().getByteArrayExtra("gg");
    t.hh = t.getIntent().getBooleanArrayExtra("hh");
    t.ii = t.getIntent().getCharArrayExtra("ii");
    t.j = t.getIntent().getParcelableExtra("j");
    Parcelable[] jj = t.getIntent().getParcelableArrayExtra("jj");
    if( null != jj) {
      t.jj = new TestParcelable[jj.length];
      for (int i = 0; i < jj.length; i++) {
        t.jj[i] = (TestParcelable)jj[i];
      }
    }
    t.k1 = t.getIntent().getParcelableArrayListExtra("k1");
    t.k2 = t.getIntent().getParcelableArrayListExtra("k2");
    t.k3 = t.getIntent().getStringArrayListExtra("k3");
    t.k4 = t.getIntent().getIntegerArrayListExtra("k4");
    t.test = t.getIntent().getIntExtra("hhhhhh", t.test);
    t.testService1 = (TestService) DNRouter.getInstance().build("/main/service1").navigation();
    t.testService2 = (TestService) DNRouter.getInstance().build("/main/service2").navigation();
    t.testService3 = (TestService) DNRouter.getInstance().build("/module1/service").navigation();
    t.testService4 = (TestService) DNRouter.getInstance().build("/module2/service").navigation();
  }
}

处理Extra注解的生成类,都放在com.example.dn_component下

三、router_core模块

router_core模块是用来实现跳转逻辑的。

1.初始化

在Application中进行初始化,加载表信息类。

先定义一个路由表Warehouse,来保存所有的路由信息。

public class Warehouse {
    // root 映射表 保存分组信信息类
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    // group 映射表 保存所有的路由页面
    static Map<String, RouteMeta> routes = new HashMap<>();
    // group 映射表 保存所有的IService对象
    static Map<Class, IService> services = new HashMap<>();
}

然后,遍历所有的dex文件,查找所有的使用javapoet生成的分组信息类和表信息类。查找是一个耗时操作,使用ThreadPoolExecutor维护一个线程池,来查找所有包名为gsw.toolrouter.routes 类,并使用使用同步计数器来判断查找是否完成。

public static Set<String> getFileNameByPackageName(...) {
    final Set<String> classNames = new HashSet<>();
    //获得程序所有的apk(instant run会产生很多split apk)
    List<String> paths = getSourcePaths(context);
    //使用同步计数器判断均处理完成
    final CountDownLatch parserCtl = new CountDownLatch(paths.size());
    ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor.newDefaultPoolExecutor(paths
            .size());
    for (final String path : paths) {
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                DexFile dexfile = null;
                try {
                    //加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类
                    dexfile = new DexFile(path);
                    Enumeration<String> dexEntries = dexfile.entries();
                    while (dexEntries.hasMoreElements()) {
                        String className = dexEntries.nextElement();
                        if (className.startsWith(packageName)) {
                            classNames.add(className);
                        }
                    }
                } 
                ...
                    //释放1个
                    parserCtl.countDown();
                }
            }
        });
    }
    //等待执行完成
    parserCtl.await();
    return classNames;
}

然后调用表信息类(比如:NDRouter的loadInfo方法,将所有的分组信息类存放到Warehouse的groupsIndex中)

private static void loadInfo()  {
    //获得所有 apt生成的路由类的全类名 (路由表)
    Set<String> routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    for (String className : routerMap) {
        if (className.startsWith(ROUTE_ROOT_PAKCAGE + "." + SDK_NAME + SEPARATOR +
                SUFFIX_ROOT)) {
            // root中注册的是分组信息 将分组信息加入仓库中
            ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto
                    (Warehouse.groupsIndex);
        }
    }
}

2.创建跳卡

先根据路由地址生成一个继承RouteMeta的跳卡Postcard,在Postcard中保存路由地址、分组名以及要传递的数据。

调用Postcard的withXXX方法,来添加要传递的数据,保存Postcard的一个Bundle对象中,比如:

public Postcard withParcelableArray(@Nullable String key, @Nullable Parcelable[] value) {
    mBundle.putParcelableArray(key, value);
    return this;
}

还可以设置Activity的转场动画:

public Postcard withTransition(int enterAnim, int exitAnim) {
    this.enterAnim = enterAnim;
    this.exitAnim = exitAnim;
    return this;
}

然后调用Postcard的navigation()进行页面跳转。

3.调转页面

调用Postcard的navigation()进行页面跳转时,如果是activity页面,就直接调转;如果是IService页面,就放回这个IService对象。

先根据分组名从Warehouse的groupsIndex中找到分组信息类,创建这个类,调用它的loadInfo(),把这个分组所有的路由页面,按照路由路径添加到Warehouse.routes中。

//创建并调用 loadInto 函数,然后记录在仓库
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(card
        .getGroup());
if (null == groupMeta) {
    throw new NoRouteFoundException("没找到对应路由: " + card.getGroup() + " " +
            card.getPath());
}
IRouteGroup iGroupInstance;
try {
    iGroupInstance = groupMeta.getConstructor().newInstance();
} catch (Exception e) {
    throw new RuntimeException("路由分组映射表记录失败.", e);
}
iGroupInstance.loadInto(Warehouse.routes);
//已经准备过了就可以移除了 (不会一直存在内存中)
Warehouse.groupsIndex.remove(card.getGroup());

然后根据路由路径查找路由页面的的class对象,保存在Postcard中。如果路由页面是IService实现类,还要将它保存在Postcard和Warehouse的services中。

//路由页面的class对象类
card.setDestination(routeMeta.getDestination());
//设置路由页面的类型(activity 或IService实现类)
card.setType(routeMeta.getType());
...
Class<?> destination = routeMeta.getDestination();
IService service = Warehouse.services.get(destination);
if (null == service) {
    try {
        service = (IService) destination.getConstructor().newInstance();
        Warehouse.services.put(destination, service);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
card.setService(service);

然后,进行跳转。如果路由页面是activity,就根据路由页面的class对象生成Intent,并且添加需要传递的数据

,再调用startActivity()就可以实现跳转了。如果路由页面是IService,就返回postcard.getService()。

switch (postcard.getType()) {
case ACTIVITY:
    final Context currentContext = null == context ? mContext : context;
    final Intent intent = new Intent(currentContext, postcard.getDestination());
    intent.putExtras(postcard.getExtras());
    int flags = postcard.getFlags();
    if (-1 != flags) {
        intent.setFlags(flags);
    } else if (!(currentContext instanceof Activity)) {
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //可能需要返回码
            if (requestCode > 0) {
                ActivityCompat.startActivityForResult((Activity) currentContext, intent,
                        requestCode, postcard.getOptionsBundle());
            } else {
                ActivityCompat.startActivity(currentContext, intent, postcard
                        .getOptionsBundle());
            }

            if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) &&
                    currentContext instanceof Activity) {
                //老版本
                ((Activity) currentContext).overridePendingTransition(postcard
                                .getEnterAnim()
                        , postcard.getExitAnim());
            }
            //跳转完成
            if (null != callback) {
                callback.onArrival(postcard);
            }
        }
    });
    break;
case ISERVICE:
    return postcard.getService();
default:
    break;
}

4.接收传递的数据

使用调用Postcard的withXXX方法,就可以传递数据。接收传递的数据,需要通过Extra注解实现。

首先,在需要接收路由数据的页面,根据withXXX方法中传递key和数据类型,声明一个成员属性,并添加Extra注解,比如:

@Extra
long key1;

然后,调用DNRouter.getInstance().inject(this)来加载Extra信息。根据Activity的类名+$$Extra得到Extra信息类的类名,比如:ActivityToolRouterDemo1$$Extra,然后创建这个Extra信息类的对象,调用它的loadExtra()方法,给Activity的key1赋值。

public class Module1Activity$$Extra implements IExtra {
  @Override
  public void loadExtra(Object target) {
    Module1Activity t = (Module1Activity)target;
    t.msg = t.getIntent().getStringExtra("msg");
  }
}

如果给TestService类型的成员添加注解,则需要设置路由路径。

//给IService接口设置Extra注解,用来向其他路由页面传递IService对象
@Extra(name = "/main/service1")
TestService testService1;

到这里,core模块就基本完成了。下面说ToolRouter的使用。

四、ToolRouter的使用

ToolRouter的使用和Arouter基本一致。

1.添加依赖和配置

在application模块和library模块的build.gradle中添加如下配置:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    annotationProcessor project(':router_compiler')
    implementation project(':base')

    if (isModule){
        implementation project(':module2')
        implementation project(':module1')
    }

}

2.初始化SDK

在application模块的Application中初始化ARouter

DNRouter.init(getApplication());

在需要路由的Acitivity中加载Extra信息类,用来处理Extra注解,接收传递的数据:

DNRouter.getInstance().inject(this);

3.添加注解

在application模块和library模块的需要路由的Activity中添加Route注解,注解必须设置两级,因为初始化路由模块是按分组进行的。

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/module1/test")
public class Module1Activity extends Activity {

在需要接收路由数据的页面,根据withXXX方法中传递的key和数据类型,声明一个成员属性,并添加Extra注解。

//传递普通数据
@Extra
long key1;
@Extra
String key2;

4.发起路由操作

// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();

// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
            .withLong("key1", 666L)
            .withString("key2", "888")
            .withObject("key3", new Test("Jack", "Rose"))
            .navigation();

5.添加混淆规则

-keep public gsw.toolrouter.routes.**{*;}
-keep class * implements gsw.toolrouter.core.template.**{*;}

6.跳转自定义对象

先创建一个类实现TestService,并添加Route注解

@Route(path = "/main/service1")
public class TestServiceImpl1 implements TestService {
    @Override
    public void test(Context context) {
        Toast.makeText(context, "我是demo1模块测试服务通信1", Toast.LENGTH_SHORT).show();
        Log.i("Service", "我是demo1模块测试服务通信1");
    }
}

然后,通过navigation()获取这个对象

TestService t=(TestService) DNRouter.getInstance().build("/main/service1").navigation()

另外,如果声明一个TestService类型的成员变量,并添加Extra注解(需要设置路由地址),就不需要通过navigation()获取,框架自动给这个成员变量赋值:

//给IService接口设置Extra注解,用来向其他路由页面传递IService对象
@Extra(name = "/main/service1")
TestService testService1;

testService1.test();

最后

代码地址:https://github.com/buder-cp/DesignPattern/tree/master/buder_DN_component

发布了189 篇原创文章 · 获赞 81 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/cpcpcp123/article/details/103947841