paramsType) {
+ this.paramsType = paramsType;
+ return this;
+ }
+
+ public Element getRawType() {
+ return rawType;
+ }
+
+ public RouteMeta setRawType(Element rawType) {
+ this.rawType = rawType;
+ return this;
+ }
+
+ public RouteType getType() {
+ return type;
+ }
+
+ public RouteMeta setType(RouteType type) {
+ this.type = type;
+ return this;
+ }
+
+ public Class> getDestination() {
+ return destination;
+ }
+
+ public RouteMeta setDestination(Class> destination) {
+ this.destination = destination;
+ return this;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public RouteMeta setPath(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public String getGroup() {
+ return group;
+ }
+
+ public RouteMeta setGroup(String group) {
+ this.group = group;
+ return this;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public RouteMeta setPriority(int priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ public int getExtra() {
+ return extra;
+ }
+
+ public RouteMeta setExtra(int extra) {
+ this.extra = extra;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "RouteMeta{" +
+ "type=" + type +
+ ", rawType=" + rawType +
+ ", destination=" + destination +
+ ", path='" + path + '\'' +
+ ", group='" + group + '\'' +
+ ", priority=" + priority +
+ ", extra=" + extra +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/arouter-api/build.gradle b/arouter-api/build.gradle
new file mode 100644
index 00000000..7c7a526c
--- /dev/null
+++ b/arouter-api/build.gradle
@@ -0,0 +1,41 @@
+apply plugin: 'com.android.library'
+
+ext {
+ bintrayName = 'arouter-api'
+ artifact = bintrayName
+ libraryName = 'ARouter sdk'
+ libraryDescription = 'A router for android'
+ libraryVersion = '1.0.1'
+}
+
+android {
+ compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)
+ buildToolsVersion BUILDTOOLS_VERSION
+
+ defaultConfig {
+ minSdkVersion Integer.parseInt(MIN_SDK_VERSION)
+ targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ buildTypes {
+ release {
+ debuggable false
+ minifyEnabled false
+ }
+
+ lintOptions { abortOnError false }
+ }
+}
+
+dependencies {
+ compile 'com.alibaba:arouter-annotation:1.0.0'
+ compile 'com.android.support:support-v4:23.1.1'
+}
+
+apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
+apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
\ No newline at end of file
diff --git a/arouter-api/src/main/AndroidManifest.xml b/arouter-api/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..91c2eae2
--- /dev/null
+++ b/arouter-api/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/core/InstrumentationHook.java b/arouter-api/src/main/java/com/alibaba/android/arouter/core/InstrumentationHook.java
new file mode 100644
index 00000000..ded93883
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/core/InstrumentationHook.java
@@ -0,0 +1,101 @@
+package com.alibaba.android.arouter.core;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+
+import com.alibaba.android.arouter.launcher.ARouter;
+import com.alibaba.android.arouter.utils.Consts;
+
+import java.lang.reflect.Field;
+
+
+/**
+ * Hook the instrumentation, inject values for activity's field.
+ * Support normal activity only, not contain unit test.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 2016/11/24 16:42
+ */
+public class InstrumentationHook extends Instrumentation {
+
+ /**
+ * Perform instantiation of an {@link Activity} object. This method is intended for use with
+ * unit tests, such as android.test.ActivityUnitTestCase. The activity will be useable
+ * locally but will be missing some of the linkages necessary for use within the sytem.
+ *
+ * @param clazz The Class of the desired Activity
+ * @param context The base context for the activity to use
+ * @param token The token for this activity to communicate with
+ * @param application The application object (if any)
+ * @param intent The intent that started this Activity
+ * @param info ActivityInfo from the manifest
+ * @param title The title, typically retrieved from the ActivityInfo record
+ * @param parent The parent Activity (if any)
+ * @param id The embedded Id (if any)
+ * @param lastNonConfigurationInstance Arbitrary object that will be
+ * available via {@link Activity#getLastNonConfigurationInstance()
+ * Activity.getLastNonConfigurationInstance()}.
+ * @return Returns the instantiated activity
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ */
+// public Activity newActivity(Class> clazz, Context context,
+// IBinder token, Application application, Intent intent, ActivityInfo info,
+// CharSequence title, Activity parent, String id,
+// Object lastNonConfigurationInstance) throws InstantiationException,
+// IllegalAccessException {
+// Activity activity = (Activity)clazz.newInstance();
+// ActivityThread aThread = null;
+// activity.attach(context, aThread, this, token, 0, application, intent,
+// info, title, parent, id,
+// (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
+// new Configuration(), null, null);
+// return activity;
+// }
+
+ /**
+ * Hook the instrumentation's newActivity, inject
+ *
+ * Perform instantiation of the process's {@link Activity} object. The
+ * default implementation provides the normal system behavior.
+ *
+ * @param cl The ClassLoader with which to instantiate the object.
+ * @param className The name of the class implementing the Activity
+ * object.
+ * @param intent The Intent object that specified the activity class being
+ * instantiated.
+ * @return The newly instantiated Activity object.
+ */
+ public Activity newActivity(ClassLoader cl, String className,
+ Intent intent)
+ throws InstantiationException, IllegalAccessException,
+ ClassNotFoundException {
+
+// return (Activity)cl.loadClass(className).newInstance();
+
+ Class> targetActivity = cl.loadClass(className);
+ Object instanceOfTarget = targetActivity.newInstance();
+
+ if (ARouter.canAutoInject()) {
+ String[] autoInjectParams = intent.getStringArrayExtra(ARouter.AUTO_INJECT);
+ if (null != autoInjectParams && autoInjectParams.length > 0) {
+ for (String paramsName : autoInjectParams) {
+ Object value = intent.getExtras().get(LogisticsCenter.getLeft(paramsName));
+ if (null != value) {
+ try {
+ Field injectField = targetActivity.getDeclaredField(LogisticsCenter.getLeft(paramsName));
+ injectField.setAccessible(true);
+ injectField.set(instanceOfTarget, value);
+ } catch (Exception e) {
+ ARouter.logger.error(Consts.TAG, "Inject values for activity error! [" + e.getMessage() + "]");
+ }
+ }
+ }
+ }
+ }
+
+ return (Activity) instanceOfTarget;
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/core/LogisticsCenter.java b/arouter-api/src/main/java/com/alibaba/android/arouter/core/LogisticsCenter.java
new file mode 100644
index 00000000..b6da8db3
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/core/LogisticsCenter.java
@@ -0,0 +1,420 @@
+package com.alibaba.android.arouter.core;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.alibaba.android.arouter.exception.HandlerException;
+import com.alibaba.android.arouter.exception.NoRouteFoundException;
+import com.alibaba.android.arouter.facade.Postcard;
+import com.alibaba.android.arouter.facade.callback.InterceptorCallback;
+import com.alibaba.android.arouter.facade.model.RouteMeta;
+import com.alibaba.android.arouter.facade.template.IInterceptor;
+import com.alibaba.android.arouter.facade.template.IInterceptorGroup;
+import com.alibaba.android.arouter.facade.template.IProvider;
+import com.alibaba.android.arouter.facade.template.IProviderGroup;
+import com.alibaba.android.arouter.facade.template.IRouteGroup;
+import com.alibaba.android.arouter.facade.template.IRouteRoot;
+import com.alibaba.android.arouter.launcher.ARouter;
+import com.alibaba.android.arouter.thread.CancelableCountDownLatch;
+import com.alibaba.android.arouter.utils.ClassUtils;
+import com.alibaba.android.arouter.utils.Consts;
+import com.alibaba.android.arouter.utils.TextUtils;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import static com.alibaba.android.arouter.launcher.ARouter.logger;
+import static com.alibaba.android.arouter.utils.Consts.DOT;
+import static com.alibaba.android.arouter.utils.Consts.ROUTE_ROOT_PAKCAGE;
+import static com.alibaba.android.arouter.utils.Consts.SDK_NAME;
+import static com.alibaba.android.arouter.utils.Consts.SEPARATOR;
+import static com.alibaba.android.arouter.utils.Consts.SUFFIX_INTERCEPTORS;
+import static com.alibaba.android.arouter.utils.Consts.SUFFIX_PROVIDERS;
+import static com.alibaba.android.arouter.utils.Consts.SUFFIX_ROOT;
+import static com.alibaba.android.arouter.utils.Consts.TAG;
+
+/**
+ * LogisticsCenter contain all of the map.
+ *
+ * 1. Create instance when it first used.
+ * 2. Handler Multi-Module relationship map(*)
+ * 3. Complex logic to solve duplicate group definition
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/23 15:02
+ */
+public class LogisticsCenter {
+ // Cache route and metas
+ private static Map> groupsIndex = new HashMap<>();
+ private static Map routes = new HashMap<>();
+
+ // Cache provider
+ private static Map providers = new HashMap<>();
+ private static Map providersIndex = new HashMap<>();
+
+ // Cache interceptor
+ private static Map> interceptorsIndex = new TreeMap<>();
+ private static List interceptors = new ArrayList<>();
+
+ private static Context mContext;
+
+ private static ThreadPoolExecutor executor;
+
+ private static boolean interceptorHasInit;
+
+ private static final Object interceptorInitLock = new Object();
+
+ /**
+ * LogisticsCenter init, load all metas in memory. Demand initialization
+ *
+ * @throws HandlerException
+ */
+ public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
+ mContext = context;
+ executor = tpe;
+
+ try {
+ // These class was generate by arouter-compiler.
+ List classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
+
+ //
+ for (String className : classFileNames) {
+ if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
+ // This one of root elements, load root.
+ ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(groupsIndex);
+ } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
+ // Load interceptorMeta
+ ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(interceptorsIndex);
+ } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
+ // Load providerIndex
+ ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(providersIndex);
+ }
+ }
+
+ if (ARouter.debuggable()) {
+ logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", groupsIndex.size(), interceptorsIndex.size(), providersIndex.size()));
+ }
+ } catch (Exception e) {
+ throw new HandlerException(TAG + "ARouter init atlas exception! [" + e.getMessage() + "]");
+ }
+ }
+
+ /**
+ * Init interceptors
+ */
+ public static void initInterceptors() {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (null != interceptorsIndex && interceptorsIndex.size() > 0) {
+ for (Map.Entry> entry : interceptorsIndex.entrySet()) {
+ Class extends IInterceptor> interceptorClass = entry.getValue();
+ try {
+ IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
+ iInterceptor.init(mContext);
+ 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();
+ }
+ }
+ }
+ });
+ }
+
+ private static void checkInterceptorsInitStatus() {
+ synchronized (interceptorInitLock) {
+ while (!interceptorHasInit) {
+ try {
+ interceptorInitLock.wait(10 * 1000);
+ } catch (InterruptedException e) {
+ throw new HandlerException(TAG + "ARouter waiting for interceptor init error! reason = [" + e.getMessage() + "]");
+ }
+ }
+ }
+ }
+
+ /**
+ * Build postcard by serviceName
+ *
+ * @param serviceName interfaceName
+ * @return postcard
+ */
+ public static Postcard buildProvider(String serviceName) {
+ RouteMeta meta = providersIndex.get(serviceName);
+
+ if (null == meta) {
+ return null;
+ } else {
+ return new Postcard(meta.getPath(), meta.getGroup());
+ }
+ }
+
+ /**
+ * Completion the postcard by route metas
+ *
+ * @param postcard Incomplete postcard, should completion by this method.
+ * @throws NoRouteFoundException
+ * @throws HandlerException
+ */
+ public synchronized static void completion(Postcard postcard) {
+ if (null == postcard) {
+ throw new NoRouteFoundException(TAG + "No postcard!");
+ }
+
+ RouteMeta routeMeta = routes.get(postcard.getPath());
+ if (null == routeMeta) { // Maybe its does't exist, or didn't load.
+ Class extends IRouteGroup> groupMeta = groupsIndex.get(postcard.getGroup()); // Load route meta.
+ if (null == groupMeta) {
+ 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()));
+ }
+
+ IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
+ iGroupInstance.loadInto(routes);
+ groupsIndex.remove(postcard.getGroup());
+
+ 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 resultMap = TextUtils.splitQueryParameters(rawUri);
+ Map paramsType = routeMeta.getParamsType();
+
+ if (MapUtils.isNotEmpty(paramsType)) {
+ // Set value by its type, just for params which annotation by @Param
+ for (Map.Entry params : paramsType.entrySet()) {
+ setValue(postcard,
+ params.getValue(),
+ params.getKey(),
+ resultMap.get(
+ getRight(params.getKey())
+ ));
+ }
+
+ // Save params name which need autoinject.
+ 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: // if the route is provider, should find its instance
+ // Its provider, so it must be implememt IProvider
+ Class extends IProvider> providerMeta = (Class extends IProvider>) routeMeta.getDestination();
+ IProvider instance = providers.get(providerMeta);
+ if (null == instance) { // There's no instance of this provider
+ IProvider provider;
+ try {
+ provider = providerMeta.getConstructor().newInstance();
+ provider.init(mContext);
+ providers.put(providerMeta, provider);
+ instance = provider;
+ } catch (Exception e) {
+ throw new HandlerException("Init provider failed! " + e.getMessage());
+ }
+ }
+ postcard.setProvider(instance);
+ postcard.greenChannel(); // Provider should skip all of interceptors
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Set value by known type
+ *
+ * @param postcard postcard
+ * @param typeDef type
+ * @param key key
+ * @param value value
+ */
+ private static void setValue(Postcard postcard, Integer typeDef, String key, String value) {
+ try {
+ String currentKey = getLeft(key);
+
+ if (null != typeDef) {
+ switch (typeDef) {
+ case Consts.DEF_BOOLEAN:
+ postcard.withBoolean(currentKey, Boolean.parseBoolean(value));
+ break;
+ case Consts.DEF_BYTE:
+ postcard.withByte(currentKey, Byte.valueOf(value));
+ break;
+ case Consts.DEF_SHORT:
+ postcard.withShort(currentKey, Short.valueOf(value));
+ break;
+ case Consts.DEF_INT:
+ postcard.withInt(currentKey, Integer.valueOf(value));
+ break;
+ case Consts.DEF_LONG:
+ postcard.withLong(currentKey, Long.valueOf(value));
+ break;
+ case Consts.DEF_FLOAT:
+ postcard.withFloat(currentKey, Float.valueOf(value));
+ break;
+ case Consts.DEF_DOUBLE:
+ postcard.withDouble(currentKey, Double.valueOf(value));
+ break;
+ case Consts.DEF_STRING:
+ default:
+ postcard.withString(currentKey, value);
+ }
+ } else {
+ postcard.withString(currentKey, value);
+ }
+ } catch (Throwable ex) {
+ logger.warning(Consts.TAG, "LogisticsCenter setValue failed! " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Split key with |
+ *
+ * @param key raw key
+ * @return left key
+ */
+ public static String getLeft(String key) {
+ if (key.contains("|") && !key.endsWith("|")) {
+ return key.substring(0, key.indexOf("|"));
+ } else {
+ return key;
+ }
+ }
+
+ /**
+ * Split key with |
+ *
+ * @param key raw key
+ * @return right key
+ */
+ private static String getRight(String key) {
+ if (key.contains("|") && !key.startsWith("|")) {
+ return key.substring(key.indexOf("|") + 1);
+ } else {
+ return key;
+ }
+ }
+
+ /**
+ * Start interceptions, if its not from green channal.
+ *
+ * @param postcard routeMetas.
+ */
+ public static void interceptions(final Postcard postcard, final InterceptorCallback callback) throws HandlerException {
+ if (CollectionUtils.isNotEmpty(interceptors)) {
+
+ checkInterceptorsInitStatus();
+
+ if (!interceptorHasInit) {
+ callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
+ return;
+ }
+
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(interceptors.size());
+ try {
+ _excute(0, interceptorCounter, postcard);
+ interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS); // Cancel the navigation this time, if it hasn't return anythings.
+ if (null != postcard.getTag()) { // Maybe some exception in the tag.
+ callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
+ } else {
+ callback.onContinue(postcard);
+ }
+ } catch (Exception e) {
+ callback.onInterrupt(e);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Excute interceptor
+ *
+ * @param index current interceptor index
+ * @param counter interceptor counter
+ * @param postcard routeMeta
+ */
+ private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
+ if (index < interceptors.size()) {
+ IInterceptor iInterceptor = interceptors.get(index);
+ iInterceptor.process(postcard, new InterceptorCallback() {
+ @Override
+ public void onContinue(Postcard postcard) {
+ // Last interceptor excute over with no exception.
+ counter.countDown();
+ _excute(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 excute over with fatal exception.
+
+ postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage()); // 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());
+// }
+ }
+ });
+ }
+ }
+
+ /**
+ * Suspend bussiness, clear cache.
+ */
+ public static void suspend() {
+ routes.clear();
+ groupsIndex.clear();
+ providers.clear();
+ providersIndex.clear();
+ interceptors.clear();
+ interceptorsIndex.clear();
+ interceptorHasInit = false;
+ }
+}
\ No newline at end of file
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/exception/HandlerException.java b/arouter-api/src/main/java/com/alibaba/android/arouter/exception/HandlerException.java
new file mode 100644
index 00000000..004f5b9e
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/exception/HandlerException.java
@@ -0,0 +1,14 @@
+package com.alibaba.android.arouter.exception;
+
+/**
+ * 主流程的处理异常
+ *
+ * @author zhilong Contact me.
+ * @version 1.0
+ * @since 15/12/7 上午10:30
+ */
+public class HandlerException extends RuntimeException {
+ public HandlerException(String detailMessage) {
+ super(detailMessage);
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/exception/InitException.java b/arouter-api/src/main/java/com/alibaba/android/arouter/exception/InitException.java
new file mode 100644
index 00000000..8e912003
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/exception/InitException.java
@@ -0,0 +1,14 @@
+package com.alibaba.android.arouter.exception;
+
+/**
+ * 初始化相关异常
+ *
+ * @author zhilong Contact me.
+ * @version 1.0
+ * @since 2015-12-07 14:17:30
+ */
+public class InitException extends RuntimeException {
+ public InitException(String detailMessage) {
+ super(detailMessage);
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/exception/NoRouteFoundException.java b/arouter-api/src/main/java/com/alibaba/android/arouter/exception/NoRouteFoundException.java
new file mode 100644
index 00000000..3c88c0a8
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/exception/NoRouteFoundException.java
@@ -0,0 +1,20 @@
+package com.alibaba.android.arouter.exception;
+
+/**
+ * As its name
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/24 10:43
+ */
+public class NoRouteFoundException extends RuntimeException {
+ /**
+ * Constructs a new {@code RuntimeException} with the current stack trace
+ * and the specified detail message.
+ *
+ * @param detailMessage the detail message for this exception.
+ */
+ public NoRouteFoundException(String detailMessage) {
+ super(detailMessage);
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/Postcard.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/Postcard.java
new file mode 100644
index 00000000..608693de
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/Postcard.java
@@ -0,0 +1,510 @@
+package com.alibaba.android.arouter.facade;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.util.SparseArray;
+
+import com.alibaba.android.arouter.facade.callback.NavigationCallback;
+import com.alibaba.android.arouter.facade.model.RouteMeta;
+import com.alibaba.android.arouter.facade.template.IProvider;
+import com.alibaba.android.arouter.launcher.ARouter;
+
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * A container that contains the roadmap.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/22 19:16
+ */
+public final class Postcard extends RouteMeta {
+ private Uri uri;
+ private Object tag; // A tag prepare for some thing wrong.
+ private Bundle mBundle; // Data to tranform
+ private int flags = -1; // 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 greenChannal;
+
+ public IProvider getProvider() {
+ return provider;
+ }
+
+ public Postcard setProvider(IProvider provider) {
+ this.provider = provider;
+ return this;
+ }
+
+ public Postcard() {
+ this(null, null);
+ }
+
+ public Postcard(String path, String group) {
+ this(path, group, null, null);
+ }
+
+ public Postcard(String path, String group, Uri uri, Bundle bundle) {
+ setPath(path);
+ setGroup(group);
+ setUri(uri);
+ this.mBundle = (null == bundle ? new Bundle() : bundle);
+ }
+
+ public boolean isGreenChannal() {
+ return greenChannal;
+ }
+
+ public Object getTag() {
+ return tag;
+ }
+
+ public Postcard setTag(Object tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ public Bundle getExtras() {
+ return mBundle;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Set timeout of navigation this time.
+ *
+ * @param timeout timeout
+ * @return this
+ */
+ public Postcard setTimeout(int timeout) {
+ this.timeout = timeout;
+ return this;
+ }
+
+ public Uri getUri() {
+ return uri;
+ }
+
+ public Postcard setUri(Uri uri) {
+ this.uri = uri;
+ return this;
+ }
+
+ /**
+ * Navigation to the route with path in postcard.
+ * No param, will be use application context.
+ */
+ public Object navigation() {
+ return navigation(null);
+ }
+
+ /**
+ * Navigation to the route with path in postcard.
+ *
+ * @param context Activity and so on.
+ */
+ public Object navigation(Context context) {
+ return navigation(context, null);
+ }
+
+ /**
+ * Navigation to the route with path in postcard.
+ *
+ * @param context Activity and so on.
+ */
+ public Object navigation(Context context, NavigationCallback callback) {
+ return ARouter.getInstance().navigation(context, this, -1, callback);
+ }
+
+ /**
+ * Navigation to the route with path in postcard.
+ *
+ * @param mContext Activity and so on.
+ * @param requestCode startActivityForResult's param
+ */
+ public void navigation(Activity mContext, int requestCode) {
+ navigation(mContext, requestCode, null);
+ }
+
+ /**
+ * Navigation to the route with path in postcard.
+ *
+ * @param mContext Activity and so on.
+ * @param requestCode startActivityForResult's param
+ */
+ public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {
+ ARouter.getInstance().navigation(mContext, this, requestCode, callback);
+ }
+
+ /**
+ * Green channal, it will skip all of interceptors.
+ *
+ * @return this
+ */
+ public Postcard greenChannel() {
+ this.greenChannal = true;
+ return this;
+ }
+
+ /**
+ * BE ATTENTION TO THIS METHOD WAS SET, NOT ADD!
+ */
+ public Postcard with(Bundle bundle) {
+ if (null != bundle) {
+ mBundle = bundle;
+ }
+
+ return this;
+ }
+
+ @IntDef({
+ Intent.FLAG_ACTIVITY_SINGLE_TOP,
+ Intent.FLAG_ACTIVITY_NEW_TASK,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ Intent.FLAG_DEBUG_LOG_RESOLUTION,
+ Intent.FLAG_FROM_BACKGROUND,
+ Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT,
+ Intent.FLAG_ACTIVITY_CLEAR_TASK,
+ Intent.FLAG_ACTIVITY_CLEAR_TOP,
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
+ Intent.FLAG_ACTIVITY_FORWARD_RESULT,
+ Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY,
+ Intent.FLAG_ACTIVITY_MULTIPLE_TASK,
+ Intent.FLAG_ACTIVITY_NO_ANIMATION,
+ Intent.FLAG_ACTIVITY_NO_USER_ACTION,
+ Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP,
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,
+ Intent.FLAG_ACTIVITY_REORDER_TO_FRONT,
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME,
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FlagInt {
+ }
+
+ /**
+ * Set special flags controlling how this intent is handled. Most values
+ * here depend on the type of component being executed by the Intent,
+ * specifically the FLAG_ACTIVITY_* flags are all for use with
+ * {@link Context#startActivity Context.startActivity()} and the
+ * FLAG_RECEIVER_* flags are all for use with
+ * {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}.
+ */
+ public Postcard withFlags(@FlagInt int flag) {
+ this.flags = flag;
+ return this;
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ // Follow api copy from #{Bundle}
+
+ /**
+ * Inserts a String value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a String, or null
+ */
+ public Postcard withString(@Nullable String key, @Nullable String value) {
+ mBundle.putString(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a Boolean value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a boolean
+ */
+ public Postcard withBoolean(@Nullable String key, boolean value) {
+ mBundle.putBoolean(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a short value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a short
+ */
+ public Postcard withShort(@Nullable String key, short value) {
+ mBundle.putShort(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts an int value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value an int
+ */
+ public Postcard withInt(@Nullable String key, int value) {
+ mBundle.putInt(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a long value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a long
+ */
+ public Postcard withLong(@Nullable String key, long value) {
+ mBundle.putLong(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a double value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a double
+ */
+ public Postcard withDouble(@Nullable String key, double value) {
+ mBundle.putDouble(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a byte value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a byte
+ */
+ public Postcard withByte(@Nullable String key, byte value) {
+ mBundle.putByte(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a char value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a char
+ */
+ public Postcard withChar(@Nullable String key, char value) {
+ mBundle.putChar(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a float value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a float
+ */
+ public Postcard withFloat(@Nullable String key, float value) {
+ mBundle.putFloat(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a CharSequence value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a CharSequence, or null
+ */
+ public Postcard withCharSequence(@Nullable String key, @Nullable CharSequence value) {
+ mBundle.putCharSequence(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a Parcelable value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Parcelable object, or null
+ */
+ public Postcard withParcelable(@Nullable String key, @Nullable Parcelable value) {
+ mBundle.putParcelable(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts an array of Parcelable values into the mapping of this Bundle,
+ * replacing any existing value for the given key. Either key or value may
+ * be null.
+ *
+ * @param key a String, or null
+ * @param value an array of Parcelable objects, or null
+ */
+ public Postcard withParcelableArray(@Nullable String key, @Nullable Parcelable[] value) {
+ mBundle.putParcelableArray(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a List of Parcelable values into the mapping of this Bundle,
+ * replacing any existing value for the given key. Either key or value may
+ * be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList of Parcelable objects, or null
+ */
+ public Postcard withParcelableArrayList(@Nullable String key, @Nullable ArrayList extends Parcelable> value) {
+ mBundle.putParcelableArrayList(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a SparceArray of Parcelable values into the mapping of this
+ * Bundle, replacing any existing value for the given key. Either key
+ * or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a SparseArray of Parcelable objects, or null
+ */
+ public Postcard withSparseParcelableArray(@Nullable String key, @Nullable SparseArray extends Parcelable> value) {
+ mBundle.putSparseParcelableArray(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts an ArrayList value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList object, or null
+ */
+ public Postcard withIntegerArrayList(@Nullable String key, @Nullable ArrayList value) {
+ mBundle.putIntegerArrayList(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts an ArrayList value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList object, or null
+ */
+ public Postcard withStringArrayList(@Nullable String key, @Nullable ArrayList value) {
+ mBundle.putStringArrayList(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts an ArrayList value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList object, or null
+ */
+ public Postcard withCharSequenceArrayList(@Nullable String key, @Nullable ArrayList value) {
+ mBundle.putCharSequenceArrayList(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a Serializable value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Serializable object, or null
+ */
+ public Postcard withSerializable(@Nullable String key, @Nullable Serializable value) {
+ mBundle.putSerializable(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a byte array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a byte array object, or null
+ */
+ public Postcard withByteArray(@Nullable String key, @Nullable byte[] value) {
+ mBundle.putByteArray(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a short array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a short array object, or null
+ */
+ public Postcard withShortArray(@Nullable String key, @Nullable short[] value) {
+ mBundle.putShortArray(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a char array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a char array object, or null
+ */
+ public Postcard withCharArray(@Nullable String key, @Nullable char[] value) {
+ mBundle.putCharArray(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a float array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a float array object, or null
+ */
+ public Postcard withFloatArray(@Nullable String key, @Nullable float[] value) {
+ mBundle.putFloatArray(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a CharSequence array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a CharSequence array object, or null
+ */
+ public Postcard withCharSequenceArray(@Nullable String key, @Nullable CharSequence[] value) {
+ mBundle.putCharSequenceArray(key, value);
+ return this;
+ }
+
+ /**
+ * Inserts a Bundle value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Bundle object, or null
+ */
+ public Postcard withBundle(@Nullable String key, @Nullable Bundle value) {
+ mBundle.putBundle(key, value);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Postcard " + super.toString();
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/callback/InterceptorCallback.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/callback/InterceptorCallback.java
new file mode 100644
index 00000000..7aa73c73
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/callback/InterceptorCallback.java
@@ -0,0 +1,27 @@
+package com.alibaba.android.arouter.facade.callback;
+
+import com.alibaba.android.arouter.facade.Postcard;
+
+/**
+ * The callback of interceptor.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/4 17:36
+ */
+public interface InterceptorCallback {
+
+ /**
+ * Continue process
+ *
+ * @param postcard route meta
+ */
+ void onContinue(Postcard postcard);
+
+ /**
+ * Interrupt process, pipeline will be destory when this method called.
+ *
+ * @param exception Reson of interrupt.
+ */
+ void onInterrupt(Throwable exception);
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/callback/NavigationCallback.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/callback/NavigationCallback.java
new file mode 100644
index 00000000..e398fbeb
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/callback/NavigationCallback.java
@@ -0,0 +1,25 @@
+package com.alibaba.android.arouter.facade.callback;
+
+import com.alibaba.android.arouter.facade.Postcard;
+
+/**
+ * Callback after navigation.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 2016/9/22 14:15
+ */
+public interface NavigationCallback {
+
+ /**
+ * Callback after you find the destination.
+ * @param postcard meta
+ */
+ void onFound(Postcard postcard);
+
+ /**
+ * Callback after you lose your way.
+ * @param postcard meta
+ */
+ void onLost(Postcard postcard);
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/service/DegradeService.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/service/DegradeService.java
new file mode 100644
index 00000000..66a8311e
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/service/DegradeService.java
@@ -0,0 +1,23 @@
+package com.alibaba.android.arouter.facade.service;
+
+import android.content.Context;
+
+import com.alibaba.android.arouter.facade.Postcard;
+import com.alibaba.android.arouter.facade.template.IProvider;
+
+/**
+ * Provide degrade service for router, you can do something when route has lost.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 2016/9/22 14:51
+ */
+public interface DegradeService extends IProvider {
+
+ /**
+ * Router has lost.
+ *
+ * @param postcard meta
+ */
+ void onLost(Context context, Postcard postcard);
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/service/PathReplaceService.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/service/PathReplaceService.java
new file mode 100644
index 00000000..524689d9
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/service/PathReplaceService.java
@@ -0,0 +1,29 @@
+package com.alibaba.android.arouter.facade.service;
+
+import android.net.Uri;
+
+import com.alibaba.android.arouter.facade.template.IProvider;
+
+/**
+ * Preprocess your path
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 2016/12/9 16:48
+ */
+public interface PathReplaceService extends IProvider {
+
+ /**
+ * For normal path.
+ *
+ * @param path raw path
+ */
+ String forString(String path);
+
+ /**
+ * For uri type.
+ *
+ * @param uri raw uri
+ */
+ Uri forUri(Uri uri);
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IInterceptor.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IInterceptor.java
new file mode 100644
index 00000000..15ee07e7
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IInterceptor.java
@@ -0,0 +1,22 @@
+package com.alibaba.android.arouter.facade.template;
+
+import com.alibaba.android.arouter.facade.Postcard;
+import com.alibaba.android.arouter.facade.callback.InterceptorCallback;
+
+/**
+ * Used for inject custom logic when navigation.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/23 13:56
+ */
+public interface IInterceptor extends IProvider {
+
+ /**
+ * The operation of this interceptor.
+ *
+ * @param postcard meta
+ * @param callback cb
+ */
+ void process(Postcard postcard, InterceptorCallback callback);
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IInterceptorGroup.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IInterceptorGroup.java
new file mode 100644
index 00000000..e426df14
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IInterceptorGroup.java
@@ -0,0 +1,19 @@
+package com.alibaba.android.arouter.facade.template;
+
+import java.util.Map;
+
+/**
+ * Template of interceptor group.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/29 09:51
+ */
+public interface IInterceptorGroup {
+ /**
+ * Load interceptor to input
+ *
+ * @param interceptor input
+ */
+ void loadInto(Map> interceptor);
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/ILogger.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/ILogger.java
new file mode 100644
index 00000000..4f148b76
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/ILogger.java
@@ -0,0 +1,35 @@
+package com.alibaba.android.arouter.facade.template;
+
+import com.alibaba.android.arouter.utils.Consts;
+
+/**
+ * Logger
+ *
+ * @author 正纬 Contact me.
+ * @version 1.0
+ * @since 16/5/16 下午5:39
+ */
+public interface ILogger {
+
+ boolean isShowLog = false;
+ boolean isShowStackTrace = false;
+ String defaultTag = Consts.TAG;
+
+ void showLog(boolean isShowLog);
+
+ void showStackTrace(boolean isShowStackTrace);
+
+ void debug(String tag, String message);
+
+ void info(String tag, String message);
+
+ void warning(String tag, String message);
+
+ void error(String tag, String message);
+
+ void monitor(String message);
+
+ boolean isMonitorMode();
+
+ String getDefaultTag();
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IPolicy.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IPolicy.java
new file mode 100644
index 00000000..5b3f7e7c
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IPolicy.java
@@ -0,0 +1,12 @@
+package com.alibaba.android.arouter.facade.template;
+
+/**
+ * Store policy.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/24 10:15
+ */
+public interface IPolicy {
+ int getFlag();
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IProvider.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IProvider.java
new file mode 100644
index 00000000..4271928e
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IProvider.java
@@ -0,0 +1,20 @@
+package com.alibaba.android.arouter.facade.template;
+
+import android.content.Context;
+
+/**
+ * Provider interface, base of other interface.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/23 23:08
+ */
+public interface IProvider {
+
+ /**
+ * Do your init work in this method, it well be call when processor has been load.
+ *
+ * @param context ctx
+ */
+ void init(Context context);
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IProviderGroup.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IProviderGroup.java
new file mode 100644
index 00000000..74b79728
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IProviderGroup.java
@@ -0,0 +1,21 @@
+package com.alibaba.android.arouter.facade.template;
+
+import com.alibaba.android.arouter.facade.model.RouteMeta;
+
+import java.util.Map;
+
+/**
+ * Template of provider group.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/08/30 12:42
+ */
+public interface IProviderGroup {
+ /**
+ * Load providers map to input
+ *
+ * @param providers input
+ */
+ void loadInto(Map providers);
+}
\ No newline at end of file
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IRouteGroup.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IRouteGroup.java
new file mode 100644
index 00000000..5d53acca
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IRouteGroup.java
@@ -0,0 +1,19 @@
+package com.alibaba.android.arouter.facade.template;
+
+import com.alibaba.android.arouter.facade.model.RouteMeta;
+
+import java.util.Map;
+
+/**
+ * Group element.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/23 16:37
+ */
+public interface IRouteGroup {
+ /**
+ * Fill the atlas with routes in group.
+ */
+ void loadInto(Map atlas);
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IRouteRoot.java b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IRouteRoot.java
new file mode 100644
index 00000000..26229fc4
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/facade/template/IRouteRoot.java
@@ -0,0 +1,19 @@
+package com.alibaba.android.arouter.facade.template;
+
+import java.util.Map;
+
+/**
+ * Root element.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/23 16:36
+ */
+public interface IRouteRoot {
+
+ /**
+ * Load routes to input
+ * @param routes input
+ */
+ void loadInto(Map> routes);
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/launcher/ARouter.java b/arouter-api/src/main/java/com/alibaba/android/arouter/launcher/ARouter.java
new file mode 100644
index 00000000..848db1c5
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/launcher/ARouter.java
@@ -0,0 +1,170 @@
+package com.alibaba.android.arouter.launcher;
+
+import android.app.Application;
+import android.content.Context;
+import android.net.Uri;
+
+import com.alibaba.android.arouter.exception.InitException;
+import com.alibaba.android.arouter.facade.Postcard;
+import com.alibaba.android.arouter.facade.callback.NavigationCallback;
+import com.alibaba.android.arouter.facade.template.ILogger;
+import com.alibaba.android.arouter.utils.Consts;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * ARouter facade
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/16 14:36
+ */
+public final class ARouter {
+ // Key of raw uri
+ public static final String RAW_URI = "NTeRQWvye18AkPd6G";
+ public static final String AUTO_INJECT = "wmHzgD4lOj5o4241";
+
+ private volatile static ARouter instance = null;
+ private volatile static boolean hasInit = false;
+ public static ILogger logger;
+
+ private ARouter() {
+ }
+
+ /**
+ * Init, it must be call before used router.
+ */
+ public static void init(Application application) {
+ if (!hasInit) {
+ logger = _ARouter.logger;
+ _ARouter.logger.info(Consts.TAG, "ARouter init start.");
+ hasInit = _ARouter.init(application);
+
+ if (hasInit) {
+ _ARouter.afterInit();
+ }
+
+ _ARouter.logger.info(Consts.TAG, "ARouter init over.");
+ }
+ }
+
+ /**
+ * Get instance of router. A
+ * All feature U use, will be starts here.
+ */
+ public static ARouter getInstance() {
+ if (!hasInit) {
+ throw new InitException("ARouter::Init::Invoke init(context) first!");
+ } else {
+ if (instance == null) {
+ synchronized (ARouter.class) {
+ if (instance == null) {
+ instance = new ARouter();
+ }
+ }
+ }
+ return instance;
+ }
+ }
+
+ public static synchronized void openDebug() {
+ _ARouter.openDebug();
+ }
+
+ public static boolean debuggable() {
+ return _ARouter.debuggable();
+ }
+
+ public static synchronized void openLog() {
+ _ARouter.openLog();
+ }
+
+ public static synchronized void printStackTrace() {
+ _ARouter.printStackTrace();
+ }
+
+ public static synchronized void setExecutor(ThreadPoolExecutor tpe) {
+ _ARouter.setExecutor(tpe);
+ }
+
+ public synchronized void destroy() {
+ _ARouter.destroy();
+ hasInit = false;
+ }
+
+ public static synchronized void enableAutoInject() {
+ _ARouter.enableAutoInject();
+ }
+
+ public static boolean canAutoInject() {
+ return _ARouter.canAutoInject();
+ }
+
+ public static void attachBaseContext() {
+ _ARouter.attachBaseContext();
+ }
+
+ public static synchronized void monitorMode() {
+ _ARouter.monitorMode();
+ }
+
+ public static boolean isMonitorMode() {
+ return _ARouter.isMonitorMode();
+ }
+
+ public static void setLogger(ILogger userLogger) {
+ _ARouter.setLogger(userLogger);
+ }
+
+ /**
+ * Build the roadmap, draw a postcard.
+ *
+ * @param path Where you go.
+ */
+ public Postcard build(String path) {
+ return _ARouter.getInstance().build(path);
+ }
+
+ /**
+ * Build the roadmap, draw a postcard.
+ *
+ * @param path Where you go.
+ * @param group The group of path.
+ */
+ @Deprecated
+ public Postcard build(String path, String group) {
+ return _ARouter.getInstance().build(path, group);
+ }
+
+ /**
+ * Build the roadmap, draw a postcard.
+ *
+ * @param url the path
+ */
+ public Postcard build(Uri url) {
+ return _ARouter.getInstance().build(url);
+ }
+
+ /**
+ * Launch the navigation by type
+ *
+ * @param service interface of service
+ * @param return type
+ * @return instance of service
+ */
+ public T navigation(Class extends T> service) {
+ return _ARouter.getInstance().navigation(service);
+ }
+
+ /**
+ * Launch the navigation.
+ *
+ * @param mContext .
+ * @param postcard .
+ * @param requestCode Set for startActivityForResult
+ * @param callback cb
+ */
+ public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
+ return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/launcher/_ARouter.java b/arouter-api/src/main/java/com/alibaba/android/arouter/launcher/_ARouter.java
new file mode 100644
index 00000000..e3cf3e21
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/launcher/_ARouter.java
@@ -0,0 +1,341 @@
+package com.alibaba.android.arouter.launcher;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.alibaba.android.arouter.core.InstrumentationHook;
+import com.alibaba.android.arouter.core.LogisticsCenter;
+import com.alibaba.android.arouter.exception.HandlerException;
+import com.alibaba.android.arouter.exception.InitException;
+import com.alibaba.android.arouter.exception.NoRouteFoundException;
+import com.alibaba.android.arouter.facade.Postcard;
+import com.alibaba.android.arouter.facade.callback.InterceptorCallback;
+import com.alibaba.android.arouter.facade.callback.NavigationCallback;
+import com.alibaba.android.arouter.facade.service.DegradeService;
+import com.alibaba.android.arouter.facade.service.PathReplaceService;
+import com.alibaba.android.arouter.facade.template.ILogger;
+import com.alibaba.android.arouter.thread.DefaultPoolExecutor;
+import com.alibaba.android.arouter.utils.Consts;
+import com.alibaba.android.arouter.utils.DefaultLogger;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * ARouter core
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/16 14:39
+ */
+final class _ARouter {
+ static ILogger logger = new DefaultLogger(Consts.TAG); // 日志工具
+ private volatile static boolean monitorMode = false;
+ private volatile static boolean debuggable = false;
+ private volatile static boolean autoInject = false;
+ private volatile static _ARouter instance = null;
+ private volatile static boolean hasInit = false;
+ private volatile static ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance();
+ private static Context mContext;
+
+ private _ARouter() {
+ }
+
+ protected static synchronized boolean init(Application application) {
+ mContext = application;
+ LogisticsCenter.init(mContext, executor);
+ logger.info(Consts.TAG, "ARouter init success!");
+ hasInit = true;
+ return true;
+ }
+
+ /**
+ * Destroy arouter, it can be used only in debug mode.
+ */
+ static synchronized void destroy() {
+ if (debuggable()) {
+ hasInit = false;
+ LogisticsCenter.suspend();
+ logger.info(Consts.TAG, "ARouter destroy success!");
+ } else {
+ throw new HandlerException("ARouter::destroy can be used in debug mode only!");
+ }
+ }
+
+ protected static _ARouter getInstance() {
+ if (!hasInit) {
+ throw new InitException("ARouterCore::Init::Invoke init(context) first!");
+ } else {
+ if (instance == null) {
+ synchronized (_ARouter.class) {
+ if (instance == null) {
+ instance = new _ARouter();
+ }
+ }
+ }
+ return instance;
+ }
+ }
+
+ static synchronized void openDebug() {
+ debuggable = true;
+ logger.info(Consts.TAG, "ARouter openDebug");
+ }
+
+ static synchronized void openLog() {
+ logger.showLog(true);
+ logger.info(Consts.TAG, "ARouter openLog");
+ }
+
+ static synchronized void enableAutoInject() {
+ autoInject = true;
+ }
+
+ static boolean canAutoInject() {
+ return autoInject;
+ }
+
+ static void attachBaseContext() {
+ Log.i(Consts.TAG, "ARouter start attachBaseContext");
+ try {
+ Class> mMainThreadClass = Class.forName("android.app.ActivityThread");
+
+ // Get current main thread.
+ Method getMainThread = mMainThreadClass.getDeclaredMethod("currentActivityThread");
+ getMainThread.setAccessible(true);
+ Object currentActivityThread = getMainThread.invoke(null);
+
+ // The field contain instrumentation.
+ Field mInstrumentationField = mMainThreadClass.getDeclaredField("mInstrumentation");
+ mInstrumentationField.setAccessible(true);
+
+ // Hook current instrumentation
+ mInstrumentationField.set(currentActivityThread, new InstrumentationHook());
+ Log.i(Consts.TAG, "ARouter hook instrumentation success!");
+ } catch (Exception ex) {
+ Log.e(Consts.TAG, "ARouter hook instrumentation failed! [" + ex.getMessage() + "]");
+ }
+ }
+
+ static synchronized void printStackTrace() {
+ logger.showStackTrace(true);
+ logger.info(Consts.TAG, "ARouter printStackTrace");
+ }
+
+ static synchronized void setExecutor(ThreadPoolExecutor tpe) {
+ executor = tpe;
+ }
+
+ static synchronized void monitorMode() {
+ monitorMode = true;
+ logger.info(Consts.TAG, "ARouter monitorMode on");
+ }
+
+ static boolean isMonitorMode() {
+ return monitorMode;
+ }
+
+ static boolean debuggable() {
+ return debuggable;
+ }
+
+ static void setLogger(ILogger userLogger) {
+ if (null != userLogger) {
+ logger = userLogger;
+ }
+ }
+
+ /**
+ * Build postcard by path and default group
+ */
+ protected Postcard build(String path) {
+ if (StringUtils.isEmpty(path)) {
+ throw new HandlerException(Consts.TAG + "Parameter is invalid!");
+ } else {
+ PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
+ if (null != pService) {
+ path = pService.forString(path);
+ }
+ return build(path, extractGroup(path));
+ }
+ }
+
+ /**
+ * Build postcard by uri
+ */
+ protected Postcard build(Uri uri) {
+ if (null == uri || StringUtils.isEmpty(uri.toString())) {
+ throw new HandlerException(Consts.TAG + "Parameter invalid!");
+ } else {
+ PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
+ if (null != pService) {
+ uri = pService.forUri(uri);
+ }
+ return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null);
+ }
+ }
+
+ /**
+ * Build postcard by path and group
+ */
+ protected Postcard build(String path, String group) {
+ if (StringUtils.isEmpty(path) || StringUtils.isEmpty(group)) {
+ throw new HandlerException(Consts.TAG + "Parameter is invalid!");
+ } else {
+ PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
+ if (null != pService) {
+ path = pService.forString(path);
+ }
+ return new Postcard(path, group);
+ }
+ }
+
+ /**
+ * Extract the default group from path.
+ */
+ private String extractGroup(String path) {
+ if (StringUtils.isEmpty(path) || !path.startsWith("/")) {
+ throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
+ }
+
+ try {
+ String defaultGroup = path.substring(1, path.indexOf("/", 1));
+ if (StringUtils.isEmpty(defaultGroup)) {
+ throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
+ } else {
+ return defaultGroup;
+ }
+ } catch (Exception e) {
+ logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
+ return null;
+ }
+ }
+
+ static void afterInit() {
+ LogisticsCenter.initInterceptors();
+ }
+
+ protected T navigation(Class extends T> service) {
+ try {
+ Postcard postcard = LogisticsCenter.buildProvider(service.getSimpleName());
+ LogisticsCenter.completion(postcard);
+ return (T) postcard.getProvider();
+ } catch (NoRouteFoundException ex) {
+ logger.warning(Consts.TAG, ex.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Use router navigation.
+ *
+ * @param context Activity or null.
+ * @param postcard Route metas
+ * @param requestCode RequestCode
+ * @param callback cb
+ */
+ protected Object navigation(final Context context, final Postcard postcard, final int requestCode, NavigationCallback callback) {
+ try {
+ LogisticsCenter.completion(postcard);
+ } catch (NoRouteFoundException ex) {
+ logger.warning(Consts.TAG, ex.getMessage());
+
+ if (debuggable()) { // Show friendly tips for user.
+ Toast.makeText(mContext, "There's no route matched!\n" +
+ " Path = [" + postcard.getPath() + "]\n" +
+ " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
+ }
+
+ if (null != callback) {
+ callback.onLost(postcard);
+ } else { // No callback for this invoke, then we use the global degrade service.
+ DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
+ if (null != degradeService) {
+ degradeService.onLost(context, postcard);
+ }
+ }
+
+ return null;
+ }
+
+ if (null != callback) {
+ callback.onFound(postcard);
+ }
+
+ if (!postcard.isGreenChannal()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
+ LogisticsCenter.interceptions(postcard, new InterceptorCallback() {
+ /**
+ * Continue process
+ *
+ * @param postcard route meta
+ */
+ @Override
+ public void onContinue(Postcard postcard) {
+ _navigation(context, postcard, requestCode);
+ }
+
+ /**
+ * Interrupt process, pipeline will be destory when this method called.
+ *
+ * @param exception Reson of interrupt.
+ */
+ @Override
+ public void onInterrupt(Throwable exception) {
+ logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
+ }
+ });
+ } else {
+ return _navigation(context, postcard, requestCode);
+ }
+
+ return null;
+ }
+
+ private Object _navigation(final Context context, final Postcard postcard, final int requestCode) {
+ Context currentContext = null == context ? mContext : context;
+
+ switch (postcard.getType()) {
+ case ACTIVITY:
+
+ // Build intent
+ Intent intent = new Intent(currentContext, postcard.getDestination());
+ intent.putExtras(postcard.getExtras());
+
+ // Set flags.
+ int flags = postcard.getFlags();
+ if (-1 != flags) {
+ intent.setFlags(flags);
+ } else {
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ // Judgment activity start type.
+ if (requestCode > 0) { // RequestCode exist, tell us user's want startActivityForResult, so this context must son of activity.
+ ((Activity) currentContext).startActivityForResult(intent, requestCode);
+ } else {
+ currentContext.startActivity(intent);
+ }
+
+ break;
+ case PROVIDER:
+ return postcard.getProvider();
+ case BOARDCAST:
+ case CONTENT_PROVIDER:
+ case FRAGMENT:
+ case METHOD:
+ case SERVICE:
+ default:
+ return null;
+ }
+
+ return null;
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/thread/CancelableCountDownLatch.java b/arouter-api/src/main/java/com/alibaba/android/arouter/thread/CancelableCountDownLatch.java
new file mode 100644
index 00000000..cdda0d86
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/thread/CancelableCountDownLatch.java
@@ -0,0 +1,29 @@
+package com.alibaba.android.arouter.thread;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * As its name.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/29 15:48
+ */
+public class CancelableCountDownLatch extends CountDownLatch {
+ /**
+ * Constructs a {@code CountDownLatch} initialized with the given count.
+ *
+ * @param count the number of times {@link #countDown} must be invoked
+ * before threads can pass through {@link #await}
+ * @throws IllegalArgumentException if {@code count} is negative
+ */
+ public CancelableCountDownLatch(int count) {
+ super(count);
+ }
+
+ public void cancel() {
+ while (getCount() > 0) {
+ countDown();
+ }
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/thread/DefaultPoolExecutor.java b/arouter-api/src/main/java/com/alibaba/android/arouter/thread/DefaultPoolExecutor.java
new file mode 100644
index 00000000..5ae1d272
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/thread/DefaultPoolExecutor.java
@@ -0,0 +1,77 @@
+package com.alibaba.android.arouter.thread;
+
+import com.alibaba.android.arouter.launcher.ARouter;
+import com.alibaba.android.arouter.utils.Consts;
+import com.alibaba.android.arouter.utils.TextUtils;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Executors
+ *
+ * @author 正纬 Contact me.
+ * @version 1.0
+ * @since 16/4/28 下午4:07
+ */
+public class DefaultPoolExecutor extends ThreadPoolExecutor {
+ // Thread args
+ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+ private static final int INIT_THREAD_COUNT = CPU_COUNT + 1;
+ private static final int MAX_THREAD_COUNT = 20;
+ private static final long SURPLUS_THREAD_LIFE = 30L;
+
+ private static DefaultPoolExecutor instance;
+
+ public static DefaultPoolExecutor getInstance() {
+ if (null == instance) {
+ synchronized (DefaultPoolExecutor.class) {
+ if (null == instance) {
+ instance = new DefaultPoolExecutor(
+ INIT_THREAD_COUNT,
+ MAX_THREAD_COUNT,
+ SURPLUS_THREAD_LIFE,
+ TimeUnit.SECONDS,
+ new SynchronousQueue(),
+ new DefaultThreadFactory());
+ }
+ }
+ }
+ return instance;
+ }
+
+ private DefaultPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
+ }
+
+ /*
+ * 线程执行结束,顺便看一下有么有什么乱七八糟的异常
+ *
+ * @param r the runnable that has completed
+ * @param t the exception that caused termination, or null if
+ */
+ @Override
+ protected void afterExecute(Runnable r, Throwable t) {
+ super.afterExecute(r, t);
+ if (t == null && r instanceof Future>) {
+ try {
+ ((Future>) r).get();
+ } catch (CancellationException ce) {
+ t = ce;
+ } catch (ExecutionException ee) {
+ t = ee.getCause();
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt(); // ignore/reset
+ }
+ }
+ if (t != null) {
+ ARouter.logger.warning(Consts.TAG, "Running task appeared exception! Thread [" + Thread.currentThread().getName() + "], because [" + t.getMessage() + "]\n" + TextUtils.formatStackTrace(t.getStackTrace()));
+ }
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/thread/DefaultThreadFactory.java b/arouter-api/src/main/java/com/alibaba/android/arouter/thread/DefaultThreadFactory.java
new file mode 100644
index 00000000..9cf0758d
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/thread/DefaultThreadFactory.java
@@ -0,0 +1,52 @@
+package com.alibaba.android.arouter.thread;
+
+import android.support.annotation.NonNull;
+
+import com.alibaba.android.arouter.launcher.ARouter;
+import com.alibaba.android.arouter.utils.Consts;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 线程池工厂类
+ *
+ * @author zhilong Contact me.
+ * @version 1.0
+ * @since 15/12/25 上午10:51
+ */
+public class DefaultThreadFactory implements ThreadFactory {
+ private static final AtomicInteger poolNumber = new AtomicInteger(1);
+
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+ private final ThreadGroup group;
+ private final String namePrefix;
+
+ public DefaultThreadFactory() {
+ SecurityManager s = System.getSecurityManager();
+ group = (s != null) ? s.getThreadGroup() :
+ Thread.currentThread().getThreadGroup();
+ namePrefix = "ARouter task pool No." + poolNumber.getAndIncrement() + ", thread No.";
+ }
+
+ public Thread newThread(@NonNull Runnable runnable) {
+ String threadName = namePrefix + threadNumber.getAndIncrement();
+ ARouter.logger.info(Consts.TAG, "Thread production, name is [" + threadName + "]");
+ Thread thread = new Thread(group, runnable, threadName, 0);
+ if (thread.isDaemon()) { //设为非后台线程
+ thread.setDaemon(false);
+ }
+ if (thread.getPriority() != Thread.NORM_PRIORITY) { //优先级为normal
+ thread.setPriority(Thread.NORM_PRIORITY);
+ }
+
+ // 捕获多线程处理中的异常
+ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+ ARouter.logger.info(Consts.TAG, "Running task appeared exception! Thread [" + thread.getName() + "], because [" + ex.getMessage() + "]");
+ }
+ });
+ return thread;
+ }
+}
\ No newline at end of file
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/utils/ClassUtils.java b/arouter-api/src/main/java/com/alibaba/android/arouter/utils/ClassUtils.java
new file mode 100644
index 00000000..84b47e22
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/utils/ClassUtils.java
@@ -0,0 +1,169 @@
+package com.alibaba.android.arouter.utils;
+
+// Copy from galaxy sdk ${com.alibaba.android.galaxy.utils.ClassUtils}
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import dalvik.system.DexFile;
+
+/**
+ * Scanner, find out class with any conditions, copy from google source code.
+ *
+ * @author 正纬 Contact me.
+ * @version 1.0
+ * @since 16/6/27 下午10:58
+ */
+public class ClassUtils {
+ private static final String EXTRACTED_NAME_EXT = ".classes";
+ private static final String EXTRACTED_SUFFIX = ".zip";
+
+ private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes";
+
+ private static final String PREFS_FILE = "multidex.version";
+ private static final String KEY_DEX_NUMBER = "dex.number";
+
+ private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2;
+ private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1;
+
+ private static SharedPreferences getMultiDexPreferences(Context context) {
+ return context.getSharedPreferences(PREFS_FILE, Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? Context.MODE_PRIVATE : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
+ }
+
+ /**
+ * 通过指定包名,扫描包下面包含的所有的ClassName
+ *
+ * @param context U know
+ * @param packageName 包名
+ * @return 所有class的集合
+ */
+ public static List getFileNameByPackageName(Context context, String packageName) throws PackageManager.NameNotFoundException, IOException {
+ List classNames = new ArrayList<>();
+ for (String path : getSourcePaths(context)) {
+ DexFile dexfile;
+ 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 dexEntries = dexfile.entries();
+ while (dexEntries.hasMoreElements()) {
+ String className = dexEntries.nextElement();
+ if (className.contains(packageName)) {
+ classNames.add(className);
+ }
+ }
+ }
+
+ Log.d("galaxy", "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
+ return classNames;
+ }
+
+ /**
+ * get all the dex path
+ *
+ * @param context the application context
+ * @return all the dex path
+ * @throws PackageManager.NameNotFoundException
+ * @throws IOException
+ */
+ public static List getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
+ ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
+ File sourceApk = new File(applicationInfo.sourceDir);
+
+ List sourcePaths = new ArrayList<>();
+ sourcePaths.add(applicationInfo.sourceDir); //add the default apk path
+
+ //the prefix of extracted file, ie: test.classes
+ String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
+
+// 如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
+// 通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
+ if (!isVMMultidexCapable()) {
+ //the total dex numbers
+ int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
+ File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
+
+ for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
+ //for each dex file, ie: test.classes2.zip, test.classes3.zip...
+ String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
+ File extractedFile = new File(dexDir, fileName);
+ if (extractedFile.isFile()) {
+ sourcePaths.add(extractedFile.getAbsolutePath());
+ //we ignore the verify zip part
+ } else {
+ throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
+ }
+ }
+ }
+
+ return sourcePaths;
+ }
+
+ /**
+ * Identifies if the current VM has a native support for multidex, meaning there is no need for
+ * additional installation by this library.
+ *
+ * @return true if the VM handles multidex
+ */
+ private static boolean isVMMultidexCapable() {
+ boolean isMultidexCapable = false;
+ String vmName = null;
+
+ try {
+ if (isYunOS()) { // YunOS需要特殊判断
+ vmName = "'YunOS'";
+ isMultidexCapable = Integer.valueOf(System.getProperty("ro.build.version.sdk")) >= 21;
+ } else { // 非YunOS原生Android
+ vmName = "'Android'";
+ String versionString = System.getProperty("java.vm.version");
+ if (versionString != null) {
+ Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
+ if (matcher.matches()) {
+ try {
+ int major = Integer.parseInt(matcher.group(1));
+ int minor = Integer.parseInt(matcher.group(2));
+ isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
+ || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
+ && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
+ } catch (NumberFormatException ignore) {
+ // let isMultidexCapable be false
+ }
+ }
+ }
+ }
+ } catch (Exception ignore) {
+
+ }
+
+ Log.i("galaxy", "VM with name " + vmName + (isMultidexCapable ? " has multidex support" : " does not have multidex support"));
+ return isMultidexCapable;
+ }
+
+ /**
+ * 判断系统是否为YunOS系统
+ */
+ private static boolean isYunOS() {
+ try {
+ String version = System.getProperty("ro.yunos.version");
+ String vmName = System.getProperty("java.vm.name");
+ return (vmName != null && vmName.toLowerCase().contains("lemur"))
+ || (version != null && version.trim().length() > 0);
+ } catch (Exception ignore) {
+ return false;
+ }
+ }
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/utils/Consts.java b/arouter-api/src/main/java/com/alibaba/android/arouter/utils/Consts.java
new file mode 100644
index 00000000..b9b996ad
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/utils/Consts.java
@@ -0,0 +1,29 @@
+package com.alibaba.android.arouter.utils;
+
+/**
+ * ARouter constants.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/23 9:38
+ */
+public final class Consts {
+ public static final String SDK_NAME = "ARouter";
+ public static final String TAG = SDK_NAME + "::";
+ public static final String SEPARATOR = "$$";
+ public static final String SUFFIX_ROOT = "Root";
+ public static final String SUFFIX_INTERCEPTORS = "Interceptors";
+ public static final String SUFFIX_PROVIDERS = "Providers";
+ public static final String DOT = ".";
+ public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
+
+ // Java base type, copy from javax 'TypeKind'
+ public static final int DEF_BOOLEAN = 0;
+ public static final int DEF_BYTE = 1;
+ public static final int DEF_SHORT = 2;
+ public static final int DEF_INT = 3;
+ public static final int DEF_LONG = 4;
+ public static final int DEF_FLOAT = 6;
+ public static final int DEF_DOUBLE = 7;
+ public static final int DEF_STRING = 18;
+}
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/utils/DefaultLogger.java b/arouter-api/src/main/java/com/alibaba/android/arouter/utils/DefaultLogger.java
new file mode 100644
index 00000000..ac836966
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/utils/DefaultLogger.java
@@ -0,0 +1,116 @@
+package com.alibaba.android.arouter.utils;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.alibaba.android.arouter.facade.template.ILogger;
+
+/**
+ * Default logger
+ *
+ * @author zhilong Contact me.
+ * @version 1.0
+ * @since 2015-12-08 21:44:10
+ */
+public class DefaultLogger implements ILogger {
+
+ private static boolean isShowLog = false;
+ private static boolean isShowStackTrace = false;
+ private static boolean isMonitorMode = false;
+
+ private String defaultTag = "ARouter";
+
+ public void showLog(boolean showLog) {
+ isShowLog = showLog;
+ }
+
+ public void showStackTrace(boolean showStackTrace) {
+ isShowStackTrace = showStackTrace;
+ }
+
+ public void showMonitor(boolean showMonitor) {
+ isMonitorMode = showMonitor;
+ }
+
+ public DefaultLogger() {
+ }
+
+ public DefaultLogger(String defaultTag) {
+ this.defaultTag = defaultTag;
+ }
+
+ @Override
+ public void debug(String tag, String message) {
+ if (isShowLog) {
+ StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
+ Log.d(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));
+ }
+ }
+
+ @Override
+ public void info(String tag, String message) {
+ if (isShowLog) {
+ StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
+ Log.i(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));
+ }
+ }
+
+ @Override
+ public void warning(String tag, String message) {
+ if (isShowLog) {
+ StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
+ Log.w(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));
+ }
+ }
+
+ @Override
+ public void error(String tag, String message) {
+ if (isShowLog) {
+ StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
+ Log.e(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));
+ }
+ }
+
+ @Override
+ public void monitor(String message) {
+ if (isShowLog && isMonitorMode()) {
+ StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
+ Log.d(defaultTag + "::monitor", message + getExtInfo(stackTraceElement));
+ }
+ }
+
+ @Override
+ public boolean isMonitorMode() {
+ return isMonitorMode;
+ }
+
+ @Override
+ public String getDefaultTag() {
+ return defaultTag;
+ }
+
+ public static String getExtInfo(StackTraceElement stackTraceElement) {
+
+ String separator = " & ";
+ StringBuilder sb = new StringBuilder("[");
+
+ if (isShowStackTrace) {
+ String threadName = Thread.currentThread().getName();
+ String fileName = stackTraceElement.getFileName();
+ String className = stackTraceElement.getClassName();
+ String methodName = stackTraceElement.getMethodName();
+ long threadID = Thread.currentThread().getId();
+ int lineNumber = stackTraceElement.getLineNumber();
+
+ sb.append("ThreadId=").append(threadID).append(separator);
+ sb.append("ThreadName=").append(threadName).append(separator);
+ sb.append("FileName=").append(fileName).append(separator);
+ sb.append("ClassName=").append(className).append(separator);
+ sb.append("MethodName=").append(methodName).append(separator);
+ sb.append("LineNumber=").append(lineNumber);
+ }
+
+ sb.append(" ] ");
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/arouter-api/src/main/java/com/alibaba/android/arouter/utils/TextUtils.java b/arouter-api/src/main/java/com/alibaba/android/arouter/utils/TextUtils.java
new file mode 100644
index 00000000..197f9962
--- /dev/null
+++ b/arouter-api/src/main/java/com/alibaba/android/arouter/utils/TextUtils.java
@@ -0,0 +1,66 @@
+package com.alibaba.android.arouter.utils;
+
+import android.net.Uri;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Text utils
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/9/9 14:40
+ */
+public class TextUtils {
+
+ /**
+ * Print thread stack
+ */
+ public static String formatStackTrace(StackTraceElement[] stackTrace) {
+ StringBuilder sb = new StringBuilder();
+ for (StackTraceElement element : stackTrace) {
+ sb.append(" at ").append(element.toString());
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Split query parameters
+ * @param rawUri raw uri
+ * @return map with params
+ */
+ public static Map splitQueryParameters(Uri rawUri) {
+ String query = rawUri.getEncodedQuery();
+
+ if (query == null) {
+ return Collections.emptyMap();
+ }
+
+ Map paramMap = new LinkedHashMap<>();
+ int start = 0;
+ do {
+ int next = query.indexOf('&', start);
+ int end = (next == -1) ? query.length() : next;
+
+ int separator = query.indexOf('=', start);
+ if (separator > end || separator == -1) {
+ separator = end;
+ }
+
+ String name = query.substring(start, separator);
+
+ if (!android.text.TextUtils.isEmpty(name)) {
+ String value = (separator == end ? "" : query.substring(separator + 1, end));
+ paramMap.put(Uri.decode(name), Uri.decode(value));
+ }
+
+ // Move start to end of name.
+ start = end + 1;
+ } while (start < query.length());
+
+ return Collections.unmodifiableMap(paramMap);
+ }
+}
diff --git a/arouter-api/src/main/res/drawable/ic_launcher.png b/arouter-api/src/main/res/drawable/ic_launcher.png
new file mode 100644
index 00000000..70a61d26
Binary files /dev/null and b/arouter-api/src/main/res/drawable/ic_launcher.png differ
diff --git a/arouter-compiler/build.gradle b/arouter-compiler/build.gradle
new file mode 100644
index 00000000..a82ab5ba
--- /dev/null
+++ b/arouter-compiler/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'java'
+
+ext {
+ bintrayName = 'arouter-compiler'
+ artifact = bintrayName
+ libraryName = 'ARouter compiler'
+ libraryDescription = 'A compiler for ARouter to find route'
+ libraryVersion = '1.0.1'
+}
+
+dependencies {
+ compile 'com.alibaba:arouter-annotation:1.0.0'
+
+ compile 'com.google.auto.service:auto-service:1.0-rc2'
+ compile 'com.squareup:javapoet:1.7.0'
+
+ compile 'org.apache.commons:commons-lang3:3.4'
+ compile 'org.apache.commons:commons-collections4:4.1'
+}
+
+apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
+apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
\ No newline at end of file
diff --git a/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/processor/InterceptorProcessor.java b/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/processor/InterceptorProcessor.java
new file mode 100644
index 00000000..50fcce20
--- /dev/null
+++ b/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/processor/InterceptorProcessor.java
@@ -0,0 +1,244 @@
+package com.alibaba.android.arouter.compiler.processor;
+
+import com.alibaba.android.arouter.compiler.utils.Consts;
+import com.alibaba.android.arouter.compiler.utils.Logger;
+import com.alibaba.android.arouter.facade.annotation.Interceptor;
+import com.google.auto.service.AutoService;
+import com.google.common.collect.Sets;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.WildcardTypeName;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+
+import static com.alibaba.android.arouter.compiler.utils.Consts.IINTERCEPTOR;
+import static com.alibaba.android.arouter.compiler.utils.Consts.IINTERCEPTOR_GROUP;
+import static com.alibaba.android.arouter.compiler.utils.Consts.KEY_MODULE_NAME;
+import static com.alibaba.android.arouter.compiler.utils.Consts.METHOD_LOAD_INTO;
+import static com.alibaba.android.arouter.compiler.utils.Consts.NAME_OF_INTERCEPTOR;
+import static com.alibaba.android.arouter.compiler.utils.Consts.PACKAGE_OF_GENERATE_FILE;
+import static com.alibaba.android.arouter.compiler.utils.Consts.SEPARATOR;
+import static com.alibaba.android.arouter.compiler.utils.Consts.WARNING_TIPS;
+import static javax.lang.model.element.Modifier.PUBLIC;
+
+/**
+ * Process the annotation of #{@link Interceptor}
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/23 14:11
+ */
+@AutoService(Processor.class)
+public class InterceptorProcessor extends AbstractProcessor {
+ private Map interceptors = new TreeMap<>();
+ private Filer mFiler; // File util, write class file into disk.
+ private Logger logger;
+ private Elements elementUtil;
+ private String moduleName = null; // Module name, maybe its 'app' or others
+ private TypeMirror iInterceptor = null;
+
+ /**
+ * Initializes the processor with the processing environment by
+ * setting the {@code processingEnv} field to the value of the
+ * {@code processingEnv} argument. An {@code
+ * IllegalStateException} will be thrown if this method is called
+ * more than once on the same object.
+ *
+ * @param processingEnv environment to access facilities the tool framework
+ * provides to the processor
+ * @throws IllegalStateException if this method is called more than once.
+ */
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+
+ mFiler = processingEnv.getFiler(); // Generate class.
+ elementUtil = processingEnv.getElementUtils(); // Get class meta.
+ logger = new Logger(processingEnv.getMessager()); // Package the log utils.
+
+ // Attempt to get user configuration [moduleName]
+ Map options = processingEnv.getOptions();
+ if (MapUtils.isNotEmpty(options)) {
+ moduleName = options.get(KEY_MODULE_NAME);
+ }
+
+ if (StringUtils.isNotEmpty(moduleName)) {
+ moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
+ logger.info("The user has configuration the module name, it was [" + moduleName + "]");
+ } else {
+ logger.error("These no module name, at 'build.gradle', like :\n" +
+ "apt {\n" +
+ " arguments {\n" +
+ " moduleName project.getName();\n" +
+ " }\n" +
+ "}\n");
+ throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");
+ }
+
+ iInterceptor = elementUtil.getTypeElement(Consts.IINTERCEPTOR).asType();
+
+ logger.info(">>> InterceptorProcessor init. <<<");
+ }
+
+ /**
+ * If the processor class is annotated with {@link
+ * SupportedAnnotationTypes}, return an unmodifiable set with the
+ * same set of strings as the annotation. If the class is not so
+ * annotated, an empty set is returned.
+ *
+ * @return the names of the annotation types supported by this
+ * processor, or an empty set if none
+ */
+ @Override
+ public Set getSupportedAnnotationTypes() {
+ return Collections.singleton(Interceptor.class.getCanonicalName());
+ }
+
+ /**
+ * If the processor class is annotated with {@link
+ * SupportedSourceVersion}, return the source version in the
+ * annotation. If the class is not so annotated, {@link
+ * SourceVersion#RELEASE_6} is returned.
+ *
+ * @return the latest source version supported by this processor
+ */
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return super.getSupportedSourceVersion();
+ }
+
+ @Override
+ public Set getSupportedOptions() {
+ return Sets.newHashSet(KEY_MODULE_NAME);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param annotations
+ * @param roundEnv
+ */
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ Set extends Element> elements = roundEnv.getElementsAnnotatedWith(Interceptor.class);
+ boolean parseResult = false;
+ try {
+ parseInterceptors(elements);
+ parseResult = true;
+ } catch (Exception e) {
+ logger.error(e);
+ }
+
+ return parseResult;
+ }
+
+ /**
+ * Parse tollgate.
+ *
+ * @param elements elements of tollgate.
+ */
+ private void parseInterceptors(Set extends Element> elements) throws IOException {
+ if (CollectionUtils.isNotEmpty(elements)) {
+ logger.info(">>> Found interceptors, size is " + elements.size() + " <<<");
+
+ // Verify and cache, sort incidentally.
+ for (Element element : elements) {
+ if (verify(element)) { // Check the interceptor meta
+ logger.info("A interceptor verify over, its " + element.asType());
+ Interceptor interceptor = element.getAnnotation(Interceptor.class);
+ interceptors.put(interceptor.priority(), element);
+
+// if (StringUtils.isEmpty(moduleName)) { // Hasn't generate the module name.
+// moduleName = ModuleUtils.generateModuleName(element, logger);
+// }
+ } else {
+ logger.error("A interceptor verify failed, its " + element.asType());
+ }
+ }
+
+ // Interface of ARouter.
+ TypeElement type_ITollgate = elementUtil.getTypeElement(IINTERCEPTOR);
+ TypeElement type_ITollgateGroup = elementUtil.getTypeElement(IINTERCEPTOR_GROUP);
+
+ /**
+ * Build input type, format as :
+ *
+ * ```Map>```
+ */
+ ParameterizedTypeName inputMapTypeOfTollgate = ParameterizedTypeName.get(
+ ClassName.get(Map.class),
+ ClassName.get(Integer.class),
+ ParameterizedTypeName.get(
+ ClassName.get(Class.class),
+ WildcardTypeName.subtypeOf(ClassName.get(type_ITollgate))
+ )
+ );
+
+ // Build input param name.
+ ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();
+
+ // Build method : 'loadInto'
+ MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
+ .addAnnotation(Override.class)
+ .addModifiers(PUBLIC)
+ .addParameter(tollgateParamSpec);
+
+ // Generate
+ if (null != interceptors && interceptors.size() > 0) {
+ // Build method body
+ for (Map.Entry entry : interceptors.entrySet()) {
+ loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));
+ }
+ }
+
+ // Write to disk(Write file even interceptors is empty.)
+ JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
+ TypeSpec.classBuilder(NAME_OF_INTERCEPTOR + SEPARATOR + moduleName)
+ .addModifiers(PUBLIC)
+ .addJavadoc(WARNING_TIPS)
+ .addMethod(loadIntoMethodOfTollgateBuilder.build())
+ .addSuperinterface(ClassName.get(type_ITollgateGroup))
+ .build()
+ ).build().writeTo(mFiler);
+
+ logger.info(">>> Interceptor group write over. <<<");
+ }
+ }
+
+ /**
+ * Verify tollgate meta
+ *
+ * @param element Interceptor taw type
+ * @return verify result
+ */
+ private boolean verify(Element element) {
+ Interceptor interceptor = element.getAnnotation(Interceptor.class);
+ // It must be implement the interface IInterceptor and marked with annotation Interceptor.
+ return null != interceptor && ((TypeElement) element).getInterfaces().contains(iInterceptor);
+ }
+}
diff --git a/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/processor/RouteProcessor.java b/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/processor/RouteProcessor.java
new file mode 100644
index 00000000..5f979c39
--- /dev/null
+++ b/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/processor/RouteProcessor.java
@@ -0,0 +1,480 @@
+package com.alibaba.android.arouter.compiler.processor;
+
+import com.alibaba.android.arouter.compiler.utils.Logger;
+import com.alibaba.android.arouter.facade.annotation.Param;
+import com.alibaba.android.arouter.facade.annotation.Route;
+import com.alibaba.android.arouter.facade.enums.RouteType;
+import com.alibaba.android.arouter.facade.model.RouteMeta;
+import com.google.auto.service.AutoService;
+import com.google.common.collect.Sets;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.WildcardTypeName;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+import static com.alibaba.android.arouter.compiler.utils.Consts.ACTIVITY;
+import static com.alibaba.android.arouter.compiler.utils.Consts.BOOLEAN;
+import static com.alibaba.android.arouter.compiler.utils.Consts.BYTE;
+import static com.alibaba.android.arouter.compiler.utils.Consts.DOUBEL;
+import static com.alibaba.android.arouter.compiler.utils.Consts.FLOAT;
+import static com.alibaba.android.arouter.compiler.utils.Consts.INTEGER;
+import static com.alibaba.android.arouter.compiler.utils.Consts.IPROVIDER;
+import static com.alibaba.android.arouter.compiler.utils.Consts.IPROVIDER_GROUP;
+import static com.alibaba.android.arouter.compiler.utils.Consts.IROUTE_GROUP;
+import static com.alibaba.android.arouter.compiler.utils.Consts.ITROUTE_ROOT;
+import static com.alibaba.android.arouter.compiler.utils.Consts.KEY_MODULE_NAME;
+import static com.alibaba.android.arouter.compiler.utils.Consts.LONG;
+import static com.alibaba.android.arouter.compiler.utils.Consts.METHOD_LOAD_INTO;
+import static com.alibaba.android.arouter.compiler.utils.Consts.NAME_OF_GROUP;
+import static com.alibaba.android.arouter.compiler.utils.Consts.NAME_OF_PROVIDER;
+import static com.alibaba.android.arouter.compiler.utils.Consts.NAME_OF_ROOT;
+import static com.alibaba.android.arouter.compiler.utils.Consts.PACKAGE_OF_GENERATE_FILE;
+import static com.alibaba.android.arouter.compiler.utils.Consts.SEPARATOR;
+import static com.alibaba.android.arouter.compiler.utils.Consts.SERVICE;
+import static com.alibaba.android.arouter.compiler.utils.Consts.SHORT;
+import static com.alibaba.android.arouter.compiler.utils.Consts.STRING;
+import static com.alibaba.android.arouter.compiler.utils.Consts.WARNING_TIPS;
+import static javax.lang.model.element.Modifier.PUBLIC;
+
+/**
+ * A processor used for find route.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/15 下午10:08
+ */
+@AutoService(Processor.class)
+public class RouteProcessor extends AbstractProcessor {
+ private Map> groupMap = new HashMap<>(); // ModuleName and routeMeta.
+ private Map rootMap = new TreeMap<>(); // Map of root metas, used for generate class file in order.
+ private Filer mFiler; // File util, write class file into disk.
+ private Logger logger;
+ private Types typeUtil;
+ private Elements elementUtil;
+ private String moduleName = null; // Module name, maybe its 'app' or others
+
+ /**
+ * Initializes the processor with the processing environment by
+ * setting the {@code processingEnv} field to the value of the
+ * {@code processingEnv} argument. An {@code
+ * IllegalStateException} will be thrown if this method is called
+ * more than once on the same object.
+ *
+ * @param processingEnv environment to access facilities the tool framework
+ * provides to the processor
+ * @throws IllegalStateException if this method is called more than once.
+ */
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+
+ mFiler = processingEnv.getFiler(); // Generate class.
+ typeUtil = processingEnv.getTypeUtils(); // Get type utils.
+ elementUtil = processingEnv.getElementUtils(); // Get class meta.
+ logger = new Logger(processingEnv.getMessager()); // Package the log utils.
+
+ // Attempt to get user configuration [moduleName]
+ Map options = processingEnv.getOptions();
+ if (MapUtils.isNotEmpty(options)) {
+ moduleName = options.get(KEY_MODULE_NAME);
+ }
+
+ if (StringUtils.isNotEmpty(moduleName)) {
+ moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
+
+ logger.info("The user has configuration the module name, it was [" + moduleName + "]");
+ } else {
+ logger.error("These no module name, at 'build.gradle', like :\n" +
+ "apt {\n" +
+ " arguments {\n" +
+ " moduleName project.getName();\n" +
+ " }\n" +
+ "}\n");
+ throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");
+ }
+
+ logger.info(">>> RouteProcessor init. <<<");
+ }
+
+ /**
+ * If the processor class is annotated with {@link
+ * SupportedAnnotationTypes}, return an unmodifiable set with the
+ * same set of strings as the annotation. If the class is not so
+ * annotated, an empty set is returned.
+ *
+ * @return the names of the annotation types supported by this
+ * processor, or an empty set if none
+ */
+ @Override
+ public Set getSupportedAnnotationTypes() {
+ Set supportAnnotations = new HashSet<>();
+ supportAnnotations.add(Route.class.getCanonicalName()); // This annotation mark class which can be router.
+ return supportAnnotations;
+ }
+
+ /**
+ * If the processor class is annotated with {@link
+ * SupportedSourceVersion}, return the source version in the
+ * annotation. If the class is not so annotated, {@link
+ * SourceVersion#RELEASE_6} is returned.
+ *
+ * @return the latest source version supported by this processor
+ */
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public Set getSupportedOptions() {
+ return Sets.newHashSet(KEY_MODULE_NAME);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param annotations
+ * @param roundEnv
+ */
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ Set extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
+ boolean parseResult = false;
+ try {
+ logger.info(">>> Found routes, start... <<<");
+ this.parseRoutes(routeElements);
+
+ parseResult = true;
+ } catch (Exception e) {
+ logger.error(e);
+ }
+
+ return parseResult;
+ }
+
+ private void parseRoutes(Set extends Element> routeElements) throws IOException {
+ if (CollectionUtils.isNotEmpty(routeElements)) {
+ // Perpare the type an so on.
+
+ logger.info(">>> Found routes, size is " + routeElements.size() + " <<<");
+
+ rootMap.clear();
+
+ // Fantastic four
+ TypeElement type_Activity = elementUtil.getTypeElement(ACTIVITY);
+ TypeElement type_Service = elementUtil.getTypeElement(SERVICE);
+
+ // Interface of ARouter.
+ TypeElement type_IProvider = elementUtil.getTypeElement(IPROVIDER);
+ TypeElement type_IRouteGroup = elementUtil.getTypeElement(IROUTE_GROUP);
+ TypeElement type_IProviderGroup = elementUtil.getTypeElement(IPROVIDER_GROUP);
+ ClassName routeMetaCn = ClassName.get(RouteMeta.class);
+ ClassName routeTypeCn = ClassName.get(RouteType.class);
+
+ /**
+ * Build input type, format as :
+ *
+ * ```Map>```
+ */
+ ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
+ ClassName.get(Map.class),
+ ClassName.get(String.class),
+ ParameterizedTypeName.get(
+ ClassName.get(Class.class),
+ WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
+ )
+ );
+
+ /**
+ *
+ * ```Map```
+ */
+ ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
+ ClassName.get(Map.class),
+ ClassName.get(String.class),
+ ClassName.get(RouteMeta.class)
+ );
+
+ /**
+ * Build input param name.
+ */
+ ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
+ ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
+ ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build(); // Ps. its param type same as groupParamSpec!
+
+ /**
+ * Build method : 'loadInto'
+ */
+ MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
+ .addAnnotation(Override.class)
+ .addModifiers(PUBLIC)
+ .addParameter(rootParamSpec);
+
+ // Follow a sequence, find out metas of group first, generate java file, then statistics them as root.
+ for (Element element : routeElements) {
+ TypeMirror tm = element.asType();
+ Route route = element.getAnnotation(Route.class);
+ RouteMeta routeMete = null;
+
+ if (typeUtil.isSubtype(tm, type_Activity.asType())) { // Activity
+ logger.info(">>> Found activity route: " + tm.toString() + " <<<");
+
+ // Get all fields annotation by @Param
+ Map paramsType = new HashMap<>();
+ for (Element field : element.getEnclosedElements()) {
+ if (field.getKind().isField() && field.getAnnotation(Param.class) != null) {
+ Param paramConfig = field.getAnnotation(Param.class);
+ paramsType.put(StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : field.getSimpleName().toString() + "|" + paramConfig.name(), typeExchange(field.asType()));
+ }
+ }
+
+ paramsType.size();
+ routeMete = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
+ } else if (typeUtil.isSubtype(tm, type_IProvider.asType())) { // IProvider
+ logger.info(">>> Found provider route: " + tm.toString() + " <<<");
+ routeMete = new RouteMeta(route, element, RouteType.PROVIDER, null);
+ } else if (typeUtil.isSubtype(tm, type_Service.asType())) { // Service
+ logger.info(">>> Found service route: " + tm.toString() + " <<<");
+ routeMete = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
+ }
+
+ categories(routeMete);
+// if (StringUtils.isEmpty(moduleName)) { // Hasn't generate the module name.
+// moduleName = ModuleUtils.generateModuleName(element, logger);
+// }
+ }
+
+ MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
+ .addAnnotation(Override.class)
+ .addModifiers(PUBLIC)
+ .addParameter(providerParamSpec);
+
+ // Start generate java source, structure is divided into upper and lower levels, used for demand initialization.
+ for (Map.Entry> entry : groupMap.entrySet()) {
+ String groupName = entry.getKey();
+
+ MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
+ .addAnnotation(Override.class)
+ .addModifiers(PUBLIC)
+ .addParameter(groupParamSpec);
+
+ // Build group method body
+ Set groupData = entry.getValue();
+ for (RouteMeta routeMeta : groupData) {
+ switch (routeMeta.getType()) {
+ case PROVIDER: // Need cache provider's super class
+ List extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
+ for (TypeMirror tm : interfaces) {
+ if (typeUtil.isSubtype(tm, type_IProvider.asType())) {
+ // This interface extend the IProvider, so it can be used for mark provider
+ loadIntoMethodOfProviderBuilder.addStatement(
+ "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
+ tm.toString().substring(tm.toString().lastIndexOf(".") + 1), // Spite unuseless name
+ routeMetaCn,
+ routeTypeCn,
+ ClassName.get((TypeElement) routeMeta.getRawType()),
+ routeMeta.getPath(),
+ routeMeta.getGroup());
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Make map body for paramsType
+ StringBuilder mapBodyBuilder = new StringBuilder();
+ Map paramsType = routeMeta.getParamsType();
+ if (MapUtils.isNotEmpty(paramsType)) {
+ for (Map.Entry types : paramsType.entrySet()) {
+ mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
+ }
+ }
+ String mapBody = mapBodyBuilder.toString();
+
+ loadIntoMethodOfGroupBuilder.addStatement(
+ "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
+ routeMeta.getPath(),
+ routeMetaCn,
+ routeTypeCn,
+ ClassName.get((TypeElement) routeMeta.getRawType()),
+ routeMeta.getPath().toLowerCase(),
+ routeMeta.getGroup().toLowerCase());
+ }
+
+ // Generate groups
+ String groupFileName = NAME_OF_GROUP + groupName;
+ JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
+ TypeSpec.classBuilder(groupFileName)
+ .addJavadoc(WARNING_TIPS)
+ .addSuperinterface(ClassName.get(type_IRouteGroup))
+ .addModifiers(PUBLIC)
+ .addMethod(loadIntoMethodOfGroupBuilder.build())
+ .build()
+ ).build().writeTo(mFiler);
+
+ logger.info(">>> Generated group: " + groupName + "<<<");
+ rootMap.put(groupName, groupFileName);
+ }
+
+ if (MapUtils.isNotEmpty(rootMap)) {
+ // Generate root meta by group name, it must be generated before root, then I can findout the class of group.
+ for (Map.Entry entry : rootMap.entrySet()) {
+ loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
+ }
+ }
+
+ // Wirte provider into disk
+ String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
+ JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
+ TypeSpec.classBuilder(providerMapFileName)
+ .addJavadoc(WARNING_TIPS)
+ .addSuperinterface(ClassName.get(type_IProviderGroup))
+ .addModifiers(PUBLIC)
+ .addMethod(loadIntoMethodOfProviderBuilder.build())
+ .build()
+ ).build().writeTo(mFiler);
+
+ logger.info(">>> Generated provider map, name is " + providerMapFileName + " <<<");
+
+ // Write root meta into disk.
+ String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
+ JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
+ TypeSpec.classBuilder(rootFileName)
+ .addJavadoc(WARNING_TIPS)
+ .addSuperinterface(ClassName.get(elementUtil.getTypeElement(ITROUTE_ROOT)))
+ .addModifiers(PUBLIC)
+ .addMethod(loadIntoMethodOfRootBuilder.build())
+ .build()
+ ).build().writeTo(mFiler);
+
+ logger.info(">>> Generated root, name is " + rootFileName + " <<<");
+ }
+ }
+
+ /**
+ * Sort metas in group.
+ *
+ * @param routeMete metas.
+ */
+ private void categories(RouteMeta routeMete) {
+ if (routeVerify(routeMete)) {
+ logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
+ Set routeMetas = groupMap.get(routeMete.getGroup());
+ if (CollectionUtils.isEmpty(routeMetas)) {
+ Set routeMetaSet = new TreeSet<>(new Comparator() {
+ @Override
+ public int compare(RouteMeta r1, RouteMeta r2) {
+ try {
+ return r1.getPath().compareTo(r2.getPath());
+ } catch (NullPointerException npe) {
+ logger.error(npe.getMessage());
+ return 0;
+ }
+ }
+ });
+ routeMetaSet.add(routeMete);
+ groupMap.put(routeMete.getGroup(), routeMetaSet);
+ } else {
+ routeMetas.add(routeMete);
+ }
+ } else {
+ logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<");
+ }
+ }
+
+ /**
+ * Verify the route meta
+ *
+ * @param meta raw meta
+ */
+ private boolean routeVerify(RouteMeta meta) {
+ String path = meta.getPath();
+
+ if (StringUtils.isEmpty(path) || !path.startsWith("/")) { // The path must be start with '/' and not empty!
+ return false;
+ }
+
+ if (StringUtils.isEmpty(meta.getGroup())) { // Use default group(the first word in path)
+ try {
+ String defaultGroup = path.substring(1, path.indexOf("/", 1));
+ if (StringUtils.isEmpty(defaultGroup)) {
+ return false;
+ }
+
+ meta.setGroup(defaultGroup);
+ return true;
+ } catch (Exception e) {
+ logger.error("Failed to extract default group! " + e.getMessage());
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Diagnostics out the true java type
+ *
+ * @param rawType Raw type
+ * @return Type class of java
+ */
+ private int typeExchange(TypeMirror rawType) {
+ if (rawType.getKind().isPrimitive()) { // is java base type
+ return rawType.getKind().ordinal();
+ }
+
+ switch (rawType.toString()) {
+ case BYTE:
+ return TypeKind.BYTE.ordinal();
+ case SHORT:
+ return TypeKind.SHORT.ordinal();
+ case INTEGER:
+ return TypeKind.INT.ordinal();
+ case LONG:
+ return TypeKind.LONG.ordinal();
+ case FLOAT:
+ return TypeKind.FLOAT.ordinal();
+ case DOUBEL:
+ return TypeKind.DOUBLE.ordinal();
+ case BOOLEAN:
+ return TypeKind.BOOLEAN.ordinal();
+ case STRING:
+ default:
+ return TypeKind.OTHER.ordinal(); // I say it was java.long.String
+
+ }
+ }
+}
diff --git a/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/utils/Consts.java b/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/utils/Consts.java
new file mode 100644
index 00000000..6632c2ec
--- /dev/null
+++ b/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/utils/Consts.java
@@ -0,0 +1,53 @@
+package com.alibaba.android.arouter.compiler.utils;
+
+import java.io.File;
+
+/**
+ * Some consts used in processors
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/24 20:18
+ */
+public class Consts {
+ // System interface
+ public static final String ACTIVITY = "android.app.Activity";
+ public static final String SERVICE = "android.app.Service";
+
+ // Java type
+ public static final String BYTE = "java.lang.Byte";
+ public static final String SHORT = "java.lang.Short";
+ public static final String INTEGER = "java.lang.Integer";
+ public static final String LONG = "java.lang.Long";
+ public static final String FLOAT = "java.lang.Float";
+ public static final String DOUBEL = "java.lang.Double";
+ public static final String BOOLEAN = "java.lang.Boolean";
+ public static final String STRING = "java.lang.String";
+
+ // Custom interface
+ public static final String IPROVIDER = "com.alibaba.android.arouter.facade.template.IProvider";
+ public static final String IPROVIDER_GROUP = "com.alibaba.android.arouter.facade.template.IProviderGroup";
+
+ public static final String IINTERCEPTOR = "com.alibaba.android.arouter.facade.template.IInterceptor";
+ public static final String IINTERCEPTOR_GROUP = "com.alibaba.android.arouter.facade.template.IInterceptorGroup";
+
+ public static final String ITROUTE_ROOT = "com.alibaba.android.arouter.facade.template.IRouteRoot";
+ public static final String IROUTE_GROUP = "com.alibaba.android.arouter.facade.template.IRouteGroup";
+
+ // Log
+ public static final String PREFIX_OF_LOGGER = "ARouter::Compiler ";
+
+ // Options of processor
+ public static final String KEY_MODULE_NAME = "moduleName";
+
+ // Generate
+ public static final String SEPARATOR = "$$";
+ public static final String WARNING_TIPS = "DO NOT EDIT THIE FILE!!! IT WAS GENERATED BY AROUTER.";
+ public static final String METHOD_LOAD_INTO = "loadInto";
+ public static final String NAME_OF_ROOT = "ARouter$$Root";
+ public static final String NAME_OF_PROVIDER = "ARouter$$Providers";
+ public static final String NAME_OF_GROUP = "ARouter$$Group$$";
+ public static final String NAME_OF_INTERCEPTOR = "ARouter$$Interceptors";
+ public static final String PACKAGE_OF_GENERATE_FILE = "com.alibaba.android.arouter.routes";
+ public static final String SOURCE_FILE_FOLDER_NAME = File.separator + "src" + File.separator + "main" + File.separator + "java";
+}
\ No newline at end of file
diff --git a/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/utils/Logger.java b/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/utils/Logger.java
new file mode 100644
index 00000000..ea087050
--- /dev/null
+++ b/arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/utils/Logger.java
@@ -0,0 +1,57 @@
+package com.alibaba.android.arouter.compiler.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.annotation.processing.Messager;
+import javax.tools.Diagnostic;
+
+/**
+ * Simplify the messager.
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/8/22 上午11:48
+ */
+public class Logger {
+ private Messager msg;
+
+ public Logger(Messager messager) {
+ msg = messager;
+ }
+
+ /**
+ * Print info log.
+ */
+ public void info(CharSequence info) {
+ if (StringUtils.isNotEmpty(info)) {
+ msg.printMessage(Diagnostic.Kind.NOTE, Consts.PREFIX_OF_LOGGER + info);
+ }
+ }
+
+ public void error(CharSequence error) {
+ if (StringUtils.isNotEmpty(error)) {
+ msg.printMessage(Diagnostic.Kind.ERROR, Consts.PREFIX_OF_LOGGER + "An exception is encountered, [" + error + "]");
+ }
+ }
+
+ public void error(Throwable error) {
+ if (null != error) {
+ msg.printMessage(Diagnostic.Kind.ERROR, Consts.PREFIX_OF_LOGGER + "An exception is encountered, [" + error.getMessage() + "]" + "\n" + formatStackTrace(error.getStackTrace()));
+ }
+ }
+
+ public void warning(CharSequence warning) {
+ if (StringUtils.isNotEmpty(warning)) {
+ msg.printMessage(Diagnostic.Kind.WARNING, Consts.PREFIX_OF_LOGGER + warning);
+ }
+ }
+
+ private String formatStackTrace(StackTraceElement[] stackTrace) {
+ StringBuilder sb = new StringBuilder();
+ for (StackTraceElement element : stackTrace) {
+ sb.append(" at ").append(element.toString());
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..921790d8
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,18 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.1.3'
+ classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
+ classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2'
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..b1e0510a
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,36 @@
+## Project-wide Gradle settings.
+#
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+#
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+#
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+#Fri Oct 09 19:03:24 CST 2015
+
+# JVM
+org.gradle.daemon=true
+#org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
+
+COMPILE_SDK_VERSION=23
+BUILDTOOLS_VERSION=23.0.1
+MIN_SDK_VERSION=9
+TARGET_SDK_VERSION=23
+
+bintrayRepo=maven
+publishedGroupId=com.alibaba
+siteUrl=https://github.com/Alibaba/ARouter
+gitUrl=https://github.com/Alibaba/ARouter.git
+developerId=zhi1ong
+developerName=zhilong.liu
+developerEmail=zhilong.liu@aliyun.com
+
+licenseName=The Apache Software License, Version 2.0
+licenseUrl=http://www.apache.org/licenses/LICENSE-2.0.txt
+allLicenses=["Apache-2.0"]
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..05ef575b
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..91fa8862
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Oct 21 11:34:03 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://mirrors.taobao.net/mirror/gradle/gradle-2.14.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..9d82f789
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..aec99730
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..6ff9534b
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,6 @@
+include ':app'
+include ':arouter-api'
+include ':arouter-compiler'
+include ':arouter-annotation'
+include ':test-module-1'
+include ':test-module-2'
\ No newline at end of file
diff --git a/test-module-1/build.gradle b/test-module-1/build.gradle
new file mode 100644
index 00000000..6d8dcd79
--- /dev/null
+++ b/test-module-1/build.gradle
@@ -0,0 +1,42 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.neenbedankt.android-apt'
+
+dependencies {
+// apt project(':arouter-compiler')
+// compile project(':arouter-api')
+
+ apt 'com.alibaba:arouter-compiler:1.0.1'
+ compile 'com.alibaba:arouter-api:1.0.1'
+
+ compile 'com.android.support:appcompat-v7:23.+'
+}
+android {
+ compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)
+ buildToolsVersion BUILDTOOLS_VERSION
+
+ defaultConfig {
+ minSdkVersion Integer.parseInt(MIN_SDK_VERSION)
+ targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ buildTypes {
+ release {
+ debuggable false
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+
+ lintOptions { abortOnError false }
+ }
+}
+
+apt {
+ arguments {
+ moduleName project.getName();
+ }
+}
\ No newline at end of file
diff --git a/test-module-1/src/main/AndroidManifest.xml b/test-module-1/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..2d7b3fdf
--- /dev/null
+++ b/test-module-1/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test-module-1/src/main/java/com/alibaba/android/arouter/demo/module1/TestInterceptor90.java b/test-module-1/src/main/java/com/alibaba/android/arouter/demo/module1/TestInterceptor90.java
new file mode 100644
index 00000000..1883fde1
--- /dev/null
+++ b/test-module-1/src/main/java/com/alibaba/android/arouter/demo/module1/TestInterceptor90.java
@@ -0,0 +1,40 @@
+package com.alibaba.android.arouter.demo.module1;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.alibaba.android.arouter.facade.Postcard;
+import com.alibaba.android.arouter.facade.annotation.Interceptor;
+import com.alibaba.android.arouter.facade.callback.InterceptorCallback;
+import com.alibaba.android.arouter.facade.template.IInterceptor;
+
+/**
+ * TODO feature
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/9/9 14:34
+ */
+@Interceptor(priority = 90)
+public class TestInterceptor90 implements IInterceptor {
+ /**
+ * The operation of this tollgate.
+ *
+ * @param postcard meta
+ * @param callback cb
+ */
+ @Override
+ public void process(Postcard postcard, InterceptorCallback callback) {
+ callback.onContinue(postcard);
+ }
+
+ /**
+ * Do your init work in this method, it well be call when processor has been load.
+ *
+ * @param context ctx
+ */
+ @Override
+ public void init(Context context) {
+ Log.e("test", "位于moudle1中的拦截器初始化了");
+ }
+}
diff --git a/test-module-1/src/main/java/com/alibaba/android/arouter/demo/module1/TestModuleActivity.java b/test-module-1/src/main/java/com/alibaba/android/arouter/demo/module1/TestModuleActivity.java
new file mode 100644
index 00000000..1433d221
--- /dev/null
+++ b/test-module-1/src/main/java/com/alibaba/android/arouter/demo/module1/TestModuleActivity.java
@@ -0,0 +1,16 @@
+package com.alibaba.android.arouter.demo.module1;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.alibaba.android.arouter.facade.annotation.Route;
+
+@Route(path = "/module/1", group = "m1")
+public class TestModuleActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_test_module);
+ }
+}
diff --git a/test-module-1/src/main/res/layout/activity_test_module.xml b/test-module-1/src/main/res/layout/activity_test_module.xml
new file mode 100644
index 00000000..ffd65786
--- /dev/null
+++ b/test-module-1/src/main/res/layout/activity_test_module.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/test-module-1/src/main/res/values-w820dp/dimens.xml b/test-module-1/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 00000000..63fc8164
--- /dev/null
+++ b/test-module-1/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/test-module-1/src/main/res/values/dimens.xml b/test-module-1/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..47c82246
--- /dev/null
+++ b/test-module-1/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/test-module-1/src/main/res/values/strings.xml b/test-module-1/src/main/res/values/strings.xml
new file mode 100644
index 00000000..85420055
--- /dev/null
+++ b/test-module-1/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/test-module-2/build.gradle b/test-module-2/build.gradle
new file mode 100644
index 00000000..1cd8afd1
--- /dev/null
+++ b/test-module-2/build.gradle
@@ -0,0 +1,42 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.neenbedankt.android-apt'
+
+dependencies {
+ apt project(':arouter-compiler')
+ compile project(':arouter-api')
+
+ apt 'com.alibaba:arouter-compiler:1.0.1'
+ compile 'com.alibaba:arouter-api:1.0.1'
+
+ compile 'com.android.support:appcompat-v7:23.+'
+}
+android {
+ compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)
+ buildToolsVersion BUILDTOOLS_VERSION
+
+ defaultConfig {
+ minSdkVersion Integer.parseInt(MIN_SDK_VERSION)
+ targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ buildTypes {
+ release {
+ debuggable false
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+
+ lintOptions { abortOnError false }
+ }
+}
+
+apt {
+ arguments {
+ moduleName project.getName();
+ }
+}
\ No newline at end of file
diff --git a/test-module-2/src/main/AndroidManifest.xml b/test-module-2/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..ab22a2c3
--- /dev/null
+++ b/test-module-2/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test-module-2/src/main/java/com/alibaba/android/arouter/demo/module2/Interceptor6.java b/test-module-2/src/main/java/com/alibaba/android/arouter/demo/module2/Interceptor6.java
new file mode 100644
index 00000000..71a9d8a7
--- /dev/null
+++ b/test-module-2/src/main/java/com/alibaba/android/arouter/demo/module2/Interceptor6.java
@@ -0,0 +1,40 @@
+package com.alibaba.android.arouter.demo.module2;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.alibaba.android.arouter.facade.Postcard;
+import com.alibaba.android.arouter.facade.annotation.Interceptor;
+import com.alibaba.android.arouter.facade.callback.InterceptorCallback;
+import com.alibaba.android.arouter.facade.template.IInterceptor;
+
+/**
+ * 测试用Interceptor
+ *
+ * @author Alex Contact me.
+ * @version 1.0
+ * @since 16/9/9 14:19
+ */
+@Interceptor(priority = 2)
+public class Interceptor6 implements IInterceptor {
+ /**
+ * The operation of this tollgate.
+ *
+ * @param postcard meta
+ * @param callback cb
+ */
+ @Override
+ public void process(Postcard postcard, InterceptorCallback callback) {
+ callback.onContinue(postcard);
+ }
+
+ /**
+ * Do your init work in this method, it well be call when processor has been load.
+ *
+ * @param context ctx
+ */
+ @Override
+ public void init(Context context) {
+ Log.e("test", "位于moudle2中的拦截器初始化了");
+ }
+}
diff --git a/test-module-2/src/main/java/com/alibaba/android/arouter/demo/module2/TestModuleActivity2.java b/test-module-2/src/main/java/com/alibaba/android/arouter/demo/module2/TestModuleActivity2.java
new file mode 100644
index 00000000..aa953ea0
--- /dev/null
+++ b/test-module-2/src/main/java/com/alibaba/android/arouter/demo/module2/TestModuleActivity2.java
@@ -0,0 +1,16 @@
+package com.alibaba.android.arouter.demo.module2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.alibaba.android.arouter.facade.annotation.Route;
+
+@Route(path = "/module/2", group = "m2")
+public class TestModuleActivity2 extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_test_module2);
+ }
+}
diff --git a/test-module-2/src/main/res/layout/activity_test_module2.xml b/test-module-2/src/main/res/layout/activity_test_module2.xml
new file mode 100644
index 00000000..72f83983
--- /dev/null
+++ b/test-module-2/src/main/res/layout/activity_test_module2.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/test-module-2/src/main/res/values/dimens.xml b/test-module-2/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..47c82246
--- /dev/null
+++ b/test-module-2/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/test-module-2/src/main/res/values/strings.xml b/test-module-2/src/main/res/values/strings.xml
new file mode 100644
index 00000000..85420055
--- /dev/null
+++ b/test-module-2/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+
+