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的典型应用场景是:
- 端外、Push、h5调用native代码
- 无需下沉接口的组件化通信,有数据返回能力
- 实现增强版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底层原理