The existence of routes

  1. Modules are decoupled, and there is no need to specify the jump target class name in the class
  2. Dynamic configuration, business requirements, such as part of the banner page address need to be dynamically configurable, this time it is not convenient to write dead class name, routing is a better solution.

preface

This article will show you how to write a simple route. Before explaining routing, we need to first understand annotation under Android. Annotation is not only an important part of routing, but also has annotation components in the mainstream frameworks at present.

In fact, all the things we can do with an annotation, such as binding controls and routing jumps, are done because the annotation processor generates the corresponding code for us to do so.

The following is to explain the realization of the route jump, in addition to the relevant knowledge of annotations, can refer to the annotations under Android

Routing hop

In normal cases, jumps between activities are as follows

Intent intent=new Intent(this,TestActivity.class);
startActivity(intent);
Copy the code

The route jump principle actually executes similar code, but the route executes annotation-generated logic code. All annotated activities under the project are saved as a Map, similar to the following logic.

public static void initActivityMap(HashMap<String, Class> activityMap) {
	activityMap.put("test", TestActivity1.class);
	activityMap.put("test2", TestActivity2.class);
}
Copy the code

Then pass in a string like test and traverse the map to find the corresponding target Activity like TestActivity1. Finally, startActivity() is executed for the jump, which is a simple route jump.

The specific implementation

The following modules are established under the project

-- APP (main project) -- Route_annotation (Java Module custom annotations) -- Route_API (Android Module) -- Route_Compiler (Java Module annotations processor logic)Copy the code

Route_annotation Defines the annotation

A very simple annotation interface

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Route {
    String [] value();
}
Copy the code

Route_compiler annotates processor logic

Create a new annotation processor class RouteProcessor that inherits from AbstractProcessor

The specific implementation

@AutoService(Processor.class) public class RouteProcessor extends AbstractProcessor { private Messager mMessager; private Filer mFiler; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mMessager = processingEnv.getMessager(); mFiler = processingEnv.getFiler(); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { Set<? extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith(Route.class); try { TypeSpec typeSpec = processRouterTable(routeElements); if (typeSpec ! = null) { JavaFile.builder("com.dly.routeDemo", typeSpec).build().writeTo(mFiler); } } catch (Exception e) { e.printStackTrace(); error(e.getMessage()); } return true; } private TypeSpec processRouterTable(Set<? Extends Element> elements) {// Note that this is a nullator because APT scans all project files multiple times including those generated by javaPoet. / / don't do this step processing, generate the files you need for the first time, when the scan again the second time, and will go to a file, then the system will report an exception, for can't generate the same file if (elements = = null | | elements. The size () = = 0) { return null; } ParameterizedTypeName mapTypeName = ParameterizedTypeName .get(ClassName.get(HashMap.class), ClassName.get(String.class), ClassName.get(Class.class)); ParameterSpec mapParameterSpec = ParameterSpec.builder(mapTypeName, "activityMap") .build(); MethodSpec.Builder routerInitBuilder = MethodSpec.methodBuilder("initActivityMap") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(mapParameterSpec); for (Element element : elements) { Route route = element.getAnnotation(Route.class); String[] routerUrls = route.value(); for (String url : RouterUrls) {/ / core logic The character mapping associated with class routerInitBuilder. AddStatement (" activityMap. Put ($S, $tc lass) ", the url, ClassName.get((TypeElement) element)); } } return TypeSpec.classBuilder("AutoCreateModuleActivityMap_app") .addModifiers(Modifier.PUBLIC) .addMethod(routerInitBuilder.build()) .build(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> ret = new HashSet<>(); ret.add(Route.class.getCanonicalName()); return ret; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } private void error(String error) { mMessager.printMessage(Diagnostic.Kind.ERROR, error); }}Copy the code

After the RouteProcessor class will be done in AS compilation, app/build/generated in the directory/source/apt/debug/production under the project name a Java file.

public class AutoCreateModuleActivityMap_app { public static void initActivityMap(HashMap<String, Class> activityMap) { activityMap.put("test", TestActivity1.class); activityMap.put("test2", TestActivity2.class); }}Copy the code

This Java file will hold your annotated classes and mappings between strings.

The route_API needs to perform the specific jump logic known as startActivity

New RouteDemo. Class

public class RouteDemo { private static HashMap<String, Class> activityMap = new HashMap<>(); private static Application mApplication; public static void init(Application application) { mApplication = application; Try {/ / call AutoCreateModuleActivityMap_app Class method through reflection, and to give activityMap assignment Class clazz = Class.forName("com.dly.routeDemo.AutoCreateModuleActivityMap_app"); Method method = clazz.getMethod("initActivityMap", HashMap.class); method.invoke(null, activityMap); for (String key : activityMap.keySet()) { System.out.println("activityMap = " + activityMap.get(key)); } } catch (Exception e) { e.printStackTrace(); } } public static void open(String url) { for (String key : activityMap.keySet()) { if (url.equals(key)) { Intent intent = new Intent(mApplication, activityMap.get(key)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK ); mApplication.startActivity(intent); }}}}Copy the code

A simple reflection call assigns values to our own defined ActivityMap. So we have a collection of ActivityMaps.

The main engineering app

Define a MyApplication to initialize the route

public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); RouteDemo.init(this); }}Copy the code

Then perform the route jump under MainActivity.

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent=new Intent(this,TestActivity.class); startActivity(intent); } public void onClick(View v) { switch (v.getId()) { case R.id.text1: RouteDemo.open("test"); break; case R.id.text2: RouteDemo.open("test2"); break; }}}Copy the code

The corresponding TestActivity1

@Route("test")
public class TestActivity1 extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
    }
}
Copy the code

The above procedures complete a simplest route jump, of course, we need not only a jump, but also need to carry parameters jump, or jump interception and other requirements.

These requirements will be explained in a later article.

Making the address

RouteDemo