• In a recent componentized refactoring of the project, the routing of communication between components is indispensable for componentization, so I took a look at Ali’s ARouter again. For componentization, you can check out my previous articles on componentized Architecture, which are not covered here
    • Android Componentization Architecture – 1. Componentization foundation
    • Android Componentized Architecture – 2. Communication between Components (1)
    • Android Componentized Architecture – 2. Communication between Components (2)
    • Android componentization architecture – 3. Jump between components & ARouter route
    • Android Componentized Architecture – 4. Dynamic creation & Reflection
    • 5. Data Storage & GreenDao,Room
    • Android Componentized Architecture – 6. Permission management
    • Android componentized Architecture – 7. Confusion
    • Android componentized Architecture – 8. Multi-channel packaging
    • Android componentized Architecture – 9. Gradle Optimization
    • Android Componentized Architecture – 10. Compiler optimization

The use of,

1. Add dependencies

  1. Add dependencies to build. Gradle dependencies of baseRouter Module (preferably in a separate module with secondary encapsulation)
// arouter-api is used with arouter-compiler, and is compatible with the latest version of the compiler. API 'com.alibaba: arouter-API :1.5.2' // I'm using kotlin so I'm using kapt. Kapt 'com.alibaba: arouter-Compiler :1.5.2'Copy the code
  1. Add to build.gradle dependencies of other modules that need to componentize communication
Implementation Project (Path: ':baseRouter') kapt 'com.alibaba:arouter- Compiler :1.5.2'Copy the code
  1. Set the AROUTER_MODULE_NAME
//1. kotlin
plugins {
    id 'com.android.library'
    id 'kotlin-android'
    id 'kotlin-kapt'
}
kapt {
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}
android {
    ...
}
//2. java
android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}
Copy the code

2. Initialize ARouter

  • The following initialization modes are available:
1. Direct initialization
  • Initialize directly in the App Module (not recommended);
class AppModuleApplication : Application() { override fun onCreate() { super.onCreate() if (BuildConfig.isDebug){ ARouter.openLog(); ARouter.openDebug(); } arouter.init (this)}}Copy the code
2. Provide BaseRouterApplication to be inherited
  • Create a subclass of Application in baseRouter Module to initialize ARouter and then inherit it from App Module Application (not recommended);
class BaseRouterApplication : Application() { override fun onCreate() { super.onCreate() if (BuildConfig.isDebug){ ARouter.openLog(); ARouter.openDebug(); } ARouter. Init (this)}} class AppModuleApplication: BaseRouterApplication() { override fun onCreate() { super.onCreate() } }Copy the code
3. Provide the static method initRouter
  • Create an init method that initializes the Router in the secondary Router utility class and call it in the App Module Application
fun initRouter(application: Application,isDebug: Boolean){ if (isDebug){ ARouter.openLog(); ARouter.openDebug(); } ARouter. Init (application); } class AppModuleApplication : Application() { override fun onCreate() { super.onCreate() initRouter(this,BuildConfig.DEBUG) } }Copy the code
  1. Init via ContentProvider
  • Customize the BaseRouterProvider in baseRouter Module and initialize ARouter, then configure it in androidmanifest.xml of baseRouter Module
