ToyBricks簡介以及原理分析
ToyBricks背景
我始終認為,在高內聚,低耦合的原則下,進行組件化,模塊化,插件化都是移動應用開發的趨勢。
為什麼這麼說呢?下麵我們舉個栗子:
大家都知道,以前Android應用開發中,可以使用HttpClient或者HttpUrlConnection來進行http訪問。這裏假設有一個耦合嚴重,但代碼量巨大的項目,使用了基於HttpClient封裝的loopj/android-async-http來進行http訪問。但是,後來,Google明確支持使用HttpUrlConnection。此時,經過調研,你們覺得square/okhttp基於HttpUrlConnection,符合你們的要求。
現在,不管是否將HttpClient替換成okhttp,你們都可能麵臨以下困境:
1. 需求都做不完,根本沒有排期做這個替換。於是,你們麵臨離google的支持越來越遠,離風險越來越近的困境;
1. 辛辛苦苦耗費人力將HttpClient替換成okhttp。但由於兩者變化很大,需要投入很多測試資源,來重新確認這些接口是否正常訪問。一旦出現問題,還需要安排研發資源,去一一排查。
1. 替換的工作量太大。替換一部分之後,發現沒有足夠人力去繼續完成。於是,替換終止。整個工程又變得混亂和臃腫,同時包含了兩種http的封裝庫和調用。
模塊化可以有效解決這些問題。通用的做法,是按照業務,功能等將整個項目分成不同的模塊,由不同的研發測試小組負責。
每個模塊又分為接口和實現兩個部分。接口部分提供給模塊外部調用,而實現部分則禁止來自外部的調用。
那麼,如何將模塊的接口和實現部分關聯起來呢?通過APT工具,可以輕鬆地將接口部分和實現部分關聯起來。
APT,即Annotation Processing Tool,可以理解為“編譯時注解處理器工具”。
官方說明:
“The apt tool is a command-line utility for annotation processing. It includes a set of reflective APIs and supporting infrastructure to process program annotations (JSR 175). These reflective APIs provide a build-time, source-based, read-only view of program structure. They are designed to cleanly model the Java programming language's type system after the addition of generics (JSR 14).”
簡單理解如下:
apt工具是javac工具的一部分。在編譯時,apt工具首先會掃描工程下Java源碼中的編譯時注解,再根據預先定義的編譯時注解處理工具,生成指定的Java源碼文件。緊接著,生成的Java源碼文件和之前項目下的Java源碼一起,由javac工具來編譯成class。
但是,APT有一個局限性,就是隻會掃描Java源碼,不會掃描jar ,aar 和class 。也就是說,所有模塊需要以源碼形式存在。而現在通用的做法是,將模塊打包成jar或者aar,發布到Maven庫,再由其他模塊自行引用。
有沒有辦法將APT的這種功能和特性延伸到jar和aar呢?
於是,ToyBricks應運而生。
ToyBricks簡介
ToyBricks簡介
ToyBricks是一個Android項目模塊化的解決方案,主要包括四個部分,APT注解,APT注解處理器,ToyBricks插件(Gradle Plugin),ToyBricks庫。
其中:
1. APT注解,主要定義了兩個注解:Interface(接口,例如:IText),Implementation (實現,例如:TextImpl)
1. APT注解處理器,在javac編譯java源碼之前。APT注解處理器會掃描Java源碼中帶有上麵兩個注解的接口和類,並且生成一個json文件, ToyBricks.json.
1. ToyBricks插件(Gradle Plugin),負責ToyBricks.json的打包,合並,生成Java源文件等工作
1. ToyBricks,提供對外調用方法。通過參數傳入接口,返回相應的實現。
ToyBricks原理分析
下麵以接口IText和實現TextImpl為例,簡單介紹下ToyBricks原理。
主要分為兩個部分:
Android Library(最終可能打包成jar,aar,並發布到maven庫)
如果工程是Android庫模塊,則主要流程如下:
1.在javac編譯java源碼之前,由APT注解處理器掃描Java源碼中帶有上麵兩個注解的接口和類,生成ToyBricks.json。
{
"interfaceList" : [ "com.github.snowdream.toybricks.app.IText" ],
"globalImplementation" : {
"com.github.snowdream.toybricks.app.IText" : "com.github.snowdream.toybricks.app.impl.NewTextGobalImpl"
},
"defaultImplementation" : {
"com.github.snowdream.toybricks.app.IText" : "com.github.snowdream.toybricks.app.impl.TextImpl"
},
"singletonImplementation" : [ "com.github.snowdream.toybricks.app.impl.NewTextGobalImpl" ]
}
2.在打包jar,aar的時候,由ToyBricks插件(Gradle Plugin)提前處理,保證ToyBricks.json能被拷貝進去jar包或者aar包的根目錄下,並隨同一起分布到maven倉庫。
Android Application
如果工程是Android應用模塊,則主要流程如下:
1. 第一步,和Android Library第一步一致。
2. Javac編譯Java源代碼
3. 掃描所有依賴的庫文件,過濾出所有包含ToyBricks.json文件的jar包或者aar包,並且提取出來。提取完畢後,合並所有的ToyBricks.json文件,成為一個ToyBricks.json。
4. 將最終的ToyBricks.json按照預定規則生成一個Java源碼文件.文件名為: InterfaceLoaderImpl.java
package com.github.snowdream.toybricks.annotation.impl;
import com.github.snowdream.toybricks.annotation.InterfaceLoader;
import java.lang.Class;
import java.lang.Override;
import java.util.HashMap;
/**
*
* Created by snowdream
*
* This file is automatically generated by apt(Annotation Processing Tool)
* Do not modify this file -- YOUR CHANGES WILL BE ERASED!
*
* This file should *NOT* be checked into Version Control Systems,
* as it contains information specific to your local configuration.
*/
final class InterfaceLoaderImpl implements InterfaceLoader {
private static HashMap<Class, Object> sSingletonMap = new HashMap<Class, Object>();
private static HashMap<Class, Class> sGlobalMap = new HashMap<Class, Class>();
private static HashMap<Class, Class> sDefaultMap = new HashMap<Class, Class>();
public InterfaceLoaderImpl() {
addGlobalMap();
addDefaultMap();
addSingletonMap();
}
private void addGlobalMap() {
sGlobalMap.put(com.github.snowdream.toybricks.app.IText.class,com.github.snowdream.toybricks.app.impl.NewTextGobalImpl.class);
}
private void addDefaultMap() {
sDefaultMap.put(com.github.snowdream.toybricks.app.IText.class,com.github.snowdream.toybricks.app.impl.TextImpl.class);
}
private void addSingletonMap() {
sSingletonMap.put(com.github.snowdream.toybricks.app.impl.NewTextGobalImpl.class,null);
}
@Override
public <T> T getImplementation(Class<T> clazz) {
T implementation = null;
boolean isSingleton = false;
Class implClass;
implClass = sGlobalMap.get(clazz);
if (implClass == null) {
implClass = sDefaultMap.get(clazz);
}
if (implClass != null) {
isSingleton = sSingletonMap.containsKey(implClass);
if (isSingleton) {
implementation = (T) sSingletonMap.get(implClass);
if (implementation != null) {
return implementation;
}
}
try {
implementation = (T) implClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (isSingleton && implementation != null) {
sSingletonMap.put(implClass, implementation);
}
}
return implementation;
}
}
5.再次使用Javac工具編譯InterfaceLoaderImpl.java文件。
6.這個文件就類似字典索引,通過這個文件,就可以通過傳入接口,來查找對應的實現類。
總結
與APT工具相比,ToyBricks能夠將接口和實現之間的關係進行持久化,存儲在jar和aar中,並隨之發布到Maven倉庫,實現接口和實現的徹底分離。
如果您對ToyBricks有什麼問題或者建議,歡迎通過後麵的聯係方式聯係我。
參考資料:
- SnowdreamFramework/ToyBricks
- Annotation Processing Tool (apt)
- ANNOTATION PROCESSING 101
- Annotation-Processing-Tool詳解
- Java注解處理器
- 什麼是高內聚、低耦合?
聯係方式
- Email:yanghui1986527#gmail.com
- Github: https://github.com/snowdream
- Blog: https://snowdream.github.io/blog/
- 簡書:https://www.jianshu.com/u/748f0f7e6432
- 雲棲博客:https://yq.aliyun.com/u/snowdream86
- QQ群: 529327615
- 微信公眾號: sn0wdr1am
最後更新:2017-05-04 22:01:50