What is ARouter?
ARouter is an Open source Android routing framework of Ali. It is a routing framework that helps Android App to be componentized. It supports routing, communication and decoupling between modules. Combining routing can realize componentization.
ARouter access means north
Complete Arouter access guide, heavy users of Arouter can skip and look straight ahead
- First, use aroutter-register at the root of the build.gradle setting
apply plugin: 'com.alibaba.arouter'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "com.alibaba:arouter-register:?"}}Copy the code
- Second, create Baselib and add Dependencies
api 'com.alibaba:arouter-api:x.x.x'
Copy the code
- Third, create a component module, such as the Login or Setting component
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// Replace with the latest version, but the API needs attention
// To match with the compiler, use the latest version to ensure compatibility
//compile 'com.alibaba: aroutert-api: X.X.X
api project(path: ':baselib')
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'. }Copy the code
- Step four, annotate the @route registration page
// Add a note to the routing page (mandatory)
// The path must have at least two levels, /xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {... }Copy the code
- Step 5: Initialize
if (isDebug()) { // These two lines must be written before init, otherwise these configurations will be invalid during init
ARouter.openLog(); // Prints logs
ARouter.openDebug(); // Enable debug mode (if running in InstantRun mode, you must enable debug mode! The online version needs to be closed, otherwise there are security risks)
}
ARouter.init(mApplication); // As early as possible, it is recommended to initialize in the Application
Copy the code
- Step 6, use ARouter
ARouter.getInstance().build("/test/activity").navigation();
Copy the code
What are the advantages of ARouter over traditional Intents
Advantages of traditional Intents
- lightweight
- simple
Disadvantages of traditional Intents
- The jump procedure cannot be controlled once invoked
startActivity(Intent)
Then the system executes, the intermediate process cannot intervene - A jump failure cannot be caught, degraded, and an exception is thrown when a problem occurs
- Shows severe coupling in intents due to direct class dependencies
startActivity(new Intent(MainActivity.this, LoginActivity.class));// Rely heavily on LoginActivity
Copy the code
- Centralized management of rules occurs in implicit Intents, which leads to difficulties in collaboration and needs to be configured in the Manifest, resulting in poor scalability
// Implicit is stronger than explicit. It is possible to jump between two unclosed submodules, which cannot be done because explicit cannot introduce packages
Intent intent = new Intent();
intent.setClassName(MainActivity.this."com.cnn.loginplugin.ui.login.LoginActivity");// Set the package path
startActivity(intent);
Copy the code
ARouter advantages
- Intermodule communication (principles later)
- Support for URL Redirect
build("/test/activity").navigation()
- Support interceptors
// The classic application is to handle the login event during the jump, so there is no need to repeat the login check on the target page
Interceptors are executed between jumps, and multiple interceptors are executed in order of priority
@interceptor (priority = 8, name = "test Interceptor ")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {... callback.onContinue(postcard);// Return the control after processing
// Callback.onInterrupt (new RuntimeException(" I think it's a little weird ")); // Interrupt the routing process
// At least one of the above two must be called, otherwise the route will not continue
}
@Override
public void init(Context context) {
// Interceptor initialization will call this method only once when the SDK is initialized}}Copy the code
- Parameter injection, @Autowired annotation implementation, more convenient, need to cooperate
ARouter.getInstance().inject(this);
Used together
@Autowired
public String name;
@Autowired
int age;
// Map the different parameters in the URL by name
@Autowired(name = "girl")
boolean boy;
// Support parsing custom objects, using JSON pass in URL
@Autowired
TestObj obj;
// Pass the implementation of List and Map using withObject
// Serializable interface implementation class (ArrayList/HashMap)
// The place where the object is received cannot be marked with the specific implementation class type
// Only List or Map should be marked, otherwise the type in serialization will be affected
Other similar cases need to be treated in the same way
@Autowired
List<TestObj> list;
@Autowired
Map<String, List<TestObj>> map;
Copy the code
- Redirect to external urls
<activity android:name=".SchemeFilterActivity">
<! -- Scheme -->
<intent-filter>
<data
android:host="www.nativie.com"
android:scheme="arouter"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
Copy the code
- Simple demo, making do simple static server interface, and deploy to oslanka. The dead simple. IO/statichtml….
<html>
<body>
<p><a href="http://www.360.com/">Test jump</a> </p>
<p><a href="arouter://www.nativie.com/login/login">Go to Android-arouter</a></p>
<p><a href="arouter://www.nativie.com/login/login?username=admin&password=123456">Jump to android-arouter with parameters</a></p>
<p><a href="arouter://www.nativie.com/setting/setting">Jump to the Android-Arouter Settings screen</a></p>
<p><a href="arouter://www.nativie.com/web/web">Jump to the Android-Arouter Settings screen</a></p>
<p><a href="arouter://www.nativie.com/test/test">Jump to the wrong android-arouter path</a></p>
</body>
</html>
Copy the code
About interceptors
- Interceptors (intercepting jump procedures, aspect oriented programming)
- What is aspect Oriented Programming AOP? AOP is an abbreviation of Aspect Oriented Programming, which means: Aspect Oriented Programming. It is a technique to achieve unified maintenance of program functions by means of precompilation and dynamic proxy during run time. AOP is a continuation of OOP, a hot topic in software development, an important content in Spring framework, and a derivative paradigm of functional programming. Using AOP can isolate each part of the business logic, so that the coupling degree between each part of the business logic is reduced, the reusability of the program is improved, and the efficiency of development is improved
// Interceptors are executed before the jump, and multiple interceptors are executed in order of priority
@interceptor (priority = 8, name = "test Interceptor ")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {... callback.onContinue(postcard);// Return the control after processing
// Callback.onInterrupt (new RuntimeException(" I think it's a little weird ")); // Interrupt the routing process
// At least one of the above two must be called, otherwise the route will not continue
}
@Override
public void init(Context context) {
// Interceptor initialization will call this method only once when the SDK is initialized}}Copy the code
Dynamic routing
- Dynamic registration of routing information is applicable to some plug-in architecture apps and scenarios that require dynamic registration of routing information. Dynamic registration of routing information can be realized through the interface provided by ARouter. The target page and service can be annotated without @route annotation
ARouter.getInstance().addRouteGroup(new IRouteGroup() {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/dynamic/activity".// path
RouteMeta.build(
RouteType.ACTIVITY, // Routing information
TestDynamicActivity.class, // The Class of the target
"/dynamic/activity".// Path
"dynamic".// Group, try to keep the same as the first paragraph of path
0.// Priority, not currently in use
0 // Extra, used to mark the page)); }});Copy the code
ARouter detailed API
// Build standard routing requests and specify groups
ARouter.getInstance().build("/home/main"."ap").navigation();
// Build standard routing requests and parse them directly through the Uri
Uri uri;
ARouter.getInstance().build(uri).navigation();
// Build the standard routing request, startActivityForResult
// The first parameter of navigation must be the Activity and the second parameter must be the RequestCode
ARouter.getInstance().build("/home/main"."ap").navigation(this.5);
/ / specify the Flag
ARouter.getInstance()
.build("/home/main")
.withFlags();
.navigation();
/ / get fragments
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
// Object pass
ARouter.getInstance()
.withObject("key".new TestObj("Jack"."Rose"))
.navigation();
// Use the green channel (skip all interceptors)
ARouter.getInstance().build("/home/main").greenChannel().navigation();
Copy the code
The principle of exploring
- Arouter. init, by obtaining
/ data/app/package/base. The apk
To filter the classes generated by ARouter, as shown below.
- For the Activity type, jump
ARouter.getInstance().build("/login/login").navigation();
, the final implementation is as follows:
**
* Start activity
*
* @see ActivityCompat
*/
private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
if (requestCode >= 0) { // Need start for result
if (currentContext instanceof Activity) {// Start context as an Activity
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
// When starting context as Application, requestCode is not supported
logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]"); }}else {// Start context as Application
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((-1! = postcard.getEnterAnim() && -1! = postcard.getExitAnim()) && currentContextinstanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null! = callback) {// Navigation over.callback.onArrival(postcard); }}Copy the code
- How do two unrelated modules jump? We found that the context used in the final execution of startActivity is Application. The idea is that when a sub-module starts another uncorrelated sub-module, it returns the execution right to the main process/main program to handle
- AROUTER_GENERATE_DOC=”enable” to generate arouter-map-of-xx.json and three Java files
// Update build.gradle with AROUTER_GENERATE_DOC = enable
// The generated document path: build/generated/ap_generated_sources/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]}}}}// How to generate the ARouter mapping? Three files are Generated
//ARouter$$Group$$login
//ARouter$$Providers$$loginplugin
//ARouter$$Root$$loginplugin
Copy the code
atlas.put("/login/login", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/login/login"."login".new java.util.HashMap<String, Integer>(){{put("password".8); put("username".8); }} -1, -2147483648));
//map Saves the mapping
//static Map<String, RouteMeta> routes = new HashMap<>();
Copy the code
- How are these three files generated? APT is short for Annotation Processing Tool. It parses the annotations specified in the code at compile time and then does some other Processing (such as generating a new Java file via Javapoet). ARouter uses two libraries
auto-service
javapoet
To implement the injection from annotations to code, whereauto-service
For the library of the annotation processor,javapoet
For code generator
Learn APT by example
First of all, let’s look at meta-annotations, meta-annotations.
- @Target
TYPE, // Class, interface, enumeration class
FIELD, // Member variables (including: constants)
METHOD, // Member methods
PARAMETER, / / method parameters
CONSTRUCTOR, // The constructor
LOCAL_VARIABLE, // Local variables
ANNOTATION_TYPE, / / comment
PACKAGE, // Can be used to decorate: packages
TYPE_PARAMETER, // The type parameter is new in JDK 1.8
TYPE_USE // Use types anywhere new in JDK 1.8` ` ` -@RetentionThe Java SOURCE is reserved only during the compilation of this compilation unit and is not written to the Class file. CLASS, is retained during compilation and is written to the CLASS file, but the JVM does not need to load the CLASS as visible at runtime (reflected visible) == is not visible when the JVM loads the CLASS. RUNTIME is preserved during compilation, the Class file is written, and the JVM loads the Class as a reflection visible annotation when it loads it. ` ` ` -@DocumentedThe purpose of annotations is to describe whether to retain the annotations when using the Javadoc tool to generate help documentation for a class@InheritedThe purpose of an annotation is to make the annotation it decorates inherited (if a class uses a@Inherited- We define our own annotations through meta-annotations - [AutoService annotation handler](HTTPS://github.com/google/auto/tree/master/service)Annotation handler is a tool in JavAC that scans and processes annotations at compile time. You can register your own annotation handler for specific annotations. At this point, I'm assuming you already know what an annotation is and how to declare an annotation type. An annotation handler for an annotation takes Java code (or compiled bytecode) as input and generates a file (usually a.java file) as output. - Virtual processor 'AbstractProcessor' - 'init(ProcessingEnvironment ENV)' : [core] Every annotation processor class must have an empty constructor. However, there is a special init() method that is called by the annotation processing tool with the 'ProcessingEnviroment' parameter. 'ProcessingEnviroment' provides many useful tool classes' Elements', 'Types', and' Filer '-' process(Set<? Extends TypeElement> Annotations, RoundEnvironment env) ': [core] This corresponds to the main function () for each processor. Here you write your code scanning, assessment and treatment annotations, and generate Java files - ` getSupportedAnnotationTypes ` here you must specify the (), The annotation processor which is registered to the annotations - ` getSupportedSourceVersion ` () is used to specify Java version you use. Usually here return ` SourceVersion. LatestSupported () ` - code generator used in the APT: * * [JavaPoet] (HTTPS:/ / github.com/square/javapoet) * * is a Java API for generating `. Java ` source files. (JavaPoet is a Java API, in order to generate the Java source file)
- 官方helloworld
```java
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
Copy the code
- The following Java files are generated from the above
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!"); }}Copy the code
JavaPoet
The main API
- JavaFile is used to construct a Java file whose output contains a top-level class - TypeSpec generates classes, interfaces, Or enumeration-methodSpec to generate constructors or methods -fieldSpec to generate member variables or fields -parameterSpec to create parameters -AnnotationSpec to create annotationsCopy the code
JavaPoet
Primary placeholder
- $L(forLiterals) the character or common type that executes the structure, or TypeSpec, $S(forString, $T(forClass Types), $N (for$L>$S//1.Pass an argument value for each placeholder in the format string to `CodeBlock.add()`. In each example, we generate code to say "I ate 3 tacos"
CodeBlock.builder().add("I ate $L $L".3."tacos")
//2.When generating the code above, we pass the hexDigit() method as an argument to the byteToHex() method using $N:
MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
.addParameter(int.class, "b")
.returns(String.class)
.addStatement("char[] result = new char[2]")
.addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
.addStatement("result[1] = $N(b & 0xf)", hexDigit)
.addStatement("return new String(result)")
.build();
/ / = = = = = = = = = = = = = = = = = = = = = = =
public String byteToHex(int b) {
char[] result = new char[2];
result[0] = hexDigit((b >>> 4) & 0xf);
result[1] = hexDigit(b & 0xf);
return new String(result);
}
//$T for Types
//We Java programmers love our types: they make our code easier to understand. And JavaPoet is on board. It has rich built-in support for types, including automatic generation of import statements. Just use $T to reference types:
.addStatement("return new $T()", Date.class)== return new Date();
Copy the code
Actual combat – Custom simple version routing -CRouter
- Create a new name-annotation javaLib and define the CRoute annotation
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface CRoute {
String path(a);
}
Copy the code
- The new name – the compiler javaLib
1.
dependencies {
implementation project(path: ':TestRouter-annotation')
annotationProcessor 'com. Google. Auto. Services: auto - service: 1.0 rc7'
compileOnly 'com. Google. Auto. Services: auto - service - annotations: 1.0 rc7'
implementation 'com. Squareup: javapoet: 1.8.0 comes with'
}
2.@AutoService(Processor.class)
public class TestRouteProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//dosomething
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//dosomething}}Copy the code
- The execution sequence of service modules is as follows
1. annotationProcessor project(':TestRouter-compiler')
implementation project(':TestRouter-annotation')
2.Add annotations@CRoute(path = "/csetting/csetting")
3.Compile operation4.The Java file generated by the business Module APT is as follows:public final class C$csettingC$csettingHelloWorld {
public static String holder = "/csetting/csetting:com.cnn.settingplugin.SettingsActivity";
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!"); }}Copy the code
- reference
ARouter-init
Method, write us outCRouter-init
/** * Init, it must be call before used router. */
public static void init(Application application) {
if(! hasInit) { CRouter.application=application; hasInit=true;
try {
getFileNameByPackageName(application, ROUTE_ROOT_PAKCAGE);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code
- The annotation mapping is obtained by reflection and stored in HashMap by referring to ARouter
- Simulate the jump by implicitly starting the Activity
- Here we simulate a simple version of ARouter, full custom CRouter
/** * Created by caining on 7/29/21 16:09 * E-mail Address: [email protected] */
public class CRouter {
private volatile static CRouter instance = null;
private volatile static boolean hasInit = false;
private static Application application;
public static final String ROUTE_ROOT_PAKCAGE = "com.cnn.crouter";
private static Map<String ,String> mapHolder = new HashMap<>();
/** * Init, it must be call before used router. */
public static void init(Application application) {
if(! hasInit) { CRouter.application=application; hasInit=true;
try {
getFileNameByPackageName(application, ROUTE_ROOT_PAKCAGE);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch(InterruptedException e) { e.printStackTrace(); }}}/** * Get instance of router. A * All feature U use, will be starts here. */
public static CRouter getInstance(a) {
if(! hasInit) {throw new InitException("ARouter::Init::Invoke init(context) first!");
} else {
if (instance == null) {
synchronized (CRouter.class) {
if (instance == null) {
instance = newCRouter(); }}}returninstance; }}public void navigation(String path) {
startActivity(path);
}
private void startActivity(String path) {
String classPath
= mapHolder.get(path);
if(! TextUtils.isEmpty(classPath)) { Intent intent =new Intent();
intent.setClassName(application, classPath);// Set the package path
ActivityCompat.startActivity(application, intent, null);
}else {
Toast.makeText(application, "The path is empty", Toast.LENGTH_SHORT).show(); }}/** * scan all classnames ** contained under the package by specifying the package name@param context U know
* @paramPackageName package name *@returnSet of all classes */
private static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
final Set<String> classNames = new HashSet<>();
List<String> paths = getSourcePaths(context);
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
for (final String path : paths) {
DefaultPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run(a) {
DexFile dexfile = null;
try {
if (path.endsWith("EXTRACTED_SUFFIX")) {
//NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
dexfile = DexFile.loadDex(path, path + ".tmp".0);
} else {
dexfile = new DexFile(path);
}
Enumeration<String> dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
if (className.startsWith(packageName)) {
classNames.add(className);
try {
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
Field field03 = clazz.getDeclaredField("holder"); // Get the field whose attribute is ID
String value= (String) field03.get(obj);
String[] split = value.split(":");
if(split! =null&&split.length==2) {
mapHolder.put(split[0],split[1]);
}
Log.i("test-->",mapHolder.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch(IllegalArgumentException e) { e.printStackTrace(); }}}}catch (Throwable ignore) {
Log.e("ARouter"."Scan map file in dex files made error.", ignore);
} finally {
if (null! = dexfile) {try {
dexfile.close();
} catch(Throwable ignore) { } } parserCtl.countDown(); }}}); } parserCtl.await();return classNames;
}
private static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
List<String> sourcePaths = new ArrayList<>();
sourcePaths.add(applicationInfo.sourceDir); //add the default apk path
returnsourcePaths; }}Copy the code
conclusion
- ARouter user guide
- ARouter interceptor
- SchemeFilte to achieve external HTML jump Native, through WEB&Native
- Understand apt principle of JavaPoet &AutoService annotation processor
- Write a simple version of CRouter, through the actual combat we understand the realization principle of ARouter
- Project Demo Address
The problem
- Other than ARouter, do you know of any frameworks that take advantage of APT?
- Are there any disadvantages to ARouter?
reference
-
Github.com/alibaba/ARo…
-
Github.com/square/java…
-
github.com/google/auto
-
Github.com/Oslanka/sta…
-
Github.com/Oslanka/Aro…