路由的用途
使用路由是因为项目实现了组件化,组件化一般分三层,app主层,业务层,基础组件层,层层之间是向下依赖,平级之间不进行依赖,保证了分层隔离,避免代码的耦合。
路由分为三个级别:初级路由,中级路由,高级路由
初级路由
一:初级路由:通过map保存所有的activity页面,跳转时候通过key获取对应的Activity,然后进行跳转。
1)首先我们需要在基础层module里,写Router:
public class MyRouter {
private final static HashMap<String, Class<? extends Activity>> map
= new HashMap<>();
public static void add(String key, Class<? extends Activity> clzss) {
map.put(key, clzss);
}
public static void go(Activity activity, String key) {
Class<? extends Activity> clzss = map.get(key);
activity.startActivity(new Intent(activity, clzss));
}
}
2)然后在 Application 中,将所有的Activity添加到map中。app module 层可以获取所有业务层的Activity
private void initRouter() {
MyRouter.add("/demo/BBSActivity", BBSActivity.class);
MyRouter.add("/demo/MainActivity", MainActivity.class);
}
3)使用
MyRouter.go(BBSActivity.this, "/demo/MainActivity");
非常的简单,唯一的缺点是需要手动的添加页面。
中级路由
中级路由:通过 apt + javaPoet 技术完成
技术文章支持:
https://blog.csdn.net/cpcpcp123/article/details/107891817
https://blog.csdn.net/LVXIANGAN/article/details/88350717
https://juejin.cn/post/6881116198889586701
https://www.jianshu.com/p/a2c900373cb8
apt: 是注解处理器,可以在编译器获取项目中的所有注解,然后对注解的地方进行加工。
javaPoet:是动态生成代码的技术,我们获取的注解,通过javapoet生成我们想要编写的代码。
首先我们需要创建三个moudel:
1)router_annotation:注解模块,用来声明需要路由的页面
2)router_compiler:注解处理器,为了拿到注解信息因此需要依赖各个模块,在编译期间根据各个模块声明的注解,利用javaPoet产生路由信息
3)router_core:存储各个module中声明过注解后,在项目编译期间生成的路由表信息
1)首先我们需要创建一个java moudel 的router_annotation,用来编写注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)//编译时注解
public @interface RouterPath {
String path();
}
2)然后创建一个java moudel 的 router_compiler,依赖 router_annotation 模块,用来写注解编译器,生成代码逻辑
第一步:在gradle中添加包依赖
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.squareup:javapoet:1.7.0' //这个库的主要作用就是帮助我们通过类调用的形式来生成代码
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
implementation project(path: ':router_annotation')
}
第二步:新建一个 AnnotationCompiler 类,依赖 AbstractProcessor 类,然后实现其中的方法:
@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {
private Filer filer; //生成文件对象(新建文件就靠他了)
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
//声明返回要处理哪个注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(RouterPath.class.getCanonicalName());
return types;
}
//支持Java版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//注解处理器的核心
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
....
return false;
}
}
第三步:编写注解处理器核心逻辑,核心逻辑包括两个部分,一个是获取 对应注解所在的类,然后拿到注解里面的值,然后放入map中。
// 获取项目中所有的RouterPath注解元素
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(RouterPath.class);
Map<String, String> map = new HashMap<>();
for (Element element : elementsAnnotatedWith){
//com.qiyi.comicflutter.TwoActivity
TypeElement typeElement = (TypeElement) element;
System.out.println("MALEI :" + typeElement.toString());
//@com.qiyi.router_animation.RouterPath(path=/demo/TwoActivity)
RouterPath annotation = typeElement.getAnnotation(RouterPath.class);
System.out.println("MALEI :" + annotation.toString());
// /demo/TwoActivity
String key = annotation.path(); //注解内部值
System.out.println("MALEI :" + key);
// com.qiyi.comicflutter.TwoActivity
String activityName = typeElement.getQualifiedName().toString(); //包名+类名
System.out.println("MALEI :" + activityName);
//( " /demo/TwoActivity " ,com.qiyi.comicflutter.TwoActivity );
map.put(key, activityName);
}
// 以上的代码的工作就是通过注解处理器,获取全局注解,然后将 注解内部的值做为key,类作为value,放入map中
第四步:动态生成代码(先随便生成一个类,看一下)
if(map.size() > 0){
Writer writer = null;
String utilsName = "ActivityUtils" ;
try {
JavaFileObject javaFileObject = filer.createSourceFile("com.qiyi.router_core." + utilsName);
writer = javaFileObject.openWriter(); //创建一个空类
//编写类中代码
writer.write("package com.qiyi.router_core;\n" +
"\n"
+ "import com.qiyi.router_core.ARouter;\n"
+ "import com.qiyi.router_core.IRoute;\n"
+ "\n"
+ "public class " + utilsName + " implements IRoute {\n"
+ "\n" +
" @Override\n" +
" public void putActivity() {"
+ "\n");
writer.write("}\n}");
} catch (IOException e) {
e.printStackTrace();
}finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
第五步:新建 router_core 执行我们动态生成文件的代码
1)创建接口IRouter
public interface IRoute {
void putActivity();
}
2)创建ARouter
public class ARouter {
private Map<String, Class<? extends Activity>> activityList;
private Application context;
private static class Holder {
private static ARouter aRouter = new ARouter();
}
private ARouter() {
activityList = new HashMap<>();
}
public static ARouter getInstance() {
return Holder.aRouter;
}
//activity对象存入List
public void putActivity(String path, Class<? extends Activity> clazz) {
if (path == null || clazz == null) {
return;
}
activityList.put(path, clazz);
}
public void init(Application application) {
this.context = application;
try {
Set<String> className = ClassUtils.getFileNameByPackageName(context,"com.qiyi.router_core");
for (String name : className) {
try {
Class<?> aClass = Class.forName(name);
//判断当前类是否是IRouter的实现类
if(IRoute.class.isAssignableFrom(aClass)){
IRoute iRoute= (IRoute) aClass.newInstance();
iRoute.putActivity();
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//跳转
public void jupmActivity(String path, Bundle bundle) {
Class<? extends Activity> aClass = activityList.get(path);
if (aClass == null) {
return;
}
Intent intent = new Intent().setClass(context, aClass);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (bundle != null) {
intent.putExtra("bundle", bundle);
}
context.startActivity(intent);
}
}
3)创建ClassUtils
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)) {
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("ARouter", "Scan map file in dex files made error.", ignore);
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
parserCtl.countDown();
}
}
});
}
parserCtl.await();
return classNames;
}
/**
* 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() + "'");
}
}
}
sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
/* if (ARouter.debuggable()) { // Search instant run support only debuggable
sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
}*/
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;
}
}
}
4)创建DedalutPoolExecuter
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) {
}
});
}
/*
* 线程执行结束,顺便看一下有么有什么乱七八糟的异常
*
* @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) {
}
}
}
5)创建 DefalutThreadFactory
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();
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) {
}
});
return thread;
}
}
使用,给每个module 添加依赖
implementation project(path: ':router_annotation')
implementation project(path: ':router_core')
annotationProcessor project(path: ':router_compiler') //注解处理器使用 annotationProcessor
build下,我们看到生成的代码: