模块化开发中,不同模块之间相互不依赖,如果要从一个模块的Activity跳到另一个模块的Activity,那么正常情况只能写死要跳转的Activity的类名,或者通过隐式启动的方式来启动。这样子的话,极不方便,也不清晰。
这边介绍一种通过给目标Activity添加注解的方式实现模块间跳转的方式。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
String path();
String pathPrefix() default "";
String pathPattern() default "";
String name() default "unknown";
int priority() default -1;
}
解析注解。
package com.midea.base.core.dofrouter.apt;
import com.google.auto.service.AutoService;
import com.midea.base.core.dofrouter.annotation.RouteType;
import com.midea.base.core.dofrouter.annotation.Route;
import com.midea.base.core.dofrouter.annotation.data.RouteMetaData;
import com.midea.base.core.dofrouter.annotation.utils.Consts;
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 java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import static javax.lang.model.element.Modifier.PUBLIC;
@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.midea.base.core.dofrouter.annotation.Route"})
public class RouteProcessor extends BaseProcessor {
private HashMap<String, RouteMetaData> routeMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Logger.info(">>> RouteProcessor init. <<<");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!annotations.isEmpty()) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
try {
Logger.info(">>> Found routes, start... <<<");
this.parseRoutes(elements);
} catch (Exception e) {
Logger.error(e);
}
return true;
}
Logger.info(">>> annotations is empty. <<<");
return false;
}
private void parseRoutes(Set<? extends Element> elements) throws IOException {
if (!elements.isEmpty()) {
Logger.info(">>> Found routes, size is " + elements.size() + " <<<");
routeMap.clear();
TypeMirror tmActivity = mElements.getTypeElement(RouteType.ACTIVITY.getClassName()).asType();
TypeMirror tmFragment = mElements.getTypeElement(RouteType.FRAGMENT.getClassName()).asType();
TypeMirror tmFragmentV4 = mElements.getTypeElement(RouteType.FRAGMENT_V4.getClassName()).asType();
ParameterizedTypeName mapTypeOfRouteLoader = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class), ClassName.get(RouteMetaData.class));
ParameterSpec mapParamSpec = ParameterSpec.builder(mapTypeOfRouteLoader, "map").build();
//Generate implement IRouteLoader interface class
MethodSpec.Builder routeLoaderFunSpecBuild = MethodSpec.methodBuilder(Consts.METHOD_LOAD)
.addParameter(mapParamSpec)
.addAnnotation(Override.class)
.addModifiers(PUBLIC);
if (!elements.isEmpty()) {
for (Element element : elements) {
Route routeAnn = element.getAnnotation(Route.class);
RouteType routeType;
if (mTypes.isSubtype(element.asType(), tmActivity)) {
Logger.info("Found Activity " + element.asType());
routeType = RouteType.ACTIVITY;
} else if (mTypes.isSubtype(element.asType(), tmFragment)) {
Logger.info("Found Fragment " + element.asType());
routeType = RouteType.FRAGMENT;
} else if (mTypes.isSubtype(element.asType(), tmFragmentV4)) {
Logger.info("Found Fragment_v4 " + element.asType());
routeType = RouteType.FRAGMENT_V4;
} else {
Logger.info("Unknown route " + element.asType());
routeType = RouteType.UNKNOWN;
}
if (routeAnn.path().length() > 0) {
if (routeMap.containsKey(routeAnn.path())) {
Logger.warn("The route ${routeMap[routeAnn.path]?.name} already has Path { ${routeAnn.path} }, so skip route ${it.asType()}");
continue;
}
routeMap.put(routeAnn.path(), new RouteMetaData(routeType, routeAnn.priority(), routeAnn.name(), routeAnn.path(), routeAnn.pathPrefix(), routeAnn.pathPattern(), Object.class));
routeLoaderFunSpecBuild.addStatement(
"map.put($S, new $T($T.$L, $L, $S, $S, $S, $S, $T.class))",
routeAnn.path(),
RouteMetaData.class,
RouteType.class,
routeType,
routeAnn.priority(),
routeAnn.name(),
routeAnn.path(),
routeAnn.pathPrefix(),
routeAnn.pathPattern(),
element.asType()
);
}
}
}
String fileName = Consts.ROUTE_LOADER_NAME + "_" + moduleName;
TypeSpec typeIRouteLoader = TypeSpec.classBuilder(fileName)
.addSuperinterface(ClassName.get(mElements.getTypeElement(Consts.PACKAGE + ".api.interfaces.IRouteLoader")))
.addModifiers(PUBLIC)
.addMethod(routeLoaderFunSpecBuild.build())
.build();
JavaFile.builder(Consts.PACKAGE, typeIRouteLoader)
.build()
.writeTo(mFiler);
}
}
}
package com.midea.base.core.dofrouter.apt;
import com.midea.base.core.dofrouter.annotation.utils.Consts;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
public abstract class BaseProcessor extends AbstractProcessor {
Filer mFiler;
Elements mElements;
Types mTypes;
String moduleName;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mElements = processingEnv.getElementUtils();
mTypes = processingEnv.getTypeUtils();
Map<String, String> options = processingEnv.getOptions();
if (!options.isEmpty()) {
moduleName = options.get(Consts.MODULE_NAME);
}
if (moduleName != null && moduleName.length() > 0) {
moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]", "");
Logger.info(">>> moduleName = " + moduleName + " <<<");
} else {
Logger.error(Consts.NO_MODULE_NAME_TIPS);
throw new RuntimeException("DOFROUTER::Compiler >>> No module name, for more information, look at gradle log.");
}
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedOptions() {
return new HashSet<String>() {{
this.add(Consts.MODULE_NAME);
}};
}
}
对应常量。
package com.midea.base.core.dofrouter.annotation.utils;
public class Consts {
public static final String TAG = "[DOFRouter]::";
public static final String PACKAGE = "com.midea.base.core.dofrouter";
public static final String PROJECT_NAME = "DOFRouter";
public static final String ROUTE_LOADER_NAME = PROJECT_NAME + "_RouteLoader";
public static final String INTERCEPTOR_LOADER_NAME = PROJECT_NAME + "_InterceptorLoader";
public static final String INTERCEPTOR = PACKAGE + ".api.interfaces.IRouteInterceptor";
public static final String METHOD_LOAD = "loadInto";
public static final String PREFIX_OF_LOGGER = "DOFROUTER::Compile ";
public static final String MODULE_NAME = "DOF_MODULE_NAME";
public static final String NO_MODULE_NAME_TIPS = "These no module name, at 'build.gradle', like :\n" +
"kapt {\n" +
" arguments {\n" +
" arg("DOF_MODULE_NAME", project.getName())\n" +
" }\n" +
"}";
public static final String KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated";
}
package com.midea.base.core.dofrouter.annotation
enum class RouteType(val className: String) {
UNKNOWN(""),
ACTIVITY("android.app.Activity"),
FRAGMENT("android.app.Fragment"),
FRAGMENT_V4("android.support.v4.app.Fragment")
}
package com.midea.base.core.dofrouter.annotation.data
import com.midea.base.core.dofrouter.annotation.RouteType
/**
* Route元数据,用于存储被[Route]注解的类的信息
*/
data class RouteMetaData(
val routeType: RouteType = RouteType.UNKNOWN,
val priority: Int = -1,
val name: String = "undefine",
val path: String = "",
val pathPrefix: String = "",
val pathPattern: String = "",
val clazz: Class<*> = Any::class.java
)
假设给Activity添加注解。
package com.example.myapplication
import android.os.Build
import android.os.Bundle
import android.widget.Button
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.midea.base.core.dofrouter.annotation.Route
import com.midea.base.core.serviceloader.api.ServiceLoaderHelper
@Route(path = "/zpw/demo/main2")
class Main2Activity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.KITKAT)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.jump).text = "Main2Activity"
}
}
经过APT处理,上面的逻辑将会生成如下代码。
package com.midea.base.core.dofrouter;
import com.example.myapplication.Main2Activity;
import com.midea.base.core.dofrouter.annotation.RouteType;
import com.midea.base.core.dofrouter.annotation.data.RouteMetaData;
import com.midea.base.core.dofrouter.api.interfaces.IRouteLoader;
import java.lang.Override;
import java.lang.String;
import java.util.Map;
public class DOFRouter_RouteLoader_app implements IRouteLoader {
@Override
public void loadInto(Map<String, RouteMetaData> map) {
map.put("/zpw/demo/main2", new RouteMetaData(RouteType.ACTIVITY, -1, "unknown", "/zpw/demo/main2", "", "", Main2Activity.class));
}
}
此时我们需要提供一个跳转逻辑帮助类。
package com.midea.base.core.dofrouter.api.core
import android.annotation.TargetApi
import android.app.Activity
import android.app.Fragment
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.text.TextUtils
import android.util.Size
import android.util.SizeF
import android.util.SparseArray
import androidx.annotation.IntDef
import com.midea.base.core.dofrouter.api.utils.Logger
import java.io.Serializable
import java.util.*
object DOFRouter {
@JvmStatic
@Synchronized
fun init(context: Context) {
Logger.d("Init start")
Router.init(context)
Logger.d("Init end")
}
/**
* 创建一个Navigator 用以发起一个路由请求
* @param uri 路由地址
* @return Navigator
*/
fun create(uri: Uri): Navigator {
return Navigator(uri)
}
/**
* 创建一个Navigator 用以发起一个路由请求
* @param path 路由地址
* @return Navigator
*/
fun create(path: String): Navigator {
return Navigator(path)
}
/**
* 开启日志
*/
@JvmStatic
fun openDebug() {
Logger.openDebug()
}
@IntDef(
Intent.FLAG_GRANT_READ_URI_PERMISSION,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
Intent.FLAG_FROM_BACKGROUND,
Intent.FLAG_DEBUG_LOG_RESOLUTION,
Intent.FLAG_EXCLUDE_STOPPED_PACKAGES,
Intent.FLAG_INCLUDE_STOPPED_PACKAGES,
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION,
Intent.FLAG_ACTIVITY_NO_HISTORY,
Intent.FLAG_ACTIVITY_SINGLE_TOP,
Intent.FLAG_ACTIVITY_NEW_TASK,
Intent.FLAG_ACTIVITY_MULTIPLE_TASK,
Intent.FLAG_ACTIVITY_CLEAR_TOP,
Intent.FLAG_ACTIVITY_FORWARD_RESULT,
Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP,
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT,
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,
Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY,
Intent.FLAG_ACTIVITY_NEW_DOCUMENT,
Intent.FLAG_ACTIVITY_NO_USER_ACTION,
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT,
Intent.FLAG_ACTIVITY_NO_ANIMATION,
Intent.FLAG_ACTIVITY_CLEAR_TASK,
Intent.FLAG_ACTIVITY_TASK_ON_HOME,
Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS,
Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
)
@Retention(AnnotationRetention.SOURCE)
annotation class FlagInt
class Navigator {
var path: String
private set
var extras = Bundle()
private set
var enterAnim = -1
private set
var exitAnim = -1
private set
var flags = 0
private set
var requestCode = -1
private set
var onBeforeCallback: ((navigator: Navigator) -> Unit)? = null
private set
var onNotFoundCallback: ((navigator: Navigator) -> Unit)? = null
private set
var onArrivedCallback: ((navigator: Navigator) -> Unit)? = null
private set
var onInterceptCallback: ((navigator: Navigator) -> Unit)? = null
private set
var activity: Activity? = null
private set
var fragment: Fragment? = null
private set
var fragmentV4: androidx.fragment.app.Fragment? = null
private set
var options: Bundle? = null
private set
var context: Context? = null
private set
internal constructor(uri: Uri) {
uri.queryParameterNames.forEach {
extras.putString(it, uri.getQueryParameter(it))
}
this.path = "${if (TextUtils.isEmpty(uri.scheme)) "" else uri.scheme!!.plus("://")}${uri.host ?: ""}${uri.path}"
}
internal constructor(path: String) {
val uri = Uri.parse(path)
uri.queryParameterNames.forEach {
extras.putString(it, uri.getQueryParameter(it))
}
this.path = "${if (TextUtils.isEmpty(uri.scheme)) "" else uri.scheme!!.plus("://")}${uri.host ?: ""}${uri.path}"
}
fun redirect(path: String): Navigator {
this.path = path
return this
}
fun withExtras(bundle: Bundle): Navigator {
this.extras.putAll(bundle)
return this
}
fun withInt(key: String?, int: Int): Navigator {
extras.putInt(key, int)
return this
}
fun withIntArray(key: String?, intArray: IntArray): Navigator {
extras.putIntArray(key, intArray)
return this
}
fun withIntArrayList(key: String?, int: ArrayList<Int>?): Navigator {
extras.putIntegerArrayList(key, int)
return this
}
fun withLong(key: String?, long: Long): Navigator {
extras.putLong(key, long)
return this
}
fun withLongArray(key: String?, long: LongArray?): Navigator {
extras.putLongArray(key, long)
return this
}
fun withShort(key: String?, short: Short): Navigator {
extras.putShort(key, short)
return this
}
fun withShortArray(key: String?, short: ShortArray?): Navigator {
extras.putShortArray(key, short)
return this
}
fun withDouble(key: String?, double: Double): Navigator {
extras.putDouble(key, double)
return this
}
fun withDoubleArray(key: String?, double: DoubleArray?): Navigator {
extras.putDoubleArray(key, double)
return this
}
fun withFloat(key: String?, float: Float): Navigator {
extras.putFloat(key, float)
return this
}
fun withFloatArray(key: String?, float: FloatArray?): Navigator {
extras.putFloatArray(key, float)
return this
}
fun withString(key: String?, string: String?): Navigator {
extras.putString(key, string)
return this
}
fun withStringArray(key: String?, string: Array<String>?): Navigator {
extras.putStringArray(key, string)
return this
}
fun withStringList(key: String?, stringList: ArrayList<String>?): Navigator {
extras.putStringArrayList(key, stringList)
return this
}
fun withChar(key: String?, char: Char): Navigator {
extras.putChar(key, char)
return this
}
fun withCharArray(key: String?, char: CharArray?): Navigator {
extras.putCharArray(key, char)
return this
}
fun withCharSequence(key: String?, charSequence: CharSequence?): Navigator {
extras.putCharSequence(key, charSequence)
return this
}
fun withCharSequenceArray(key: String?, charSequence: Array<CharSequence>?): Navigator {
extras.putCharSequenceArray(key, charSequence)
return this
}
fun withCharSequenceArrayList(key: String?, charSequence: ArrayList<CharSequence>?): Navigator {
extras.putCharSequenceArrayList(key, charSequence)
return this
}
fun withBoolean(key: String?, boolean: Boolean): Navigator {
extras.putBoolean(key, boolean)
return this
}
fun withBooleanArray(key: String?, booleanArray: BooleanArray?): Navigator {
extras.putBooleanArray(key, booleanArray)
return this
}
fun withByte(key: String?, byte: Byte): Navigator {
extras.putByte(key, byte)
return this
}
fun withByteArray(key: String?, byte: ByteArray?): Navigator {
extras.putByteArray(key, byte)
return this
}
fun withParcelable(key: String?, parcelable: Parcelable?): Navigator {
extras.putParcelable(key, parcelable)
return this
}
fun withParcelableArray(key: String?, parcelable: Array<Parcelable>?): Navigator {
extras.putParcelableArray(key, parcelable)
return this
}
fun withParcelableArrayList(key: String?, parcelable: ArrayList<Parcelable>?): Navigator {
extras.putParcelableArrayList(key, parcelable)
return this
}
fun <T : Parcelable> withSparseParcelableArray(key: String?, parcelable: SparseArray<T>?): Navigator {
extras.putSparseParcelableArray(key, parcelable)
return this
}
fun withSerializable(key: String?, serializable: Serializable?): Navigator {
extras.putSerializable(key, serializable)
return this
}
@TargetApi(21)
fun withSize(key: String?, size: Size?): Navigator {
extras.putSize(key, size)
return this
}
@TargetApi(21)
fun withSizeF(key: String?, sizeF: SizeF?): Navigator {
extras.putSizeF(key, sizeF)
return this
}
fun withBundle(key: String?, bundle: Bundle?): Navigator {
extras.putBundle(key, bundle)
return this
}
/**
* animation
*/
fun withTransition(activity: Activity, enterAnim: Int, exitAnim: Int): Navigator {
this.activity = activity
this.enterAnim = enterAnim
this.exitAnim = exitAnim
return this
}
/**
* Flags
*/
fun withFlags(@FlagInt flag: Int): Navigator {
flags = flags or flag
return this
}
fun withOptions(options: Bundle?): Navigator {
this.options = options
return this
}
/**
* callback
*/
fun onBefore(block: ((navigator: Navigator) -> Unit)?): Navigator {
this.onBeforeCallback = block
return this
}
fun onArrived(block: ((navigator: Navigator) -> Unit)?): Navigator {
this.onArrivedCallback = block
return this
}
fun onNotFound(block: ((navigator: Navigator) -> Unit)?): Navigator {
this.onNotFoundCallback = block
return this
}
fun onIntercept(block: ((navigator: Navigator) -> Unit)?): Navigator {
this.onInterceptCallback = block
return this
}
/**
* startActivityForResult
*/
@JvmOverloads
fun withRequestCode(activity: Activity, requestCode: Int, options: Bundle? = null): Navigator {
this.activity = activity
this.requestCode = requestCode
this.options = options
return this
}
@JvmOverloads
fun withRequestCode(fragment: Fragment, requestCode: Int, options: Bundle? = null): Navigator {
this.fragment = fragment
this.requestCode = requestCode
this.options = options
return this
}
@JvmOverloads
fun withRequestCode(
fragment: androidx.fragment.app.Fragment,
requestCode: Int,
options: Bundle? = null
): Navigator {
fragmentV4 = fragment
this.requestCode = requestCode
this.options = options
return this
}
/**
* Initiate a routing navigation to the router.
*/
fun navigate(): Any? {
return Router.getInstance().navigator(this)
}
fun navigate(context: Context): Any? {
this.context = context
return Router.getInstance().navigator(this)
}
}
}
真正的路由类。
package com.midea.base.core.dofrouter.api.core
import android.app.Application
import android.app.Fragment
import android.content.Context
import com.midea.base.core.dofrouter.annotation.RouteType
import com.midea.base.core.dofrouter.annotation.data.RouteMetaData
import com.midea.base.core.dofrouter.annotation.utils.Consts
import com.midea.base.core.dofrouter.api.exceptions.HandleException
import com.midea.base.core.dofrouter.api.interfaces.IRouteInterceptor
import com.midea.base.core.dofrouter.api.handler.ActivityHandler
import com.midea.base.core.dofrouter.api.handler.FragmentHandler
import com.midea.base.core.dofrouter.api.handler.FragmentV4Handler
import com.midea.base.core.dofrouter.api.handler.RouteHandler
import com.midea.base.core.dofrouter.api.handler.UnknownRouteHandler
import com.midea.base.core.dofrouter.api.interfaces.IInterceptorLoader
import com.midea.base.core.dofrouter.api.interfaces.IRouteLoader
import com.midea.base.core.dofrouter.api.table.RouteTable
import com.midea.base.core.dofrouter.api.utils.ClassUtils
import com.midea.base.core.dofrouter.api.utils.Logger
/**
* 真的路由器
* 具体实现寻址、拦截器执行等逻辑
*/
internal class Router private constructor() {
internal lateinit var context: Context
internal object Inner {
val instance = Router()
}
companion object {
fun getInstance() = Inner.instance
fun init(context: Context) {
getInstance().context = context as? Application ?: context.applicationContext
getInstance().loadRouteTable()
}
}
var registerByPlugin = false
/**
* 加载路由表
*/
private fun loadRouteTable() {
loadRouterMap()
if (!registerByPlugin) {
Logger.d("Load RouteTable by reflect")
val routerMap = ClassUtils.getFileNameByPackageName(context, Consts.PACKAGE)
if (routerMap != null && routerMap.isNotEmpty()) {
routerMap.forEach { className ->
when {
className.startsWith("${Consts.PACKAGE}.${Consts.ROUTE_LOADER_NAME}") -> {
Logger.d("Load route: $className")
(loadClassForName(className)?.newInstance() as? IRouteLoader)?.loadInto(RouteTable.routes)
}
className.startsWith("${Consts.PACKAGE}.${Consts.INTERCEPTOR_LOADER_NAME}") -> {
Logger.d("Load interceptor: $className")
(loadClassForName(className)?.newInstance() as? IInterceptorLoader)?.loadInto(RouteTable.interceptors)
}
}
}
}
} else {
Logger.d("Load RouteTable by Auto-Register")
}
}
private fun loadRouterMap() {
registerByPlugin = false
}
private fun registerRouteRoot(routeLoader: IRouteLoader?) {
markRegisteredByPlugin()
routeLoader?.let {
Logger.d("Load route: ${it.javaClass.simpleName}")
it.loadInto(RouteTable.routes)
}
}
private fun registerInterceptor(interceptorLoader: IInterceptorLoader?) {
markRegisteredByPlugin()
interceptorLoader?.let {
Logger.d("Load interceptor: ${it.javaClass.simpleName}")
it.loadInto(RouteTable.interceptors)
}
}
private fun markRegisteredByPlugin() {
if (!registerByPlugin) {
registerByPlugin = true
}
}
private fun loadClassForName(className: String): Class<*>? {
return try {
Class.forName(className)
} catch (e: ClassNotFoundException) {
null
}
}
/**
* 发起路由请求
* 若对应路由为fragment则返回,其余情况返回null
*/
fun navigator(navigator: DOFRouter.Navigator): Any? {
val isIntercept = isIntercept(navigator)
if (isIntercept) {
navigator.onInterceptCallback?.invoke(navigator)
return null
}
val map = addressingComponent(navigator)
if (map.isEmpty()) {
Logger.w("${navigator.path} Not Found!")
navigator.onNotFoundCallback?.invoke(navigator)
return null
}
val handlers = createRouteHandler(map)
handlers.forEach {
navigator.onBeforeCallback?.invoke(navigator)
try {
val result = it.handle(context, navigator)
navigator.onArrivedCallback?.invoke(navigator)
if (result is Fragment || result is androidx.fragment.app.Fragment) {
return result
}
} catch (e: HandleException) {
e.printStackTrace()
navigator.onNotFoundCallback?.invoke(navigator)
}
}
return null
}
/**
* 开始寻址,从路由表中获取对应路径的路由
* @return Map<String, RouteMetadata> key: 路由路径 value: 路由元数据
*/
private fun addressingComponent(navigator: DOFRouter.Navigator): Map<String, RouteMetaData> {
Logger.d("Addressing >> ${navigator.path}")
return RouteTable.routes.filterKeys {
RouteTable.matchers.find { matcher ->
matcher.match(it, navigator.path)
} != null
}
}
/**
* 将Map<String, RouteMetadata>转化为List<AbsRouteHandler>并且按照优先级进行排序
* @return 返回路由处理者列表
*/
private fun createRouteHandler(map: Map<String, RouteMetaData>): List<RouteHandler> {
return map.map { createHandler(it.value) }
.sortedWith(Comparator { o1, o2 -> o1.routeMetadata.priority - o2.routeMetadata.priority })
}
/**
* 根据不同RouteType返回不同处理者
*/
internal fun createHandler(routeMetaData: RouteMetaData): RouteHandler {
return when (routeMetaData.routeType) {
RouteType.ACTIVITY -> ActivityHandler(routeMetaData)
RouteType.FRAGMENT -> FragmentHandler(routeMetaData)
RouteType.FRAGMENT_V4 -> FragmentV4Handler(routeMetaData)
else -> UnknownRouteHandler(routeMetaData)
}
}
/**
* 执行拦截器
* @return true:路由请求被拦截 false:该请求未被拦截
*/
private fun isIntercept(navigator: DOFRouter.Navigator): Boolean {
return RouteTable.interceptors.asSequence().find {
try {
val clazz = it.value.clazz
val interceptor = clazz.newInstance() as IRouteInterceptor
return@find if (interceptor.intercept(context, navigator)) {
Logger.d("Intercept by ${clazz.simpleName}")
true
} else {
false
}
} catch (e: ClassNotFoundException) {
e.printStackTrace()
} catch (e: ClassCastException) {
e.printStackTrace()
}
return@find false
} != null
}
}
对应工具类。
package com.midea.base.core.dofrouter.api.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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.midea.base.core.dofrouter.annotation.utils.Consts;
import dalvik.system.DexFile;
/**
* Scanner, find out class with any conditions, copy from google source code.
*
* @author 正纬 <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
* @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 Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
final Set<String> classNames = new HashSet<>();
List<String> paths = getSourcePaths(context);
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
for (final String path : paths) {
DefaultPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
DexFile dexfile = null;
try {
if (path.endsWith(EXTRACTED_SUFFIX)) {
//NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
dexfile = new DexFile(path);
}
Enumeration<String> dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (Throwable ignore) {
Log.e(Consts.TAG, "Scan map file in dex files made error.", ignore);
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
parserCtl.countDown();
}
}
});
}
parserCtl.await();
Log.d(Consts.TAG, "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<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
File sourceApk = new File(applicationInfo.sourceDir);
List<String> 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;
}
/**
* Get instant run dex path, used to catch the branch usingApkSplits=false.
*/
private static List<String> tryLoadInstantRunDexFile(ApplicationInfo applicationInfo) {
List<String> instantRunSourcePaths = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && null != applicationInfo.splitSourceDirs) {
// add the split apk, normally for InstantRun, and newest version.
instantRunSourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs));
Log.d(Consts.TAG, "Found InstantRun support");
} else {
try {
// This man is reflection from Google instant run sdk, he will tell me where the dex files go.
Class pathsByInstantRun = Class.forName("com.android.tools.fd.runtime.Paths");
Method getDexFileDirectory = pathsByInstantRun.getMethod("getDexFileDirectory", String.class);
String instantRunDexPath = (String) getDexFileDirectory.invoke(null, applicationInfo.packageName);
File instantRunFilePath = new File(instantRunDexPath);
if (instantRunFilePath.exists() && instantRunFilePath.isDirectory()) {
File[] dexFile = instantRunFilePath.listFiles();
for (File file : dexFile) {
if (null != file && file.exists() && file.isFile() && file.getName().endsWith(".dex")) {
instantRunSourcePaths.add(file.getAbsolutePath());
}
}
Log.d(Consts.TAG, "Found InstantRun support");
}
} catch (Exception e) {
Log.e(Consts.TAG, "InstantRun support error, " + e.getMessage());
}
}
return instantRunSourcePaths;
}
/**
* 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(Consts.TAG, "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;
}
}
}
不同目标跳转逻辑不同。
package com.midea.base.core.dofrouter.api.handler
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import androidx.core.app.ActivityCompat
import com.midea.base.core.dofrouter.annotation.data.RouteMetaData
import com.midea.base.core.dofrouter.api.core.DOFRouter
import com.midea.base.core.dofrouter.api.exceptions.RouteNotFoundException
import com.midea.base.core.dofrouter.api.utils.Logger
internal class ActivityHandler(routeMetaData: RouteMetaData): RouteHandler(routeMetaData) {
override fun handle(context: Context, navigator: DOFRouter.Navigator): Any? {
Logger.d("Handle by ActivityHandler, route to ${routeMetadata.clazz.simpleName}")
val intent = Intent()
val component = ComponentName(context, routeMetadata.clazz)
intent.setComponent(component)
.addFlags(navigator.flags)
.putExtras(navigator.extras)
try {
when {
navigator.activity != null -> {
navigator.activity!!.startActivityForResult(
intent,
navigator.requestCode,
navigator.options
)
if (navigator.enterAnim > -1 || navigator.exitAnim > -1) {
navigator.activity?.overridePendingTransition(navigator.enterAnim, navigator.exitAnim)
}
}
navigator.fragment != null -> {
navigator.fragment?.startActivityForResult(
intent,
navigator.requestCode,
navigator.options
)
}
navigator.fragmentV4 != null -> {
navigator.fragmentV4?.startActivityForResult(
intent,
navigator.requestCode,
navigator.options
)
}
navigator.context is Activity -> {
(navigator.context as Activity).startActivityForResult(
intent,
navigator.requestCode,
navigator.options
)
if (navigator.enterAnim > -1 || navigator.exitAnim > -1) {
(navigator.context as Activity).overridePendingTransition(navigator.enterAnim, navigator.exitAnim)
}
}
else -> {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val tmpContext = navigator.context ?: context
ActivityCompat.startActivity(tmpContext, intent, navigator.options)
if (tmpContext is Activity && (navigator.enterAnim > -1 || navigator.exitAnim > -1)) {
tmpContext.overridePendingTransition(navigator.enterAnim, navigator.exitAnim)
}
}
}
} catch (e: ActivityNotFoundException) {
throw RouteNotFoundException(e)
}
return null
}
}
package com.midea.base.core.dofrouter.api.handler
import android.app.Fragment
import android.content.Context
import com.midea.base.core.dofrouter.annotation.data.RouteMetaData
import com.midea.base.core.dofrouter.api.core.DOFRouter
import com.midea.base.core.dofrouter.api.exceptions.HandleException
import com.midea.base.core.dofrouter.api.exceptions.RouteNotFoundException
import com.midea.base.core.dofrouter.api.utils.Logger
import java.lang.ClassCastException
internal class FragmentHandler(routeMetaData: RouteMetaData): RouteHandler(routeMetaData) {
override fun handle(context: Context, navigator: DOFRouter.Navigator): Any? {
Logger.d("Handle by FragmentHandler, route to ${routeMetadata.clazz.simpleName}")
try {
val clz = routeMetadata.clazz
val fragment = clz.newInstance() as Fragment
fragment.arguments = navigator.extras
return fragment
} catch (e: ClassNotFoundException) {
throw RouteNotFoundException(e)
} catch (e: ClassCastException) {
throw HandleException(e)
}
}
}
package com.midea.base.core.dofrouter.api.handler
import android.content.Context
import androidx.fragment.app.Fragment
import com.midea.base.core.dofrouter.annotation.data.RouteMetaData
import com.midea.base.core.dofrouter.api.core.DOFRouter
import com.midea.base.core.dofrouter.api.exceptions.HandleException
import com.midea.base.core.dofrouter.api.exceptions.RouteNotFoundException
import com.midea.base.core.dofrouter.api.utils.Logger
internal class FragmentV4Handler(routeMetaData: RouteMetaData): RouteHandler(routeMetaData) {
override fun handle(context: Context, navigator: DOFRouter.Navigator): Any? {
Logger.d("Handle by FragmentV4Handler, route to ${routeMetadata.clazz.simpleName}")
try {
val clz = routeMetadata.clazz
val fragment = clz.newInstance() as Fragment
fragment.arguments = navigator.extras
return fragment
} catch (e: ClassNotFoundException) {
throw RouteNotFoundException(e)
} catch (e: ClassCastException) {
throw HandleException(e)
}
}
}
package com.midea.base.core.dofrouter.api.handler
import android.content.Context
import com.midea.base.core.dofrouter.annotation.data.RouteMetaData
import com.midea.base.core.dofrouter.api.core.DOFRouter
import com.midea.base.core.dofrouter.api.utils.Logger
internal class UnknownRouteHandler(routeMetaData: RouteMetaData): RouteHandler(routeMetaData) {
override fun handle(context: Context, navigator: DOFRouter.Navigator) {
Logger.w("Unknown route : ${routeMetadata.clazz.name}")
}
}
package com.midea.base.core.dofrouter.api.table
import com.midea.base.core.dofrouter.annotation.data.InterceptorMetaData
import com.midea.base.core.dofrouter.annotation.data.RouteMetaData
import com.midea.base.core.dofrouter.api.matcher.DefaultMatcher
import com.midea.base.core.dofrouter.api.matcher.PathMatcher
import java.util.*
import kotlin.collections.HashMap
internal object RouteTable {
internal val routes = HashMap<String, RouteMetaData>()
internal val matchers = mutableListOf<PathMatcher>(DefaultMatcher)
internal val interceptors = TreeMap<Int, InterceptorMetaData>()
fun clear() {
routes.clear()
matchers.clear()
interceptors.clear()
}
}
package com.midea.base.core.dofrouter.api.handler
import android.content.Context
import com.midea.base.core.dofrouter.annotation.data.RouteMetaData
import com.midea.base.core.dofrouter.api.core.DOFRouter
import com.midea.base.core.dofrouter.api.exceptions.HandleException
internal abstract class RouteHandler(val routeMetadata: RouteMetaData) {
@Throws(HandleException::class)
abstract fun handle(context: Context, navigator: DOFRouter.Navigator): Any?
}
package com.midea.base.core.dofrouter.api.utils;
import android.util.Log;
import com.midea.base.core.dofrouter.annotation.utils.Consts;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Executors
*
* @author 正纬 <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
* @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 = INIT_THREAD_COUNT;
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 ArrayBlockingQueue<Runnable>(64),
new DefaultThreadFactory());
}
}
}
return instance;
}
private DefaultPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
Log.e(Consts.TAG, "Task rejected, too many task!");
}
});
}
/*
* 线程执行结束,顺便看一下有么有什么乱七八糟的异常
*
* @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) {
// Log.w(Consts.TAG, "Running task appeared exception! Thread [" + Thread.currentThread().getName() + "], because [" + t.getMessage() + "]\n" + TextUtils.formatStackTrace(t.getStackTrace()));
}
}
}
package com.midea.base.core.dofrouter.api.utils;
import androidx.annotation.NonNull;
import android.util.Log;
import com.midea.base.core.dofrouter.annotation.utils.Consts;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程池工厂类
*
* @author zhilong <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
* @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 = "Task pool No." + poolNumber.getAndIncrement() + ", thread No.";
}
public Thread newThread(@NonNull Runnable runnable) {
String threadName = namePrefix + threadNumber.getAndIncrement();
Log.i(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) {
Log.i(Consts.TAG, "Running task appeared exception! Thread [" + thread.getName() + "], because [" + ex.getMessage() + "]");
}
});
return thread;
}
}