一、Android註解快速入門和實用解析 二、Annotation Processor重複造輪子ARouterjava
經過APT處理用註解標記的Activity類,生成對應的映射文件,在運行時把映射表加載到內存中。這裏建立兩個類型爲java library的module。一個library(ARouter處理邏輯),一個compiler(處理註解,生成源碼)android
在library的gradle中引入app
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly 'com.google.android:android:4.1.1.4'
}
sourceCompatibility = "7"
targetCompatibility = "7"
複製代碼
在complier的gradle中引入ide
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.squareup:javapoet:1.9.0'
compile project(':library')
// compile 'com.google.auto.service:auto-service:1.0-rc3'
}
sourceCompatibility = "7"
targetCompatibility = "7"
複製代碼
並在complier中的main目錄下建立resources/MEAT-INF/services/目錄,並在這個目錄下建立名字爲javax.annotation.processing.Processor的文件,而文件內容爲咱們的註解處理的全類名,固然這裏你可使用AutoServuce去實現,接下來就開始實現。gradle
首先在library模塊中定義一個Route編譯時註解和一個咱們在生成的類文件的接口IRoute,用來標識咱們的組件,代碼以下:ui
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
String path();
}
public interface IRoute {
void loadInto(Map<String, Class<?>> routes);
}
複製代碼
講過上面的步驟這時咱們能夠將這兩個模塊引入咱們app模塊中使用了。google
annotationProcessor project(':compiler')
implementation project(':library')
@Route(path = "/app2/main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
................
}
}
複製代碼
可是你發現這並無什麼用,那麼下面開始註解處理器,代碼以下:spa
public class RouterProcessor extends AbstractProcessor {
private Filer filer;
private Messager messager;
private Map<String, ClassName> routes = new HashMap<>();
private String moduleName;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
Map<String, String> options = processingEnvironment.getOptions();
moduleName = options.get("moduleName");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
parseRoute(roundEnvironment);
parseRouteFile();
return true;
}
private void parseRoute(RoundEnvironment roundEnvironment) {
for (Element e : roundEnvironment.getElementsAnnotatedWith(Route.class)) {
Route route = e.getAnnotation(Route.class);
String path = route.path();
TypeElement te = (TypeElement) e;
ClassName className = ClassName.get(te);
routes.put(path, className);
}
}
private void parseRouteFile() {
//build type
TypeSpec.Builder builder =
TypeSpec.classBuilder(GENERATE_ROUTE_SUFFIX_NAME + moduleName)
.addModifiers(Modifier.PUBLIC);
TypeName superInterface = ClassName.bestGuess(SUPER_INTERFACE);
builder.addSuperinterface(superInterface);
//build Map<String, Class<?>>
ParameterizedTypeName mapType = ParameterizedTypeName.get(
//Map
ClassName.get(Map.class),
//string
ClassName.get(String.class),
//Class<?>
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(Object.class)
)
);
//build method for loadInto(Map<String, Class<?>> routes)
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("loadInto")
.addAnnotation(Override.class)
.returns(void.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(mapType, "routes");
//build method body
for (String key : routes.keySet()) {
methodBuilder.addStatement("routes.put($S,$T.class)", key, routes.get(key));
}
builder.addMethod(methodBuilder.build());
try {
JavaFile javaFile = JavaFile.builder(GENERATE_ROUTE_TABLE_PACKAGE, builder.build()).build();
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(Route.class.getCanonicalName());
return annotations;
}
@Override
public Set<String> getSupportedOptions() {
HashSet<String> hashSet = new HashSet<>();
hashSet.add(MODULE_NAME);
return hashSet;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
複製代碼
這時你只要makeproject,將會在generateJava目錄中生成RouterMapTable$$Root_app該類,這個類使咱們在編譯時生成的,文件內容以下:code
public class RouterMapTable$$Root_app implements IRoute {
@Override
public void loadInto(Map<String, Class<?>> routes) {
routes.put("/app/main2",Main2Activity.class);
routes.put("/app2/main",MainActivity.class);
}
}
複製代碼
這裏面有loadInto方法,而且參數是Map<String, Class<?>>,方法體內還添加了咱們註解標識的Activity,key就是Router註解的path,而value則是對應Activity的class,咱們知道啓動一個Activity只須要一個class便可,那那麼如今問題來了,咱們怎麼把這些主句加載到內存中呢?component
前面我說過生成類的包名是有必定的規則,因此能夠在運行時經過dexfile掃描指定包名下的全部的咱們須要的類,並加載到內存中。
public class InjectApi extends ContentProvider {
String GENERATE_COMPONENT_SUFFIX_NAME = "ComponentInject$$";
String GENERATE_COMPONENT_TABLE_PACKAGE = "com.qihe.components";
@Override
public boolean onCreate() {
installer();
return true;
}
public void installer() {
try {
List<String> packageNames = getPackageName(GENERATE_COMPONENT_TABLE_PACKAGE + "." + GENERATE_COMPONENT_SUFFIX_NAME);
Application app = (Application) Objects.requireNonNull(getContext()).getApplicationContext();
for (String packageName : packageNames) {
IComponent loadComponents = (IComponent) Class.forName(packageName).getConstructor().newInstance();
ArrayList<Class<?>> cmps = new ArrayList<>();
loadComponents.loadInto(cmps);
install(app, cmps);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void install(Application app, ArrayList<Class<?>> cmps) {
try {
for (Class<?> clazz : cmps) {
((IComponentProvider) clazz.getConstructor().newInstance()).initialApp(app);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
private List<String> getPackageName(String pkg) throws InterruptedException {
Application app = (Application) Objects.requireNonNull(getContext()).getApplicationContext();
ApplicationInfo applicationInfo = app.getApplicationInfo();
ArrayList<String> apkPaths = new ArrayList<>();
//添加默認的apk路徑
apkPaths.add(applicationInfo.sourceDir);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String[] sourceDirs = applicationInfo.splitSourceDirs;
if (sourceDirs != null && sourceDirs.length > 0) {
apkPaths.addAll(Arrays.asList(sourceDirs));
}
}
if (apkPaths.size() <= 0) {
return apkPaths;
}
List<String> paks = new CopyOnWriteArrayList<>();
CountDownLatch latch = new CountDownLatch(apkPaths.size());
for (String apkPath : apkPaths) {
DefaultPoolExecutor.getInstance().execute(() -> {
DexFile dexFile = null;
try {
dexFile = new DexFile(apkPath);
} catch (IOException e) {
e.printStackTrace();
}
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String element = entries.nextElement();
if (element.startsWith(pkg)) {
Log.e("tag", "" + element);
paks.add(element);
}
}
latch.countDown();
});
}
latch.await();
return paks;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
複製代碼
這裏我使用了一個ContentProvider,作了初始化操做把生成放的類所有加載到內存中,由於ContentProvidetr的onCreate方法是比Application的onCreate先調用的。由於咱們將生成的數據都加載到了 private static Map<String, Class<?>> routes = new HashMap<>();中,因此咱們能夠動過key去匹配獲取到Class,就能夠啓動Activity了。
public class Router {
private static Router mRouter;
final static String GENERATE_ROUTE_TABLE_PACKAGE = "com.wfy.simple.router";
final static String GENERATE_ROUTE_SUFFIX_NAME = "RouterMapTable$$Root_";
final static String DOT = ".";
private static Map<String, Class<?>> routes = new HashMap<>();
private static Context mContext;
private Router() {
}
public static Router getInstance() {
if (mRouter == null) {
synchronized (Router.class) {
if (mRouter == null) {
mRouter = new Router();
}
}
}
return mRouter;
}
public void go(String path) {
if (routes.containsKey(path)) {
Class<?> aClass = routes.get(path);
Intent intent = new Intent(mContext, aClass);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
}
public static void init(Application app) {
mContext = app;
Log.e("tag", "init");
try {
Set<String> routerMap = ClassUtils.getFileNameByPackageName(mContext, GENERATE_ROUTE_TABLE_PACKAGE);
for (String className : routerMap) {
Log.e("tag", "" + className);
if (className.startsWith(GENERATE_ROUTE_TABLE_PACKAGE + DOT + GENERATE_ROUTE_SUFFIX_NAME)) {
((IRoute) (Class.forName(className).getConstructor().newInstance())).loadInto(routes);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
ARouter是在編譯時註解表示,並經過JavaPoet在編譯時生成帶有Activity或其餘組件的有用信息,而後在運行時經過掃描dex文件,特定包下的的類,間接將編譯時的生成的有用信息封裝到Map中