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中

相關文章
相關標籤/搜索