1. It resolved
- Postcard, inherited from RouteMeta, acts as the location provider class in the routing process, and all jump message classes in a project are based on this class and provide some jump capability
RouteMeta
private RouteType type; // Route type, supported as follows:
// ACTIVITY(0, "android.app.Activity"),
// SERVICE(1, "android.app.Service"),
// PROVIDER(2, "com.alibaba.android.arouter.facade.template.IProvider"),
// CONTENT_PROVIDER(-1, "android.app.ContentProvider"),
// BOARDCAST(-1, ""),
// METHOD(-1, ""),
// FRAGMENT(-1, "android.app.Fragment"),
// UNKNOWN(-1, "Unknown route type");
private Element rawType; // The original type in the route
privateClass<? > destination;// Jump to the target class
private String path; // Routing address
private String group; // Routing groups
private int priority = -1; // Priority. The smaller the number, the higher the priority
private int extra; // Additional data
private Map<String, Integer> paramsType; / / parameters
private String name; / / name
private Map<String, Autowired> injectConfig;// Cache injection configuration
Copy the code
Postcard
// Base
private Uri uri; //Uri
private Object tag; // The tag is prepared for certain errors. This is an internal parameter. Do not use it
private Bundle mBundle; // Data conversion Bundele
private int flags = 0; // Routing flags
private int timeout = 300; // Timeout period
private IProvider provider; // If the PostCard is a Provider, it is used to pass data
private boolean greenChannel; // Green channel, true, the interceptor is invalid
private SerializationService serializationService; // Serialization service for internal use
private Context context; / / context
private String action; / / action
// Animation processing
private Bundle optionsCompat;
private int enterAnim = -1;
private int exitAnim = -1;
Copy the code
Postcard.navigation()
-
The PostCard also provides a series of hop navigation methods, which are actually called internally to the ** arouter.getInstance ().navigation(mContext, this, requestCode, callback)** method, It is finally implemented in the _arouter.getInstance ().navigation(mContext, Postcard, requestCode, callback) method
_ARouter is actually the class that implements the functionality
-
_arouter.getInstance ().navigation()
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null! = pretreatmentService && ! pretreatmentService.onPretreatment(context, postcard)) {return null;
}
postcard.setContext(null == context ? mContext : context);
try {
// The Logistics center prefixes the Postcard and fully assembles it internally
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
//Debug specialized processing
if (debuggable()) {
// If the Postcard preprocessing fails, the user is prompted with a route mismatch
runInMainThread(new Runnable() {
@Override
public void run(a) {
Toast.makeText(mContext, "There's no route matched! \n" +
" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show(); }}); }// If there is a callBack implementation, the onLost callBack is called
if (null! = callback) { callback.onLost(postcard); }else {
// If there is no specific implementation, enable global degradation service for processing
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
// Call the onLost callback if the degraded service is also not pre-declared
if (null != degradeService) { degradeService.onLost(context, postcard); }
}
return null;
}
// If the destination is found, the onFound callback is performed
if (null! = callback) { callback.onFound(postcard); }// If it is not a green channel, the interceptor processing is performed
if(! postcard.isGreenChannel()) { interceptorService.doInterceptions(postcard,new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
_navigation(postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) {
if (null! = callback) { callback.onInterrupt(postcard); } logger.info(Consts.TAG,"Navigation failed, termination by interceptor : "+ exception.getMessage()); }}); }else {
// Otherwise, do _navigation()
return _navigation(postcard, requestCode, callback);
}
return null;
}
Copy the code
If the destination exists, the final implementation is in the **_navigation(postcard, requestCode, callback)** method, whether or not the green channel exists
_navigation(postcard, requestCode, callback)
- Methods are sorted internally by type and eventually called to the Android implementation, such as the startActivity method if the type is Activity.
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = postcard.getContext();
switch (postcard.getType()) {
// Activity type processing
case ACTIVITY:
/ / assembly Intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
int flags = postcard.getFlags();
if (0! = flags) { intent.setFlags(flags); }if(! (currentContextinstanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
String action = postcard.getAction();
if(! TextUtils.isEmpty(action)) { intent.setAction(action); }// Finally call startActivity on the main thread
runInMainThread(new Runnable() {
@Override
public void run(a) { startActivity(requestCode, currentContext, intent, postcard, callback); }});break;
// Provider type processing
case PROVIDER:
// Return directly
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
/ / get fragmentMetaClass<? > fragmentMeta = postcard.getDestination();try {
Object instance = fragmentMeta.getConstructor().newInstance();
// Ordinary Fragment processing
if (instance instanceof Fragment) {
((Fragment) instance).setArguments(postcard.getExtras());
// V4 Fragment processing
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}
/ / returns the fragments
return instance;
} catch (Exception ex) {
// Cannot find the corresponding instance
logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
}
case METHOD:
case SERVICE:
default:
return null;
}
return null;
}
Copy the code
conclusion
- First, Arouter creates the PostCard based on the final destination and the corresponding jump parameters. The PostCard contains all the properties inside
- The final jump is through the PostCard’s navigation() method
- The navigation() method ends up calling the implementation of _arouter.getInstance ().navigation() internally
- The _arouter.getInstance ().navigation() interior will first implement the logistics pre-postcard via LogisticsCenter, and continue if it is a legal path
- If it is a green channel, no interceptor processing is performed
- If it is not a green channel interceptor processing is performed
- Finally, the _navigation() method is called
- The interior of _navigation() is differentiated by type
- If it is an Activity, then the Intent is loaded from the PostCard, and finally StartActivity() is called
- If it is a Provider, it returns the Provider in the PostCard directly
- If it is Fragment, it is V4 or ordinary Fragment and returns the instance. If no instance is found, an exception is displayed
2.Annotation handler
One of the great things about Arouter is that it automatically populates a lot of repetitive code with annotations, which are provided as follows:
- Route: The marked page can be routed through the router
- Interceptor: Marks an Interceptor for route interception
- Autowired: Automatically implement parameter assembly
Every annotation must correspond to this annotation handler, so that all tags corresponding to the annotation class can automatically implement its capabilities
Both annotation handlers implement aspect oriented programming capabilities through Javapoet and auto-service
Route
Let’s look directly at the parseRoutes method in Process, leaving only the key parts in the code
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {...if (CollectionUtils.isNotEmpty(routeElements)) {
......
// All incoming elements marked with a Route are analyzed and a RouteMeta is returned to be added to the Map
for (Element element : routeElements) {
// Activity or Fragment
if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
.....
if (types.isSubtype(tm, type_Activity)) {
// Create a RouteMeta for the Activity type
routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
} else {
// Create a RouteMeta for the Fragment type
routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
}
routeMeta.setInjectConfig(injectConfig);
} else if (types.isSubtype(tm, iProvider)) {
// Create a RouteMeta for the Provider type
routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
} else if (types.isSubtype(tm, type_Service)) {
// Create a RouteMeta for the Service type
routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
} else {
throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "].");
}
// Finally add the RouteMeta to the groupMap
categories(routeMeta);
}
MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(providerParamSpec);
Map<String, List<RouteDoc>> docSource = new HashMap<>();
// Start generating JAVA code based on the groupMap list
for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
......
Set<RouteMeta> groupData = entry.getValue();
for (RouteMeta routeMeta : groupData) {
.....
switch (routeMeta.getType()) {
case PROVIDER:
.....
// If the type is Provider, cache it
break;
default:
break;
}
StringBuilder mapBodyBuilder = new StringBuilder();
Map<String, Integer> paramsType = routeMeta.getParamsType();
Map<String, Autowired> injectConfigs = routeMeta.getInjectConfig();
if (MapUtils.isNotEmpty(paramsType)) {
List<RouteDoc.Param> paramList = new ArrayList<>();
for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
// Start actually writing JAVA code
mapBodyBuilder.append("put(\"").append(types.getKey()).append("\",").append(types.getValue()).append("); ");
RouteDoc.Param param = newRouteDoc.Param(); Autowired injectConfig = injectConfigs.get(types.getKey()); param.setKey(types.getKey()); param.setType(TypeKind.values()[types.getValue()].name().toLowerCase()); param.setDescription(injectConfig.desc()); param.setRequired(injectConfig.required()); paramList.add(param); } routeDoc.setParams(paramList); } String mapBody = mapBodyBuilder.toString(); }... }}Copy the code
@ the Route summary
-
Get all classes annotated Route, generate the corresponding RouteMeta, put the group into the groupMap, key is group name, value is put into the Set that supports sorting
-
All routemetas that traverse all sets in the groupMap (so it’s a two-layer for loop) generate the corresponding code.
The corresponding code generated is as follows:
public class ARouter$$Group$$test implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview"."test".null, -1)); }}Copy the code
Autowired
- Autowired is actually implemented through the annotation processor. The code is relatively simple, and it actually generates the automatic assembly of Intent. GetInstance ().inject(this) code is generated as an automatic assembly of inject’s rewrite intent.
The generated code is as follows:
public class Test1Activity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);
Test1Activity substitute = (Test1Activity)target;
// Intents are automatically assembled
substitute.age = substitute.getIntent().getIntExtra("age", substitute.age);
substitute.height = substitute.getIntent().getIntExtra("height", substitute.height);
substitute.girl = substitute.getIntent().getBooleanExtra("boy", substitute.girl);
For example, to use serialization, implement SerializationService
substitute.url = substitute.getIntent().getExtras() == null ? substitute.url : substitute.getIntent().getExtras().getString("url", substitute.url); substitute.helloService = ARouter.getInstance().navigation(HelloService.class); }}Copy the code
The @autowired summary
- Annotate variables in the Activity. The annotation processor will scan variables annotated by Autowired during compilation and generate a Java code based on the class and variables. The main object of the annotation is the inject method, which is used to parse and assign values to related variables. Then call the corresponding inject method near the onCreate position of the Activity. The main purpose of this annotation is to eliminate the need to manually write code to parse Intent parameters.
Interceptor
- The interceptor implementation is essentially two methods in the InterceptorServiceImpl class
doInterceptions
@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
checkInterceptorsInitStatus();
if(! interceptorHasInit) {// If the initialization is not complete before the interception is handled, then the initialization time is too long
callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
return;
}
// The async thread calls _execute() one at a time
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run(a) {
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
_execute(0, interceptorCounter, postcard);
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
// Check whether the timeout period has expired
if (interceptorCounter.getCount() > 0) {
callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
// Exception handling
} else if (null! = postcard.getTag()) { callback.onInterrupt((Throwable) postcard.getTag());// Continue processing
} else{ callback.onContinue(postcard); }}catch(Exception e) { callback.onInterrupt(e); }}}); }else{ callback.onContinue(postcard); }}Copy the code
_execute
private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
counter.countDown();
// Internally recurse the call, and adjust the index value
_execute(index + 1, counter, postcard);
}
@Override
public void onInterrupt(Throwable exception) {
postcard.setTag(null == exception ? new HandlerException("No message.") : exception); counter.cancel(); }}); }}Copy the code
@ Interceptor summary
- In the doInterceptions() method, an asynchronous thread implementation calls the _execute method
- There is also internal exception and timeout handling
- _execute() is called recursively and jumps to index to handle interceptors one by one