Annotation Processor重复造轮子ARouter

一、Android注解快速入门和实用解析 二、Annotation Processor重复造轮子ARouterjava

经过APT处理用注解标记的Activity类,生成对应的映射文件,在运行时把映射表加载到内存中。这里建立两个类型为java library的module。一个library(ARouter处理逻辑),一个compiler(处理注解,生成源码)android

  • 首先ARouter是经过APT编译时注解标志Activity类,在编译时生成生成对应的映射文件;
  • 在编译时生成的类文件的类名是具备必定的规则;
  • 在运行时要怎么找咱们的在编译时生成的类,而且把映射表加载到内存中呢?

首先环境的配置

在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

建立注解@Route

首先在library模块中定义一个Route编译时注解和一个咱们在生成的类文件的接口IRoute,用来标识咱们的组件,代码以下:ui

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
 String path();
}

public interface IRoute {
	void loadInto(Map<String, Class<?>> routes);
}
复制代码
  • Route注解的做用是用于标识组件,在注解处理器中能够找到该注解,并获取对应的信息生成咱们须要的类文件。
  • IRoute接口是为了约束咱们生成的类文件的约束;

讲过上面的步骤这时咱们能够将这两个模块引入咱们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中

相关文章
相关标签/搜索