DRouter的底层实现浅析

Posted 冬天的毛毛雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DRouter的底层实现浅析相关的知识,希望对你有一定的参考价值。

前言

DRouter是滴滴乘客端自研的一套android路由框架,基于平台化解耦的思想,为组件间通信服务。该项目以功能全面、易用为原则,支持各种路由场景,在页面路由、服务获取和过滤、跨进程及跨应用、VirtualApk插件支持等方面都能提供多样化的服务。目前已在滴滴乘客端、顺风车、单车、国际化、滴滴定制车中控、滴滴车载屏等十多个滴滴的app内使用,得到各种场景的验证。

接入方式与使用

接入

github上的项目地址:github.com/didi/DRoute…

在项目根目录下的build.gradle下添加插件依赖:

buildscript     
	dependencies         
		classpath "io.github.didi:drouter-plugin-proxy:1.0.1"
	

在主module的build.gradle文件中应用插件:

plugins 
    id 'com.didi.drouter'

在主module的build.gradle文件中的dependencies中添加依赖:

dependencies 
		api "io.github.didi:drouter-api:2.1.0"

引入成功后,需要在Application中进行初始化:

DRouter.init(application);

使用方法

这里只做简述,详情参考官方文档:github.com/didi/DRoute…

Activity、Fragment、View

静态注册

Activity、Fragment、View只支持静态跳转。在Activity注册路由地址:

@Router(scheme = "didi", host = "router", path = "/test1")
class TestActivity1 : AppCompatActivity()
发起页面跳转
            DRouter.build("didi://router/test1")
                .putExtra("tag", this.javaClass.name)
                .start(this, object : RouterCallback.ActivityCallback() 
                    // Activity回调
                    override fun onActivityResult(resultCode: Int, data: Intent?) 
                    
                )

Handler

Handler的典型应用场景是:

  1. 端外、Push、h5调用native代码
  2. 无需下沉接口的组件化通信,有数据返回能力
  3. 实现增强版eventbus

Handler支持静态和动态两种注册方式。

静态注册
@Router(scheme = "didi", host = "router", path = "/sendOrder",
        thread = Thread.WORKER)   //指定执行线程
public class SendOrderHandler implements IRouterHandler 
    @Override
    void handle(@NonNull Request request, @NonNull Result result);
        // result可以添加数据,会返回给调用方
        // 如果需要拦截后面的所有结点可以执行,默认不拦截
        request.getInterceptor().onInterrupt();
    

动态定义和注册

在内部注册一个监听者,不会重新实例化,类似EventBus,比EventBus增加了数据返回的能力 如果注册时使用了lifecycleOwner,会自动解注册

// 动态注册
IRegister register = 
       DRouter.register(
              RouterKey.build("/dynamic/handler").setLifecycleOwner(lifecycleOwner), 
              new IRouterHandler() 
                     @Override
                     public void handle(@NonNull Request request, @NonNull Result result) 
                     
              );
// 解注册,如果注册时使用了生命周期,则可省
register.unregister();
发起导航
DRouter.build("didi://router/sendOrder")
       .setHoldTimeout(3000)         // 设置超时时间内未返回则自动回调callback
       .start(context, new RouterCallback() 
            @Override
            public void onResult(@NonNull Result result) 
                // 只有目标被释放后才会回调,且不会阻塞该线程
            
       );

Hold

正常情况下Activity被start后以及RouterHandler的handle方法执行完成后,RouterCallback中的Result会立刻返回,如果希望RouterCallback中的Result可以等待目标某个触发时机然后才返回Result,同时不阻塞当前线程,可以考虑暂存Result

应用场景
  • 启动登录页,等待用户真正登陆成功后回调
  • 用户端外冷启动App,需要等待MainActivity的onResume回调或某些事情准备好后再回调
  • RouterHandler有耗时任务,希望等待执行完后再返回给调用方,又不阻塞调用方
配置

Activity和RouterHandler增加hold参数,就启用了HoldResult

异步Activity
@Router(scheme = "didi", host = "router", path = "/login"
        hold = "true")
public class ActivityLogin extends Activity 

    @Override
    protected void onCreate() 
        super.onCreate();
        Request request = RouterHelper.getRequest(this);
        Result result = RouterHelper.getResult(this);

        // 根据业务,在某个时间点触发这个释放(必须有)
        RouterHelper.release(this);
    

异步RouterHandler
@Router(scheme = "didi", host = "router", path = "/sendOrder",
        hold = "true")
public class SendOrderHandler implements IRouterHandler 
    @Override
    void handle(@NonNull Request request, @NonNull Result result);

        // 根据业务,在某个时间点触发这个释放(必须有)
        RouterHelper.release(request);
    

发起导航
DRouter.build("didi://router/sendOrder")
       .setHoldTimeout(3000)         // 设置超时时间内未返回则自动回调callback
       .start(context, new RouterCallback() 
            @Override
            public void onResult(@NonNull Result result) 
                // 只有目标被释放后才会回调,且不会阻塞该线程
            
       );

Interceptor

拦截器可以在打开指定页面时进行一些行为,比如:

  • 目标页面需要某种权限,比如某些场景下需要判断用户是否已登录
  • 目标页面需要先进行某项操作,比如打开定位功能
定义拦截器

全局拦截器是指任意请求都会执行

@Interceptor(name = "interceptor1",          //可选,可以使用名字替代类耦合的方式来引用到
             priority = 1,                   //可选,优先级,值越大优先级越高
             global = true)                  //可选,是否是全局拦截器
public class LoginInterceptor implements IRouterInterceptor 
    @Override
    public void handle(@NonNull Request request) 

        if (isGo?) 
            request.getInterceptor().onContinue();
         else 
            request.getInterceptor().onInterrupt();
        
    

应用到页面
@Router(path = "/main", interceptor = LoginInterceptor.class)
public class MainActvity extends Activity 

源码分析

初始化流程

上述为DRouter的初始化流程图,上述为DRouter的初始化流程图,DRouter的项目结构为:

  • drouter-api:直接供业务层调用
  • drouter-api-stub:定义了MetaLoader和它的实现类,在编译期生成路由表代码
  • drouter-plugin:真正执行DRouter构建流程的插件,执行RouterTask去创建RouterLoader的真正实现并处理注解,加载路由表。
  • drouter-plugin-proxy:是一个Gradle Task,主要任务是拉取最新版本的drouter-plugin.jar包

其中drouter-api是直接供业务层调用的module,在api module中,依赖了drouter-api-stub:

    compileOnly project(':drouter-api-stub')

只在编译时有效,不会参与打包 可以在自己的moudle中使用该方式依赖一些比如com.android.support,gson这些使用者常用的库,避免冲突。

drouter-api-stub中,定义了MetaLoader和它的实现类:RouterLoader、ServiceLoader和InterceptorLoader,而这些子类的方法都是空实现,在编译期,会通过javaassit API和Gradle Transform API来实现具体的方法。

项目中直接引用的是drouter-plugin-proxy,根目录下的build.gradle中:

classpath 'io.github.didi:drouter-plugin-proxy:1.0.1'

drouter-plugin-proxy是一个Gradle Task,主要任务是拉取最新版本的drouter-plugin.jar包

drouter-plugin内部是真正执行DRouter构建流程的插件,执行RouterTask去创建RouterLoader的真正实现并处理注解,加载路由表。

所以DRouter的初始化,可以从编译和运行两个时期去分析。

编译期

通过Transform在项目编译过程中在class编译为dex的过程中,能够获取到所有的class类,并进行处理。DRouter用到了groovy插件 + Transform API在编译期自动生成了路由表。首先,在drouter-plugin-proxy module中,创建了一个RouterPlugin:

class RouterPlugin implements Plugin<Project> 
    @Override
    void apply(Project project) 
        ProxyUtil.confirm(project)
        project.extensions.create('drouter', RouterSetting)
        project.android.registerTransform(new TransformProxy(project))
    

RouterPlugin最后注册了一个TransformProxy:

class TransformProxy extends Transform 
    // ... 
  	@Override
    void transform(TransformInvocation invocation) throws TransformException, 
  			InterruptedException, IOException 
        // 插件版本
        String pluginVersion = ProxyUtil.getPluginVersion(invocation)
        if (pluginVersion != null) 
            File pluginJar = new File(project.rootDir, ".gradle/drouter/drouter-plugin-$pluginVersion.jar")
            if (!pluginJar.exists())  // 若不存在,尝试下载
                // ...
            
            if (pluginJar.exists()) 
                URLClassLoader newLoader = new URLClassLoader([pluginJar.toURI().toURL()] as URL[], getClass().classLoader)
                Class<?> transformClass = newLoader.loadClass("com.didi.drouter.plugin.RouterTransform") //【1】
                ClassLoader threadLoader = Thread.currentThread().getContextClassLoader()
                Thread.currentThread().setContextClassLoader(newLoader)
                Constructor constructor = transformClass.getConstructor(Project.class)
                Transform transform = (Transform) constructor.newInstance(project)
                transform.transform(invocation)	
                Thread.currentThread().setContextClassLoader(threadLoader)
                return
             else 
                ProxyUtil.Logger.e("Error: there is no drouter-plugin jar")
            
         else 
            ProxyUtil.Logger.e("Error: there is no drouter-plugin version")
        
        copyFile(invocation)
    

TransformProxy在下载最新的drouter-plugin插件,并在【1】处,创建了一个com.didi.drouter.plugin.RouterTransform对象,并执行了transform(invocation) ,查看RouterTransform的代码:

class RouterTransform extends Transform 
    @Override
    void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException 
        long timeStart = System.currentTimeMillis()
        SystemUtil.debug = project.drouter.debug
        File cacheFile = new File(tmpDir, "cache")
        boolean configChanged = SystemUtil.configChanged(project)
        boolean useCache = !isWindow && invocation.incremental && project.drouter.cache && cacheFile.exists() && !configChanged
        if (useCache) 
            cachePathSet.addAll(cacheFile.readLines())
        
        if (!invocation.incremental) 
            invocation.outputProvider.deleteAll()
        
        compilePath = new ConcurrentLinkedQueue<>(project.android.bootClasspath)
        for (TransformInput transformInput : invocation.inputs) 
            handleDirectory(invocation, transformInput)
            handleJar(invocation, transformInput)
        
        File dest = invocation.outputProvider.getContentLocation("DRouterTable", TransformManager.CONTENT_CLASS,
                ImmutableSet.of(QualifiedContent.Scope.PROJECT), Format.DIRECTORY) // 【2】
        (new RouterTask(project, compilePath, cachePathSet, useCache, dest, tmpDir, project.drouter, isWindow)).run()	// 【3】
        FileUtils.writeLines(cacheFile, cachePathSet)
        Logger.v("Link: https://github.com/didi/DRouter")
        Logger.v("DRouterTask done, time used: " + (System.currentTimeMillis() - timeStart) / 1000f  + "s")
    

【2】处创建了一个DRouterTable目录,供RouterTask使用,最终执行到RouterTask的run方法:

    void run() 
        StoreUtil.clear();
        JarUtils.printVersion(project, compileClassPath);
        pool = new ClassPool();
        classClassify = new ClassClassify(pool, setting);
        startExecute();
    

    private void startExecute() 
        try 
            long timeStart = System.currentTimeMillis();
            for (File file : compileClassPath) 
                pool.appendClassPath(file.getAbsolutePath());
            
            if (useCache) 
                loadCachePaths(cachePathSet);
             else 
                loadFullPaths(compileClassPath);
            
            timeStart = System.currentTimeMillis();
            classClassify.generatorRouter(routerDir); // 【4】
         catch (Exception e) 
            JarUtils.check(e);
            String message = e.getMessage();
            if (message == null || !message.startsWith("Class:")) 
                e.printStackTrace();
            
            throw new GradleException("DRouter: Could not generate router table\\n" + e.getMessage());
         finally 
            executor.shutdown();
            FileUtils.deleteQuietly(wTmpDir);
        
    

【4】处ClassClassify的generatorRouter方法:

    public void generatorRouter(File routerDir) throws Exception 
        for (int i = 0; i < classifies.size(); i++) 
            AbsRouterCollect cf = classifies.get(i);
            cf.generate(routerDir);
        
    

AbsRouterCollect有三个实现类:

  • RouterCollect
  • InterceptorCollect
  • ServiceCollect

ClassClassify在初始化时就添加了这三个:

public class ClassClassify 
		private List<AbsRouterCollect> classifies = new ArrayList<>();

    public ClassClassify(ClassPool pool, RouterSetting setting) 
        // 初始化时就加入了三种AbsRouterCollect
        classifies.add(new RouterCollect(pool, setting));
        classifies.add(new ServiceCollect(pool, setting));
        classifies.add(new InterceptorCollect(pool, setting));
    
		// ...

这里以RouterCollect为例,RouterCollect的generate方法实现为:

    @Override
    public void generate(File routerDir) throws Exception 
      	// 创建RouterLoader类 
        CtClass ctClass = pool.makeClass(getPackageName() + ".RouterLoader");
        CtClass superClass = pool.get("com.didi.drouter.store.MetaLoader");
        ctClass.setSuperclass(superClass);

        StringBuilder builder = new StringBuilder();
        builder.append("public void load(java.util.Map data) \\n");
        for (CtClass routerCc : routerClass.values()) 
            try 
                StringBuilder interceptorClass = null;
                StringBuilder interceptorName = null;

                String uriValue = "";
                String schemeValue = "";
                String hostValue = "";
                String pathValue = "";
                Annotation annotation = null;
                String type;
                int thread = 0;
                int priority = 0;
                boolean hold = false;
                if (routerCc.hasAnnotation(Router.class)) 
                    uriValue = ((Router) routerCc.getAnnotation(Router.class)).uri();
                    schemeValue = ((Router) routerCc通过子类实现KVO,浅析KVO底层原理

hashMap底层源码浅析

浅析CountDownLatch闭锁底层实现原理

浅析CountDownLatch闭锁底层实现原理

浅析CountDownLatch闭锁底层实现原理

SEAL全同态加密开源库 BFV-源码浅析