class BaseRouterProvider : ContentProvider() { override fun onCreate(): Boolean {//ContentProvider can also get Context val application = Context? .applicationContext as Application? application? .let { if (BuildConfig.DEBUG) { ARouter.openLog() ARouter.openDebug() } ARouter.init(application) } return true } // Return null or return -1... } <application> <provider android:name=".BaseRouterProvider" android:authorities="${applicationId}.BaseRouterProvider" android:exported="false" /> ... </application>Copy the code
5. Use the Jetpack component AppStartup to initialize
  1. Add the dependent
Implementation "androidx startup: startup - runtime: 1.0.0"Copy the code
  1. Create the Initializer implementation class and initialize ARouter
Class BaseRouterInitializer: Initializer<Unit> {// Execute the code to initialize in create method override fun create(context: Context) { val application = context.applicationContext as Application? application? .let { if (BuildConfig.DEBUG) { ARouter.openLog() ARouter.openDebug() } ARouter.init(application) } } // The dependencies method configures whether LjyToastInitializer depends on other initializers. EmptyList <Class >>> {return emptyList()}Copy the code
  1. AndroidMainfest. XML configuration
<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup"  android:exported="false" tools:node="merge"> <meta-data android:name=".BaseRouterInitializer" android:value="androidx.startup" /> </provider>Copy the code

3. Simple use

1. Define a routing path management class
  • Create arouterPath. kt in baseRouter module to store routing paths for each module.
/** * app main module entry */ const val PATH_ACTIVITY_MAIN = "/app/MainActivity" /** * login module entry */ const val PATH_ACTIVITY_LOGIN = "/login/LoginActivity" /** */ const val PATH_ACTIVITY_WEB = "/web/WebActivity"Copy the code
2. Add the @route annotation
  • Create the corresponding module and Activity, annotate @route, and set the corresponding routing path. For example, set LoginActivity as follows
@Route(path = PATH_ACTIVITY_LOGIN)
class LoginActivity : AppCompatActivity() {

}
Copy the code
3. Initiate a route forward
  • Just jump
ARouter.getInstance().build(PATH_ACTIVITY_LOGIN).navigation()
Copy the code
  • Carry parameters, there are a lot of withXXX methods are basically see knowingly meaning
ARouter.getInstance().build(PATH_ACTIVITY_LOGIN)
    .withLong("key1", 666L)
    .withString("key2", "888")
    .navigation()
Copy the code
4. Receive parameters
  • Annotating fields with the @Autowired annotation supports automatic injection and calls the Inject method in onCreate (similar to ButterKnife)
  • You need to use the LateInit modifier or add the @jVMField annotation in Kotlin, otherwise the compilation will fail.
  • But it is important to note lateinit modified fields are lazy initialization, must pass when jump, otherwise an error UninitializedPropertyAccessException: Lateinit property name has not been initialized, so use @jvmField;
@Route(path = PATH_ACTIVITY_LOGIN)
class LoginActivity : AppCompatActivity() {
    @Autowired
    @JvmField
    var key1: Int? = -1

    @Autowired(name = "key2", required = true, desc = "userName field")
    lateinit var name: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ARouter.getInstance().inject(this)
        log("key1=$key1")
        log("name=$name")
    }
}
Copy the code
5. Add obfuscation rules (if using Proguard)
-keep public class com.alibaba.android.arouter.routes.**{*; } -keep public class com.alibaba.android.arouter.facade.**{*; } -keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*; } # if byType is used, add the following rule: Protection interface - keep interface * implements com in alibaba. Android. Arouter. The facade. The template. The IProvider # if you are using a single type of injection, neither define interfaces to realize IProvider, Need to add the following rules, protect realize # - keep class * implements com in alibaba. Android. Arouter. The facade. The template. IProviderCopy the code
6. Automatic loading of routing tables using Gradle plugin (optional)
  • Gradle plugins automatically scan arouter’s map manager for classes that need to rely on arouter’s annotations at compile time.
// Buildscript {... dependencies { ... Classpath "com.alibaba:arouter-register:1.0.2"}} // App Module gradle plugins {... id 'com.alibaba.arouter' }Copy the code

4. Advanced usage

Secondary encapsulation
  • Routerutil.kt (routerUtil.kt) routerUtil.kt (RouterUtil.kt) routerUtil.kt (RouterUtil.kt
/* / fun routerNavigation(path: String, params: Map<String, Any? >? = null) { buildParams(ARouter.getInstance().build(path), */ Private Fun buildParams(Postcard: Postcard, Params: Map<String, Any? >? : Postcard { return postcard.apply { if (params ! = null && params.isNotEmpty()) for ((k, v) in params) when (v) { is String -> withString(k, v) is Int -> withInt(k, v) is Boolean -> withBoolean(k, v) is Long -> withLong(k, v) is Float -> withFloat(k, v) is Double -> withDouble(k, v) is ByteArray -> withByteArray(k, v) is Bundle -> with(v) is List<*>, is Map<*, *>, is Set<*> -> withObject(k, v) is Parcelable -> withParcelable(k, v) is Serializable -> withSerializable(k, v) else -> withObject(k, V)}}} /** * Advanced usage method * @path route path * @params pass parameter * @activity Current activity, Use requestCode with requestCode or Callback * @requestCode when startActivityForResult * @Callback uses NavigationCallback to handle jump results (local degradation strategy) * @group usually does not pass, FLAG_ACTIVITY_CLEAR_TOP * @enteranim enters the animation * @exitanim exits the animation * @compat ActivityOptionsCompat animation * @greenchannel whether to use greenChannel (skip all interceptors) */ fun routerNavigation(path: String, params: Map<String, Any? >? = null, activity: Activity? = null, requestCode: Int = -1, callback: NavigationCallback? = null, group: String? = null, uri: Uri? = null, flag: Int? = null, enterAnim: Int? = null, exitAnim: Int? = null, compat: ActivityOptionsCompat? = null, greenChannel: Boolean = false, ) { if (path.isNullOrEmpty()) { buildParams(ARouter.getInstance().build(uri), params) } else { val postcard = if (group.isNullOrEmpty()) ARouter.getInstance().build(path) else ARouter.getInstance().build(path, group) buildParams( postcard.apply { if (flag ! = null) postcard.withFlags(flag) if (enterAnim ! = null && exitAnim ! = null)// Postcard. withTransition(enterAnim, exitAnim) if (compat! = null) / / animated transitions (API16 +) it withOptionsCompat (compat) if (greenChannel) / / use the green channel (skip all the interceptors) it. GreenChannel ()},  params ) }.navigation(activity, requestCode, callback) }Copy the code
1. More types of parameters
  • Passing parameters
data class TestObj(val name:String? ,val age:Int? ,val email:String?) val testObj = TestObj("ljy", 18, "[email protected]") val testObj2 = TestObj("yang", 20, "[email protected]") val list = listOf(testObj, testObj2) val testSerializable = TestSerializable("serial", 12) val testParcelable = TestParcelable("parcel", 11) routerNavigation( PATH_ACTIVITY_LOGIN, mapOf( "key1" to 123, "key2" to "aaa", "key3" to "bbb", "testObj" to testObj, "list" to list, "testSerializable" to testSerializable, "testParcelable" to testParcelable ) )Copy the code
  • Receive parameters
@Autowired lateinit var testObj: TestObj @Autowired lateinit var list: List<TestObj? > @Autowired lateinit var testSerializable: TestSerializable @Autowired lateinit var testParcelable: TestParcelableCopy the code
2. Parse user-defined objects
  • TestObj is a custom object. To pass the custom object, you also need to implement a SerializationService
  • Parse through Gson or Fastjson
@ the Route (path = "/ serialization/gson") / / here and before routing defined rules, their good control group is not to repeat the class GsonSerializationServiceImpl: SerializationService { override fun init(context: Context?) { log("GsonSerializationServiceImpl.init") } override fun <T : Any? > json2Object(input: String? , clazz: Class<T>?) : T? { log("GsonSerializationServiceImpl.json2Object") return GsonUtils.fromJson(input, clazz) } override fun object2Json(instance: Any?): String { log("GsonSerializationServiceImpl.object2Json") return GsonUtils.toJson(instance) } override fun <T : Any?> parseObject(input: String?, clazz: Type?): T? { log("GsonSerializationServiceImpl.parseObject") return GsonUtils.fromJson(input, clazz) } }Copy the code
3. Go to the URL
  1. Create a new Activity in the baseRouter Module that listens for Scheme events and then passes the URL directly to ARouter
class SchemeFilterActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val uri = intent.data
        ARouter.getInstance().build(uri).navigation()
    }
}
Copy the code
  1. Register in the Androidmanifest.xml of the baseRouter Module to define the Scheme and App Links protocols respectively
<activity android:name=".ui.SchemeFilterActivity"> <! -- Scheme --> <intent-filter> <data android:host="test.ljy.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> <! -- App Links --> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="test.ljy.com" android:scheme="http" /> <data android:host="test.ljy.com" android:scheme="https" /> </intent-filter> </activity>Copy the code
  1. jump
routerNavigation( uri = Uri.parse("arouter://test.ljy.com$PATH_ACTIVITY_LOGIN"))
Copy the code
4. Use interceptors
  • To define an Interceptor, you need to implement IInterceptor and add @Interceptor, such as login verification, runtime permissions, etc
  • The priority parameter for annotations: Interceptors can define priorities, and if there are more than one interceptor, the interceptors will be executed in sequence
  • Take runtime permission verification as an example, the code is as follows
@Interceptor(priority = 0) class PermissionInterceptor : IInterceptor { private var context: Context? = null private var postcard: Postcard? = null private var callback: InterceptorCallback? = null override fun init(context: Context) { this.context = context log("PermissionInterceptor.init") } override fun process(postcard: Postcard, callback: InterceptorCallback) { log("PermissionInterceptor.process") this.postcard = postcard this.callback = callback if (postcard.path == PATH_ACTIVITY_WEB) { log("PermissionInterceptor.process: The path matching, To check the runtime permissions ") requestMyPermissions (arrayOf (Manifest. Permission. WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, )) {if (it) {log(" Permission request is allowed ") callback.onContinue(postcard)}else{log(" permission request is rejected ")}}}else{ The log (" PermissionInterceptor. Process: the path does not match, without intercept ") callback. The onContinue (it)}}}Copy the code
5. Downgrade strategy
  • That is, how to deal with the routing table that cannot be found
Global degradation strategy
  • Achieve survival of eservice and rewrite the onLost method
@Route(path = "/degrade/Test") class TestDegradeServiceImpl : DegradeService { override fun onLost(context: Context? , postcard: Postcard?) {log (" TestDegradeServiceImpl onLost: didn't find which by address: ${it? .path}") // Do something: Can provide an error page to jump to routerNavigation(PATH_ACTIVITY_ERROR)} Override fun init(context: context?) { log("TestDegradeServiceImpl.init") } }Copy the code
Local degradation strategy
  • NavigationCallback listens for single hops
routerNavigation("/aaa/bbb", callback = object : NavigationCallback { override fun onFound(postcard: Postcard?) {the log (" NavigationCallback onFound - found: $it ")} override fun onLost (it: it?) {the log (" NavigationCallback onLost - I can't find it: $it ") routerNavigation (PATH_ACTIVITY_ERROR)} override fun onArrival(postcard: Postcard?) {the log (" NavigationCallback onArrival - jump out: $it ")} override fun onInterrupt (it: it?) {the log (" NavigationCallback onInterrupt - the intercepted: $it ")}})Copy the code
6. Decoupling through dependency injection: service management
1. Offer services
  • Define the service interface interface that inherits from IProvider
interface HelloService:IProvider {
    fun sayHello(msg:String)
}
Copy the code
  • Create implementation classes for the service interface, two of which are provided below
@Route(path = PATH_SERVICE_HELLO, name = "hello service") class HelloServiceImpl:HelloService { override fun sayHello(msg: String) { log("Hello $msg") } override fun init(context: Context?) { log("HelloServiceImpl.init") } } @Route(path = PATH_SERVICE_NIHAO, name = "nihao service") class NiHaoServiceImpl : HelloService {override fun sayHello(MSG: String) {log(" hello $MSG ")} Override fun init(context: context?) { log("NiHaoServiceImpl.init") } }Copy the code
2. Discover the service
  1. Discovering services using dependency injection (recommended)
    • After annotating the fields in Autowired annotations, the corresponding fields will be injected by byName.
    • If the name attribute is not set, the service discovery mode is byType by default. (If multiple implementations of the same interface exist, the service discovery mode must be byName.)
byType@autoWired lateInit var helloService1: HelloService //byName @Autowired(name = PATH_SERVICE_HELLO) lateinit var helloService2: SayHello ("msg1") sayHello("msg2")Copy the code
  1. Use dependency lookup to discover services and actively discover and use services. The following two methods are byName and byType
Var helloService3: HelloService? = null var helloService4: HelloService? = null //byType helloService3 = ARouter.getInstance().navigation(HelloService::class.java) helloService3? .sayHello("msg3") //byName helloService4 = ARouter.getInstance().build(PATH_SERVICE_HELLO).navigation() as HelloService?  helloService4? .sayHello("msg4")Copy the code
7. Get fragments
  1. Define a Fragment and add the path with the @route annotation
@Route(path = PATH_FRAGMENT_DIALOG_TEST)
class TestDialogFragment : AppCompatDialogFragment() {
    ...
}
Copy the code
  1. Obtaining the Fragment instance
val fragment: AppCompatDialogFragment =ARouter.getInstance() .build(PATH_FRAGMENT_DIALOG_TEST).navigation() as AppCompatDialogFragment  (fragment as TestDialogFragment).setConfirmCallback { log("MainActivity confirmCallback ") } fragment.show(supportFragmentManager, "TestDialogFragment")Copy the code
8. Pretreatment service
  • PretreatmentService is required to implement interference routing before navigation.
  • Rewrite onpre () to preprocess the jump and return false if the jump needs to be processed by itself;
  • The interceptor function is similar to the preprocessor function, except that the preprocessor function precedes the interceptor;
@Route(path = "/pretreatment/test") class TestPretreatmentServiceImpl : PretreatmentService { override fun init(context: Context?) { log("TestPretreatmentServiceImpl.init") } override fun onPretreatment(context: Context? , postcard: Postcard?) : Boolean { log("TestPretreatmentServiceImpl.onPretreatment") if (postcard? .path == PATH_ACTIVITY_WEB) { if (! The ApplicationUtil. Instance. IsLogin) {Toast. MakeText (ApplicationUtil. Instance. GetAppContext (), "haven't login oh", Toast.LENGTH_SHORT ).show() routerNavigation(PATH_ACTIVITY_LOGIN) return false } } return true } }Copy the code
9. Redefine the URL forward
  • The PathReplaceService interface is required to process the path or URI to jump to, such as redirection
@Route(path = "/pathReplace/test") class TestPathReplaceServiceImpl : PathReplaceService { override fun init(context: Context?) { log("TestPathReplaceServiceImpl.init") } override fun forString(path: String): String {log (" TestPathReplaceServiceImpl replacePath ") / / according to certain rules of treatment after return the result of the return after the if (path = = PATH_ACTIVITY_MAIN) PATH_ACTIVITY_LOGIN else path } override fun forUri(uri: Uri?) : Uri? {the log (" TestPathReplaceServiceImpl replaceUri ") return the uri / / according to certain rules of treatment after return results after}}Copy the code
10. Dynamically register routing information
  • It is suitable for some apps with plug-in architecture and scenarios where dynamic routing information registration is required. Dynamic routing information registration can be realized through the interface provided by ARouter. The target page and service can not be marked with @route annotation
//1. An Activity class RegisterActivity: AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) setContentView(r.laiout.activity_register)}} //2 Arouter.getinstance ().addrouteGroup {it[PATH_ACTIVITY_REGISTER] = routemeta.build (routetype.activity,// Routing information Class.forname (" com. Jinyang. Login. RegisterActivity "), / / target Class PATH_ACTIVITY_REGISTER, // Path path_activity_register.split ("/")[1],// Group, try to keep the same as the first segment of Path 0, // priority, not use 0 for the moment // for page labeling)} //3. RouterNavigation (PATH_ACTIVITY_REGISTER)Copy the code
11. Perform more initial configurations
Private fun initRouter() {if (buildconfig.debug) {// Arouter.openlog () Arouter.opendebug () // Prints thread stack arouter.printStackTrace (); / / using your own log tool print log ARouter. SetLogger (myLogger) / / the use of their own thread pool ARouter. SetExecutor (DispatcherExecutor. GetCPUExecutor ())} ARouter. Init (this)} val myLogger = object: ILogger { private var mIsMonitorMode = false private var mIsShowStackTrace: Boolean = false override fun showLog(isShowLog: Boolean) { LjyLogUtil.setIsLog(isShowLog) } override fun showStackTrace(isShowStackTrace: Boolean) { mIsShowStackTrace = isShowStackTrace } override fun debug(tag: String? , message: String?) { LjyLogUtil.d("$tag --> $message") } override fun info(tag: String? , message: String?) { LjyLogUtil.i("$tag --> $message") } override fun warning(tag: String? , message: String?) { LjyLogUtil.w("$tag --> $message") } override fun error(tag: String? , message: String?) { LjyLogUtil.e("$tag --> $message") } override fun error(tag: String? , message: String? , e: Throwable?) { LjyLogUtil.e("$tag --> message=$message,e=${e? .message}") } override fun monitor(message: String?) { if (LjyLogUtil.isLog() && isMonitorMode) { val stackTraceElement = Thread.currentThread().stackTrace[3] LjyLogUtil.d("$defaultTag::monitor", message + DefaultLogger.getExtInfo(stackTraceElement)) } } override fun isMonitorMode(): Boolean { return mIsMonitorMode } override fun getDefaultTag(): String { return "LJY_LOG" } }Copy the code
12. Generate routing documents
// Update build.gradle with AROUTER_GENERATE_DOC = enable build/generated/source/apt/(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"] } } } }Copy the code
13. Similarities and differences between interceptors and services
  • Interceptors and services need to implement different interfaces, but have similar structures. Both have init(Context Context) methods, but they are called at different times.
  1. Interceptors, because of their specificity, can be triggered by any route. Interceptors are initialized asynchronously when the ARouter initializes. If the interceptors have not been initialized for the first time, the route will wait until initialization is complete.
  2. Services do not have this limitation. A service may not be used in the entire App life cycle, so the initialization operation will be triggered only when the service is called.
  • To this has followed the official documentation to the side, all kinds of use methods are basically listed all, let’s open the veil of the source code, see how he is implemented

The source code parsing

Routing principle

  • Mainly through the compilation time through APT scanning annotations, and corresponding processing, Java code generation through Javapoet library;
  • I have introduced APT and Javapoet in my previous exploration of Android open source framework 6. ButterKnife application and source code analysis. APT technology is widely used in Java framework, ButterKnife, EventBus, Dagger2 and ARouter are used in APT;
  • The main steps are as follows:
  1. Providers(IProviderGroup),Root(IRouteRoot). Use Warehouse to save the files to In three different Hashmaps, the Warehouse acts as a routing table, holding the jump relationships for all modules;
  2. Encapsulate the Postcard object with ARouter. Navigation.
  3. The ARouter index is passed to LogisticsCenter to ask if there is a jump object;
  4. Judge whether the traffic is green and whether it can pass the interception service;
  5. All through is called ActivityCompat. StartActivity method to jump to the Activity;
  • So ARouter actually uses startActivity, the native Framework mechanism, to create jump rules in the form of APT annotations, and artificially intercept jumps and set jump conditions;

ARouter.init

  • The code for arouter.init is as follows
public static void init(Application application) { if (! HasInit) {// Prevent repeated initialization of logger = _ARouter. Logger; _ARouter.logger.info(Consts.TAG, "ARouter init start."); Init hasInit = _ARouter. Init (application); If (hasInit) {// Call _arouter.afterinit (); } _ARouter.logger.info(Consts.TAG, "ARouter init over."); }}Copy the code
  • The _ARouter. Init code is as follows, mainly calling logisticScenter. init and creating mHandler
protected static synchronized boolean init(Application application) { mContext = application; LogisticsCenter.init(mContext, executor); logger.info(Consts.TAG, "ARouter init success!" ); hasInit = true; mHandler = new Handler(Looper.getMainLooper()); return true; }Copy the code
LogisticsCenter.init
  • Logisticscenter. init is responsible for loading the routing table
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { mContext = context; executor = tpe; try { long startInit = System.currentTimeMillis(); Arouter -register loadRouterMap(); if (registerByPlugin) { logger.info(TAG, "Load router map by arouter-auto-register plugin."); } else {routerMap Set<String> routerMap; // If openDebug is called, Or a new version if (ARouter debuggable () | | PackageUtils. IsNewVersion (context)) { / / traverse all the className in dex, filter out the prefix for the com. Alibaba. Android. Arouter. Routes, On the set set inside routerMap = ClassUtils. GetFileNameByPackageName (mContext ROUTE_ROOT_PAKCAGE); // Add routerMap to sp so that we can get if (! routerMap.isEmpty()) { context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply(); } / / update the latest version PackageUtils. UpdateVersion (context). // Save new version name when router map update.} else {// Finish in SP for the new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>())); } logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms."); startInit = System.currentTimeMillis(); for (String className : routerMap) { if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) { GroupsIndex ((IRouteRoot)); (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { / / the prefix ARouter $$Interceptors class in the Warehouse. The interceptorsIndex ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) { // Put the prefix ARouter$$Providers' class into warehouse.providersIndex ((IProviderGroup). (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex); } } } } catch (Exception e) { throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]"); }}Copy the code
_ARouter.afterInit
Static void afterInit() {aftertorService = (interceptorService); ARouter.getInstance().build("/arouter/service/interceptor").navigation(); }Copy the code

ARouter.build

  • Take a look at the ARouter. Build method called on the jump, as follows
public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}
Copy the code
  • Call _ARouter’s build as well
protected Postcard build(String path) { if (TextUtils.isEmpty(path)) { throw new HandlerException(Consts.TAG + "Parameter is invalid!" ); } else {/ navigation/call generates PathReplaceService instance, remember before we realize TestPathReplaceServiceImpl?  PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); // If the user implements PathReplaceService, it is called to handle the path or URI of the jump. If (null! = pService) {// PathReplaceService process path path = pservice.forstring (path); } //extractGroup is extractGroup return build(path, extractGroup(path), true); }}Copy the code
  • The build of the three parameters is the same as the build of the above. It first looks at whether PathReplaceService has been implemented and whether path or URI needs to be processed. Then it new one of the Postcard
protected Postcard build(String path, String group, Boolean afterReplace) { if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) { throw new HandlerException(Consts.TAG  + "Parameter is invalid!" ); } else { if (! afterReplace) { PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); if (null ! = pService) { path = pService.forString(path); } } return new Postcard(path, group); }}Copy the code
  • The Postcard saves all the information you need for a route jump and has a series of withXXX methods for us to set up
public final class Postcard extends RouteMeta { // Base private Uri uri; private Object tag; // A tag prepare for some thing wrong. inner params, DO NOT USE! private Bundle mBundle; // Data to transform private int flags = 0; // Flags of route private int timeout = 300; // Navigation timeout, TimeUnit.Second private IProvider provider; // It will be set value, if this postcard was provider. private boolean greenChannel; private SerializationService serializationService; private Context context; // May application or activity, check instance type before use it. private String action; // Animation private Bundle optionsCompat; // The transition animation of activity private int enterAnim = -1; private int exitAnim = -1; . }Copy the code

ARouter.navigation

  • After building, call navigation to perform the jump. There are a series of overloaded methods, all of which end up calling _arouter.getInstance ().navigation
public Object navigation() {
    return navigation(null);
}

public Object navigation(Context context) {
    return navigation(context, null);
}

public Object navigation(Context context, NavigationCallback callback) {
    return ARouter.getInstance().navigation(context, this, -1, callback);
}

public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
    return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
Copy the code
  • _arouter.getInstance (). Navigation code is as follows
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, Final NavigationCallback callback) {// Get PretreatmentService PretreatmentService PretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class); // If PretreatmentService exists and onPretreatment returns false, block this treatmentService if (null! = pretreatmentService && ! pretreatmentService.onPretreatment(context, postcard)) { // Pretreatment failed, navigation canceled. return null; } // Set context to postcard. postcard.setContext(null == context ? mContext : context); Try {/ / the most main is invoked in this line of LogisticsCenter.com pletion (it); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); If (debuggable()) {// Show friendly tips for user.runinMainThread (new Runnable() {@override public void run() { Toast.makeText(mContext, "There's no route matched! \n" + " Path = [" + postcard.getPath() + "]\n" + " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show(); }}); } // Call NavigationCallback onLost if (null! = callback) { callback.onLost(postcard); } else {// If the callback does not exist, it is separated into three groups to achieve a degradability. Degradates all routes to achieve survival = ARouter. GetInstance (). Navigation (degradates eservice.class); if (null ! = degradeService) { degradeService.onLost(context, postcard); } } return null; } if (null ! = callback) { callback.onFound(postcard); } if (! postcard.isGreenChannel()) { // It must be run in async thread, Maybe interceptor cost too mush time made ANR. / / interceptor service is in the above _ARouter afterInit interceptorService. Initialization in doInterceptions (it, new InterceptorCallback() { /** * Continue process * * @param postcard route meta */ @Override public void onContinue(Postcard postcard) { _navigation(postcard, requestCode, callback); } /** * Interrupt process, pipeline will be destory when this method called. * * @param exception Reson of interrupt. */ @Override public void onInterrupt(Throwable exception) { if (null ! = callback) { callback.onInterrupt(postcard); } logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage()); }}); } else { return _navigation(postcard, requestCode, callback); } return null; }Copy the code
LogisticsCenter.completion
public synchronized static void completion(Postcard postcard) { if (null == postcard) { throw new NoRouteFoundException(TAG + "No postcard!" ); RouteMeta RouteMeta = Warehouse. RouteMeta. Get (postcard.getPath()); if (null == routeMeta) { // Maybe its does't exist, or didn't load. if (! Warehouse. GroupsIndex. Either containsKey (it getGroup ())) {/ / if the group didn't find throw new NoRouteFoundException (TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]"); } else { // Load route and cache it into memory, then delete from metas. try { if (ARouter.debuggable()) { logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath())); } // Find the corresponding groupMeta addRouteGroupDynamic(postcard.getGroup(), null); if (ARouter.debuggable()) { logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath())); } } catch (Exception e) { throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "] "); } completion(postcard); // Reload } } else { postcard.setDestination(routeMeta.getDestination()); postcard.setType(routeMeta.getType()); postcard.setPriority(routeMeta.getPriority()); postcard.setExtra(routeMeta.getExtra()); Uri rawUri = postcard.getUri(); if (null ! = rawUri) { // Try to set params into bundle. Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri); Map<String, Integer> paramsType = routeMeta.getParamsType(); if (MapUtils.isNotEmpty(paramsType)) { // Set value by its type, just for params which annotation by @Param for (Map.Entry<String, Integer> params : paramsType.entrySet()) { setValue(postcard, params.getValue(), params.getKey(), resultMap.get(params.getKey())); } // Save params name which need auto inject. postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{})); } // Save raw uri postcard.withString(ARouter.RAW_URI, rawUri.toString()); Switch (routemeta.getType ()) {case PROVIDER: // Enable greenChannel if type is Fragment or IProvider. // if the route is provider, should find its instance // Its provider, so it must implement IProvider Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination(); IProvider instance = Warehouse.providers.get(providerMeta); if (null == instance) { // There's no instance of this provider IProvider provider; try { provider = providerMeta.getConstructor().newInstance(); provider.init(mContext); Warehouse.providers.put(providerMeta, provider); instance = provider; } catch (Exception e) { logger.error(TAG, "Init provider failed!" , e); throw new HandlerException("Init provider failed!" ); } } postcard.setProvider(instance); postcard.greenChannel(); // Provider should skip all of interceptors break; case FRAGMENT: postcard.greenChannel(); // Fragment needn't interceptors default: break; }}}Copy the code
Real jump method
  • The last line of the _navigation(Postcard, requestCode, callback) calls _navigation(Postcard, requestCode, callback) as follows, which you can see is the real method for handling jumps
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) { final Context currentContext = postcard.getContext(); switch (postcard.getType()) { case ACTIVITY: // Build intent final Intent intent = new Intent(currentContext, postcard.getDestination()); intent.putExtras(postcard.getExtras()); // Set flags. int flags = postcard.getFlags(); if (0 ! = flags) { intent.setFlags(flags); } // Non activity, need FLAG_ACTIVITY_NEW_TASK if (! (currentContext instanceof Activity)) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } // Set Actions String action = postcard.getAction(); if (! TextUtils.isEmpty(action)) { intent.setAction(action); } // Navigation in main looper. runInMainThread(new Runnable() { @Override public void run() { startActivity(requestCode, currentContext, intent, postcard, callback); }}); break; case PROVIDER: return postcard.getProvider(); case BOARDCAST: case CONTENT_PROVIDER: case FRAGMENT: Class<? > fragmentMeta = postcard.getDestination(); try { Object instance = fragmentMeta.getConstructor().newInstance(); if (instance instanceof Fragment) { ((Fragment) instance).setArguments(postcard.getExtras()); } else if (instance instanceof android.support.v4.app.Fragment) { ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras()); } return instance; } catch (Exception ex) { logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace())); } case METHOD: case SERVICE: default: return null; } return null; }Copy the code
InterceptorServiceImpl
  • The interceptor above service interceptorService InterceptorServiceImpl implementation class, in its init method, through the thread pool to traverse the Warehouse. InterceptorsIndex: Map

    > creates an interceptor instance into the collection List
    ,>
public void init(final Context context) { LogisticsCenter.executor.execute(new Runnable() { @Override public void run() { if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) { for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) { Class<? extends IInterceptor> interceptorClass = entry.getValue(); try { IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance(); iInterceptor.init(context); Warehouse.interceptors.add(iInterceptor); } catch (Exception ex) { throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]"); } } interceptorHasInit = true; logger.info(TAG, "ARouter interceptors init over."); synchronized (interceptorInitLock) { interceptorInitLock.notifyAll(); }}}}); }Copy the code
  • _execute is called from the doInterceptions by CountDownLatch, which decrements the count of the latch and releases all waiting threads if the count reaches zero.
@Override public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) { if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) { checkInterceptorsInitStatus(); // If the interceptor list is not initialized if (! interceptorHasInit) { callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time.")); return; } LogisticsCenter.executor.execute(new Runnable() { @Override public void run() { CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size()); try { _execute(0, interceptorCounter, postcard); // The interceptor will call callback.oninterrupt interceptorCounter.await(postcard.getTimeout(), timeUnit.seconds) if it times out; if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn't return anythings. callback.onInterrupt(new HandlerException("The interceptor processing timed out.")); } else if (null ! = postcard.getTag()) { // Maybe some exception in the tag. callback.onInterrupt((Throwable) postcard.getTag()); } else { callback.onContinue(postcard); } } catch (Exception e) { callback.onInterrupt(e); }}}); } else { callback.onContinue(postcard); }}Copy the code
  • The interceptor collection is traversed in _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) { // Last interceptor excute over with no exception. counter.countDown(); _execute(index + 1, counter, postcard); // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know. } @Override public void onInterrupt(Throwable exception) { // Last interceptor execute over with fatal exception. postcard.setTag(null == exception ? new HandlerException("No message.") : exception); // save the exception message for backup. counter.cancel(); // Be attention, maybe the thread in callback has been changed, // then the catch block(L207) will be invalid. // The worst is the thread changed to main thread, then the app will be crash, if you throw this exception! // if (! Looper.getMainLooper().equals(Looper.myLooper())) { // You shouldn't throw the exception if the thread is main thread. // throw new HandlerException(exception.getMessage()); / /}}}); }}Copy the code

APT compile-time annotations

  • Let’s take a look at how some of the annotations we used earlier generated the class file,
  • ARouter has three custom annotations: Route, Autowired, Interceptor, and RouteProcessor, AutowiredProcessor, and InterceptorProcessor
  • Take RouteProcessor as an example, the code is as follows. In fact, THE use of APT has been described in detail in my previous exploration of Android open source framework – 6. ButterKnife use and source code analysis
@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_AUTOWIRED})
public class AutowiredProcessor extends BaseProcessor {
    private Map<TypeElement, List<Element>> parentAndChild = new HashMap<>();   // Contain field need autowired and his super class.
    private static final ClassName ARouterClass = ClassName.get("com.alibaba.android.arouter.launcher", "ARouter");
    private static final ClassName AndroidLog = ClassName.get("android.util", "Log");

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        logger.info(">>> AutowiredProcessor init. <<<");
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (CollectionUtils.isNotEmpty(set)) {
            try {
                logger.info(">>> Found autowired field, start... <<<");
                categories(roundEnvironment.getElementsAnnotatedWith(Autowired.class));
                generateHelper();

            } catch (Exception e) {
                logger.error(e);
            }
            return true;
        }

        return false;
    }

    private void generateHelper() throws IOException, IllegalAccessException {
        TypeElement type_ISyringe = elementUtils.getTypeElement(ISYRINGE);
        TypeElement type_JsonService = elementUtils.getTypeElement(JSON_SERVICE);
        TypeMirror iProvider = elementUtils.getTypeElement(Consts.IPROVIDER).asType();
        TypeMirror activityTm = elementUtils.getTypeElement(Consts.ACTIVITY).asType();
        TypeMirror fragmentTm = elementUtils.getTypeElement(Consts.FRAGMENT).asType();
        TypeMirror fragmentTmV4 = elementUtils.getTypeElement(Consts.FRAGMENT_V4).asType();

        // Build input param name.
        ParameterSpec objectParamSpec = ParameterSpec.builder(TypeName.OBJECT, "target").build();

        if (MapUtils.isNotEmpty(parentAndChild)) {
            for (Map.Entry<TypeElement, List<Element>> entry : parentAndChild.entrySet()) {
                // Build method : 'inject'
                MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder(METHOD_INJECT)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(objectParamSpec);

                TypeElement parent = entry.getKey();
                List<Element> childs = entry.getValue();

                String qualifiedName = parent.getQualifiedName().toString();
                String packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf("."));
                String fileName = parent.getSimpleName() + NAME_OF_AUTOWIRED;

                logger.info(">>> Start process " + childs.size() + " field in " + parent.getSimpleName() + " ... <<<");

                TypeSpec.Builder helper = TypeSpec.classBuilder(fileName)
                        .addJavadoc(WARNING_TIPS)
                        .addSuperinterface(ClassName.get(type_ISyringe))
                        .addModifiers(PUBLIC);

                FieldSpec jsonServiceField = FieldSpec.builder(TypeName.get(type_JsonService.asType()), "serializationService", Modifier.PRIVATE).build();
                helper.addField(jsonServiceField);

                injectMethodBuilder.addStatement("serializationService = $T.getInstance().navigation($T.class)", ARouterClass, ClassName.get(type_JsonService));
                injectMethodBuilder.addStatement("$T substitute = ($T)target", ClassName.get(parent), ClassName.get(parent));

                // Generate method body, start inject.
                for (Element element : childs) {
                    Autowired fieldConfig = element.getAnnotation(Autowired.class);
                    String fieldName = element.getSimpleName().toString();
                    if (types.isSubtype(element.asType(), iProvider)) {  // It's provider
                        if ("".equals(fieldConfig.name())) {    // User has not set service path, then use byType.

                            // Getter
                            injectMethodBuilder.addStatement(
                                    "substitute." + fieldName + " = $T.getInstance().navigation($T.class)",
                                    ARouterClass,
                                    ClassName.get(element.asType())
                            );
                        } else {    // use byName
                            // Getter
                            injectMethodBuilder.addStatement(
                                    "substitute." + fieldName + " = ($T)$T.getInstance().build($S).navigation()",
                                    ClassName.get(element.asType()),
                                    ARouterClass,
                                    fieldConfig.name()
                            );
                        }

                        // Validater
                        if (fieldConfig.required()) {
                            injectMethodBuilder.beginControlFlow("if (substitute." + fieldName + " == null)");
                            injectMethodBuilder.addStatement(
                                    "throw new RuntimeException(\"The field '" + fieldName + "' is null, in class '\" + $T.class.getName() + \"!\")", ClassName.get(parent));
                            injectMethodBuilder.endControlFlow();
                        }
                    } else {    // It's normal intent value
                        String originalValue = "substitute." + fieldName;
                        String statement = "substitute." + fieldName + " = " + buildCastCode(element) + "substitute.";
                        boolean isActivity = false;
                        if (types.isSubtype(parent.asType(), activityTm)) {  // Activity, then use getIntent()
                            isActivity = true;
                            statement += "getIntent().";
                        } else if (types.isSubtype(parent.asType(), fragmentTm) || types.isSubtype(parent.asType(), fragmentTmV4)) {   // Fragment, then use getArguments()
                            statement += "getArguments().";
                        } else {
                            throw new IllegalAccessException("The field [" + fieldName + "] need autowired from intent, its parent must be activity or fragment!");
                        }

                        statement = buildStatement(originalValue, statement, typeUtils.typeExchange(element), isActivity);
                        if (statement.startsWith("serializationService.")) {   // Not mortals
                            injectMethodBuilder.beginControlFlow("if (null != serializationService)");
                            injectMethodBuilder.addStatement(
                                    "substitute." + fieldName + " = " + statement,
                                    (StringUtils.isEmpty(fieldConfig.name()) ? fieldName : fieldConfig.name()),
                                    ClassName.get(element.asType())
                            );
                            injectMethodBuilder.nextControlFlow("else");
                            injectMethodBuilder.addStatement(
                                    "$T.e(\"" + Consts.TAG + "\", \"You want automatic inject the field '" + fieldName + "' in class '$T' , then you should implement 'SerializationService' to support object auto inject!\")", AndroidLog, ClassName.get(parent));
                            injectMethodBuilder.endControlFlow();
                        } else {
                            injectMethodBuilder.addStatement(statement, StringUtils.isEmpty(fieldConfig.name()) ? fieldName : fieldConfig.name());
                        }

                        // Validator
                        if (fieldConfig.required() && !element.asType().getKind().isPrimitive()) {  // Primitive wont be check.
                            injectMethodBuilder.beginControlFlow("if (null == substitute." + fieldName + ")");
                            injectMethodBuilder.addStatement(
                                    "$T.e(\"" + Consts.TAG + "\", \"The field '" + fieldName + "' is null, in class '\" + $T.class.getName() + \"!\")", AndroidLog, ClassName.get(parent));
                            injectMethodBuilder.endControlFlow();
                        }
                    }
                }

                helper.addMethod(injectMethodBuilder.build());

                // Generate autowire helper
                JavaFile.builder(packageName, helper.build()).build().writeTo(mFiler);

                logger.info(">>> " + parent.getSimpleName() + " has been processed, " + fileName + " has been generated. <<<");
            }

            logger.info(">>> Autowired processor stop. <<<");
        }
    }

    private String buildCastCode(Element element) {
        if (typeUtils.typeExchange(element) == TypeKind.SERIALIZABLE.ordinal()) {
            return CodeBlock.builder().add("($T) ", ClassName.get(element.asType())).build().toString();
        }
        return "";
    }

    private String buildStatement(String originalValue, String statement, int type, boolean isActivity) {
        switch (TypeKind.values()[type]) {
            case BOOLEAN:
                statement += (isActivity ? ("getBooleanExtra($S, " + originalValue + ")") : ("getBoolean($S)"));
                break;
            case BYTE:
                statement += (isActivity ? ("getByteExtra($S, " + originalValue + ")") : ("getByte($S)"));
                break;
            case SHORT:
                statement += (isActivity ? ("getShortExtra($S, " + originalValue + ")") : ("getShort($S)"));
                break;
            case INT:
                statement += (isActivity ? ("getIntExtra($S, " + originalValue + ")") : ("getInt($S)"));
                break;
            case LONG:
                statement += (isActivity ? ("getLongExtra($S, " + originalValue + ")") : ("getLong($S)"));
                break;
            case CHAR:
                statement += (isActivity ? ("getCharExtra($S, " + originalValue + ")") : ("getChar($S)"));
                break;
            case FLOAT:
                statement += (isActivity ? ("getFloatExtra($S, " + originalValue + ")") : ("getFloat($S)"));
                break;
            case DOUBLE:
                statement += (isActivity ? ("getDoubleExtra($S, " + originalValue + ")") : ("getDouble($S)"));
                break;
            case STRING:
                statement += (isActivity ? ("getExtras() == null ? " + originalValue + " : substitute.getIntent().getExtras().getString($S, " + originalValue + ")") : ("getString($S)"));
                break;
            case SERIALIZABLE:
                statement += (isActivity ? ("getSerializableExtra($S)") : ("getSerializable($S)"));
                break;
            case PARCELABLE:
                statement += (isActivity ? ("getParcelableExtra($S)") : ("getParcelable($S)"));
                break;
            case OBJECT:
                statement = "serializationService.parseObject(substitute." + (isActivity ? "getIntent()." : "getArguments().") + (isActivity ? "getStringExtra($S)" : "getString($S)") + ", new " + TYPE_WRAPPER + "<$T>(){}.getType())";
                break;
        }

        return statement;
    }

    /**
     * Categories field, find his papa.
     *
     * @param elements Field need autowired
     */
    private void categories(Set<? extends Element> elements) throws IllegalAccessException {
        if (CollectionUtils.isNotEmpty(elements)) {
            for (Element element : elements) {
                TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

                if (element.getModifiers().contains(Modifier.PRIVATE)) {
                    throw new IllegalAccessException("The inject fields CAN NOT BE 'private'!!! please check field ["
                            + element.getSimpleName() + "] in class [" + enclosingElement.getQualifiedName() + "]");
                }

                if (parentAndChild.containsKey(enclosingElement)) { // Has categries
                    parentAndChild.get(enclosingElement).add(element);
                } else {
                    List<Element> childs = new ArrayList<>();
                    childs.add(element);
                    parentAndChild.put(enclosingElement, childs);
                }
            }

            logger.info("categories finished.");
        }
    }
}

Copy the code

reference

  • ARouter source
  • Ali ARouter: There’s always something you don’t know
  • ARouter parsing

My name is Jinyang. If you want to learn more about jinyang, please pay attention to the wechat public number “Jinyang said” to receive my latest articles