本文首發(fā)于 vivo互聯(lián)網(wǎng)技術(shù) 微信公眾號 https://mp.weixin.qq.com/s/TXFt7ymgQXLJyBOJL8F6xg
作者:朱壹飛十多年的宜陽網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。營銷型網(wǎng)站建設(shè)的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整宜陽建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“宜陽網(wǎng)站設(shè)計(jì)”,“宜陽網(wǎng)站推廣”以來,每個客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
ARetrofit 是一款針對Android組件之間通信的路由框架,實(shí)現(xiàn)快速組件化開發(fā)的利器。本文主要講述 ARetrofit 實(shí)現(xiàn)的原理。
ARetrofit 是一款針對Android組件之間通信的路由框架,實(shí)現(xiàn)快速組件化開發(fā)的利器。
源碼鏈接:https://github.com/yifei8/ARetrofit
組件化架構(gòu) APP Demo, ARetrofit 使用實(shí)例:https://github.com/yifei8/HappyNote
Android組件化已經(jīng)不是一個新鮮的概念了,出來了已經(jīng)有很長一段時間了,大家可以自行Google,可以看到一堆相關(guān)的文章。
簡單的來說,所謂的組件就是Android Studio中的Module,每一個Module都遵循高內(nèi)聚的原則,通過ARetrofit 來實(shí)現(xiàn)無耦合的代碼結(jié)構(gòu),如下圖:
每一個 Module 可單獨(dú)作為一個 project 運(yùn)行,而打包到整體時 Module 之間的通信通過 ARetrofit 完成。
講原理之前,我想先說說為什么要ARetrofit。開發(fā)ARetrofit 這個項(xiàng)目的思路來源其實(shí)是 Retrofit,Retrofit 是Square公司開發(fā)的一款針對 Android 網(wǎng)絡(luò)請求的框架,這里不對Retrofit展開來講。主要是 Retrofit 框架使用非常多的設(shè)計(jì)模式,可以說 Retrofit 這個開源項(xiàng)目將Java的設(shè)計(jì)模式運(yùn)用到了極致,當(dāng)然最終提供的API也是非常簡潔的。如此簡潔的API,使得我們APP中的網(wǎng)絡(luò)模塊實(shí)現(xiàn)變得非常輕松,并且維護(hù)起來也很舒服。因此我覺得有必要將Android組件之間的通信也變得輕松,使用者可以優(yōu)雅的通過簡潔的API就可以實(shí)現(xiàn)通信,更重要的是維護(hù)起來也非常的舒服。
ARetrofit 基本原理可以簡化為下圖所示:
1.通過注解聲明需要通信的Activity/Fragment或者Class
2.每一個module通過annotationProcessor在編譯時生成待注入的RouteInject的實(shí)現(xiàn)類和AInterceptorInject的實(shí)現(xiàn)類。
這一步在執(zhí)行app[build]時會輸出日志,可以直觀的看到,如下圖所示:
注: AInjecton::Compiler >>> Apt interceptor Processor start... <<<
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = 3
注: AInjecton::Compiler auto generate class = com$$sjtu$$yifei$$eCGVmTMvXG$$AInterceptorInject
注: AInjecton::Compiler add path= 3 and class= LoginInterceptor
....
注: AInjecton::Compiler >>> Apt route Processor start... <<<
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = /login-module/ILoginProviderImpl
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = /login-module/LoginActivity
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = /login-module/Test2Activity
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = /login-module/TestFragment
注: AInjecton::Compiler auto generate class = com$$sjtu$$yifei$$VWpdxWEuUx$$RouteInject
注: AInjecton::Compiler add path= /login-module/TestFragment and class= null
注: AInjecton::Compiler add path= /login-module/LoginActivity and class= null
注: AInjecton::Compiler add path= /login-module/Test2Activity and class= null
注: AInjecton::Compiler add path= /login-module/ILoginProviderImpl and class= null
注: AInjecton::Compiler >>> Apt route Processor succeed <<<
3.將編譯時生成的類注入到RouterRegister中,這個類主要用于維護(hù)路由表和攔截器,對應(yīng)的[build]日志如下:
TransformPluginLaunch >>> ========== Transform scan start ===========
TransformPluginLaunch >>> ========== Transform scan end cost 0.238 secs and start inserting ===========
TransformPluginLaunch >>> Inserting code to jar >> /Users/yifei/as_workspace/ARetrofit/app/build/intermediates/transforms/TransformPluginLaunch/release/8.jar
TransformPluginLaunch >>> to class >> com/sjtu/yifei/route/RouteRegister.class
InjectClassVisitor >>> inject to class:
InjectClassVisitor >>> com/sjtu/yifei/route/RouteRegister{
InjectClassVisitor >>> public *** init() {
InjectClassVisitor >>> register("com.sjtu.yifei.FBQWNfbTpY.com$$sjtu$$yifei$$FBQWNfbTpY$$RouteInject")
InjectClassVisitor >>> register("com.sjtu.yifei.klBxerzbYV.com$$sjtu$$yifei$$klBxerzbYV$$RouteInject")
InjectClassVisitor >>> register("com.sjtu.yifei.JmhcMMUhkR.com$$sjtu$$yifei$$JmhcMMUhkR$$RouteInject")
InjectClassVisitor >>> register("com.sjtu.yifei.fpyxYyTCRm.com$$sjtu$$yifei$$fpyxYyTCRm$$AInterceptorInject")
InjectClassVisitor >>> }
InjectClassVisitor >>> }
TransformPluginLaunch >>> ========== Transform insert cost 0.017 secs end ===========
4.Routerfit.register(Class
前面講的是整體的框架設(shè)計(jì)思想,便于讀者從全局的覺得來理解ARetrofit的框架的架構(gòu)。接下來,將待大家個個擊破上面提到的annotationProcessor、 transform在項(xiàng)目中如何使用,以及動態(tài)代理、攔截器功能的實(shí)現(xiàn)等細(xì)節(jié)。
annotationProcessor(注解處理器)是javac內(nèi)置的一個用于編譯時掃描和處理注解(Annotation)的工具。簡單的說,在源代碼編譯階段,通過注解處理器,我們可以獲取源文件內(nèi)注解(Annotation)相關(guān)內(nèi)容。Android Gradle 2.2 及以上版本提供annotationProcessor的插件。
在ARetrofit中annotationProcessor對應(yīng)的module是auto-complier,在使用annotationProcessor之前首先需要聲明好注解。關(guān)于注解不太了解或者遺忘的同學(xué)可直接參考我之前寫的Java注解這篇文章,本項(xiàng)目中聲明的注解在auto-annotation這個module中,主要有:
@Extra 路由參數(shù)
@Flags intent flags
@Go 路由路徑key
@Interceptor 聲明自定義攔截器
@RequestCode 路由參數(shù)
@Route路由
@Uri
@IMethod 用于標(biāo)記注冊代碼將插入到此方法中(transform中使用)
創(chuàng)建自定義的注解處理器,具體使用方法可參考利用注解動態(tài)生成代碼,本項(xiàng)目中的注解處理器如下所示:
//這是用來注冊注解處理器要處理的源代碼版本。
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//這個注解用來注冊注解處理器要處理的注解類型。有效值為完全限定名(就是帶所在包名和路徑的類全名
@SupportedAnnotationTypes({ANNOTATION_ROUTE, ANNOTATION_GO})
//來注解這個處理器,可以自動生成配置信息
@AutoService(Processor.class)
public class IProcessor extends AbstractProcessor {
}
生成代碼的關(guān)鍵部分在GenerateAInterceptorInjectImpl 和?GenerateRouteInjectImpl中,以下貼出關(guān)鍵代碼:
public void generateAInterceptorInjectImpl(String pkName) {
try {
String name = pkName.replace(".",DECOLLATOR) + SUFFIX;
logger.info(String.format("auto generate class = %s", name));
TypeSpec.Builder builder = TypeSpec.classBuilder(name)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Inject.class)
.addSuperinterface(AInterceptorInject.class);
ClassName hashMap = ClassName.get("java.util", "HashMap");
//Map>
TypeName wildcard = WildcardTypeName.subtypeOf(Object.class);
TypeName classOfAny = ParameterizedTypeName.get(ClassName.get(Class.class), wildcard);
TypeName string = ClassName.get(Integer.class);
TypeName map = ParameterizedTypeName.get(ClassName.get(Map.class), string, classOfAny);
MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("getAInterceptors")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(map)
.addStatement("$T interceptorMap = new $T<>()", map, hashMap);
for (Map.Entry entry : interceptorMap.entrySet()) {
logger.info("add path= " + entry.getKey() + " and class= " + entry.getValue().simpleName());
injectBuilder.addStatement("interceptorMap.put($L, $T.class)", entry.getKey(), entry.getValue());
}
injectBuilder.addStatement("return interceptorMap");
builder.addMethod(injectBuilder.build());
JavaFile javaFile = JavaFile.builder(pkName, builder.build())
.build();
javaFile.writeTo(filer);
} catch (Exception e) {
e.printStackTrace();
}
}
public void generateRouteInjectImpl(String pkName) {
try {
String name = pkName.replace(".",DECOLLATOR) + SUFFIX;
logger.info(String.format("auto generate class = %s", name));
TypeSpec.Builder builder = TypeSpec.classBuilder(name)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Inject.class)
.addSuperinterface(RouteInject.class);
ClassName hashMap = ClassName.get("java.util", "HashMap");
//Map
TypeName wildcard = WildcardTypeName.subtypeOf(Object.class);
TypeName classOfAny = ParameterizedTypeName.get(ClassName.get(Class.class), wildcard);
TypeName string = ClassName.get(String.class);
TypeName map = ParameterizedTypeName.get(ClassName.get(Map.class), string, classOfAny);
MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("getRouteMap")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(map)
.addStatement("$T routMap = new $T<>()", map, hashMap);
for (Map.Entry entry : routMap.entrySet()) {
logger.info("add path= " + entry.getKey() + " and class= " + entry.getValue().enclosingClassName());
injectBuilder.addStatement("routMap.put($S, $T.class)", entry.getKey(), entry.getValue());
}
injectBuilder.addStatement("return routMap");
builder.addMethod(injectBuilder.build());
JavaFile javaFile = JavaFile.builder(pkName, builder.build())
.build();
javaFile.writeTo(filer);
} catch (Exception e) {
e.printStackTrace();
}
}
二、Transform
Android Gradle 工具在 1.5.0 版本后提供了 Transfrom API, 允許第三方 Plugin在打包dex文件之前的編譯過程中操作 .class 文件。這一部分面向高級Android工程師的,面向字節(jié)碼編程,普通工程師可不做了解。
寫到這里也許有人會有這樣一個疑問,既然annotationProcessor這么好用為什么還有Transform面向字節(jié)碼注入呢?這里需要解釋以下,annotationProcessor具有局限性,annotationProcessor只能掃描當(dāng)前module下的代碼,且對于第三方的jar、aar文件都掃描不到。而Transform就沒有這樣的局限性,在打包dex文件之前的編譯過程中操作.class 文件。
關(guān)于Transfrom API在Android Studio中如何使用可以參考Transform API?—?a real world example,順便提供一下字節(jié)碼指令方便我們讀懂ASM。
本項(xiàng)目中的Transform插件在AInject中,實(shí)現(xiàn)源碼TransformPluginLaunch如下,貼出關(guān)鍵部分:
/**
*
* 標(biāo)準(zhǔn)transform的格式,一般實(shí)現(xiàn)transform可以直接拷貝一份重命名即可
*
* 兩處todo實(shí)現(xiàn)自己的字節(jié)碼增強(qiáng)/優(yōu)化操作
*/
class TransformPluginLaunch extends Transform implements Plugin {
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
//todo step1: 先掃描
transformInvocation.inputs.each {
TransformInput input ->
input.jarInputs.each { JarInput jarInput ->
...
}
input.directoryInputs.each { DirectoryInput directoryInput ->
//處理完輸入文件之后,要把輸出給下一個任務(wù)
...
}
}
//todo step2: ...完成代碼注入
if (InjectInfo.get().injectToClass != null) {
...
}
}
/**
* 掃描jar包
* @param jarFile
*/
static void scanJar(File jarFile, File destFile) {
}
/**
* 掃描文件
* @param file
*/
static void scanFile(File file, File dest) {
...
}
}
注入代碼一般分為兩個步驟:
第一步:掃描
這一部分主要是掃描的內(nèi)容有:
注入類和方法的信息,是AutoRegisterContract的實(shí)現(xiàn)類和其中@IMethod,@Inject的方法。
待注入類的和方法信息,是RouteInject 和 AInterceptorInject實(shí)現(xiàn)類且被@Inject注解的。
class InjectClassVisitor extends ClassVisitor {
...
class InjectMethodAdapter extends MethodVisitor {
InjectMethodAdapter(MethodVisitor mv) {
super(Opcodes.ASM5, mv)
}
@Override
void visitInsn(int opcode) {
Log.e(TAG, "inject to class:")
Log.e(TAG, own + "{")
Log.e(TAG, " public *** " + InjectInfo.get().injectToMethodName + "() {")
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
InjectInfo.get().injectClasses.each { injectClass ->
injectClass = injectClass.replace('/', '.')
Log.e(TAG, " " + method + "(\"" + injectClass + "\")")
mv.visitVarInsn(Opcodes.ALOAD, 0)
mv.visitLdcInsn(injectClass)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, own, method, "(Ljava/lang/String;)V", false)
}
}
Log.e(TAG, " }")
Log.e(TAG, "}")
super.visitInsn(opcode)
}
...
}
...
}
定義:為其它對象提供一種代理以控制對這個對象的訪問控制;在某些情況下,客戶不想或者不能直接引用另一個對象,這時候代理對象可以在客戶端和目標(biāo)對象之間起到中介的作用。
Routerfit.register(Class
本項(xiàng)目相關(guān)源碼:
public final class Routerfit {
...
private T create(final Class service) {
RouterUtil.validateServiceInterface(service);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[]{service}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
ServiceMethod
這里ServiceMethod是一個非常重要的類,使用了外觀模式,主要用于解析方法中的被注解所有信息并保存起來。
本項(xiàng)目中的攔截器鏈設(shè)計(jì),使得使用者可以非常優(yōu)雅的處理業(yè)務(wù)邏輯。如下:
@Interceptor(priority = 3)
public class LoginInterceptor implements AInterceptor {
private static final String TAG = "LoginInterceptor";
@Override
public void intercept(final Chain chain) {
//Test2Activity 需要登錄
if ("/login-module/Test2Activity".equalsIgnoreCase(chain.path())) {
Routerfit.register(RouteService.class).launchLoginActivity(new ActivityCallback() {
@Override
public void onActivityResult(int i, Object data) {
if (i == Routerfit.RESULT_OK) {//登錄成功后繼續(xù)執(zhí)行
Toast.makeText(ActivityLifecycleMonitor.getTopActivityOrApp(), "登錄成功", Toast.LENGTH_LONG).show();
chain.proceed();
} else {
Toast.makeText(ActivityLifecycleMonitor.getTopActivityOrApp(), "登錄取消/失敗", Toast.LENGTH_LONG).show();
}
}
});
} else {
chain.proceed();
}
}
}
這一部分實(shí)現(xiàn)的思想是參考了okhttp中的攔截器,這里使用了java設(shè)計(jì)模式責(zé)任鏈模式,具體實(shí)現(xiàn)歡迎閱讀源碼。
基本上讀完本文可以對 ARetrofit 的核心原理有了很清晰的理解.簡單來說 ARetrofit 通過 annotationProcessor 在編譯時獲取路由相關(guān)內(nèi)容,通過 ASM 實(shí)現(xiàn)了可跨模塊獲取對象,最終通過動態(tài)代理實(shí)現(xiàn)面向切面編程(AOP)。
ARetrofit 相對于其他同類型的路由框架來說,其優(yōu)點(diǎn)是提供了更加簡潔的 API,其中高階用法對開發(fā)者提供了更加靈活擴(kuò)展方式,開發(fā)者還可以結(jié)合 RxJava 完成復(fù)雜的業(yè)務(wù)場景。具體可以參考 ARetrofit 的基本用法,以及 Issues。
————? 參考資料? ?————
Java注解:https://www.jianshu.com/p/ef1146a771b5
利用注解動態(tài)生成代碼:https://blog.csdn.net/Gaugamela/article/details/79694302