手写ARouter路由框架

作者: Peakmain | 来源:发表于2019-08-01 13:53 被阅读27次

    这里我不阐述ARouter,组件化的作用和使用方式,大家可以自行百度。android组件化开发实战

    思路一

    我们可以让每个module去依赖同一个module(定义为arouter),之后新建一个类将每个activity添加到一个HashMap集合中,key是每个路径,value是类的名字

    public class ARouter {
        private Map<String, Class<? extends Activity>> activityList;
    
        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);
        }
    }
    

    新建一个接口IRoute(为什么定义接口,后面会说到)

    public interface IRoute {
        void putActivity();
    }
    

    此时我们可以让依赖aroute包的module(假设登录module,有个loginActivity)新建一个ActivityUtils工具类,此时工具类可以这么写

    public class ActivityUtils implements IRoute {
    
        @Override
        public void putActivity() {
            ARouter.getInstance().putActivity("/login/loginActivity", com.peakmain.loginmodule.LoginActivity.class);
        }
    }
    

    问题:每个module都需要写一个工具类,且每次新增一个activity就需要去写一行ARouter.getInstance().putActivity()方法,且当删除一个activity的时候还需要去这个类下删除相关的activity

    思路二

    • 目的:我们不用自己去写ActivityUtils工具类,我们让程序自动生成
    • 技术:用注解+编译器方式
    • 首先新建两个module:animation(注解),animation-compiler(编译器)

    新建注解Path

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)//编译时注解
    public @interface Path {
        String path();
    }
    

    animation-compile需要添加依赖

    implementation 'com.google.auto.service:auto-service:1.0-rc3'
    

    注解生成器代码

    @AutoService(Processor.class)
    public class AnnotationCompiler extends AbstractProcessor {
        //生成文件对象
        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(Path.class.getCanonicalName());
            return types;
        }
    
        //支持Java版本
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return processingEnv.getSourceVersion();
        }
    
        //注解处理器的核心
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //拿到该模块所有path注解的节点
            Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(Path.class);
            //结构化数据
            Map<String, String> map = new HashMap<>();
            for (Element element : elementsAnnotatedWith) {
                //实际就是类节点
                TypeElement typeElement = (TypeElement) element;
                Path annotation = typeElement.getAnnotation(Path.class);
                //读取到key
                String key = annotation.path();
                //包名+类名
                String activityName = typeElement.getQualifiedName().toString();
                map.put(key, activityName);
            }
            if (map.size() > 0) {
                //开始写文件
                Writer writer = null;
                String utilsName = "ActivityUtils" ;
                try {
                    JavaFileObject javaFileObject = filer.createSourceFile("com.peakmain.utils." + utilsName);
                    writer = javaFileObject.openWriter();
                    writer.write("package com.peakmain.utils;\n" +
                            "\n"
                            + "import com.peakmain.arouter.ARouter;\n"
                            + "import com.peakmain.arouter.IRoute;\n"
                            + "\n"
                            + "public class " + utilsName + " implements IRoute {\n"
                            + "\n" +
                            " @Override\n" +
                            " public void putActivity() {"
                            + "\n");
                    Iterator<String> iterator = map.keySet().iterator();
                    while (iterator.hasNext()) {
                        String path = iterator.next();
                        String value = map.get(path);
                        writer.write("ARouter.getInstance().putActivity(\"" + path + "\","
                                + value + ".class);\n");
                    }
                    writer.write("}\n}");
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (writer != null) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return false;
        }
    }
    

    代码看起来应该还是较简单,主要的目的就是生成工具类ActivityUtils相关代码
    之后需要每个module去依赖annmtion和annmtion-compiler

    思路三

    生成相关工具类代码之后,我们可以在之前ARouter类中写个跳转方法

        //跳转
        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);
        }
    

    此时还不能跳转,因为activityList还是空的,这时候我们需要通过指定包名,扫描下面所有的类名,继续在ARouter类中写

        private Context context;
    
        public void init(Application application) {
            this.context = application;
            try {
                Set<String> className = ClassUtils.getFileNameByPackageName(context,"com.peakmain.utils");
                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();
            }
        }
    
    

    这里我们就可以知道为什么要用一个接口,一、因为可能com.peakmain.utils下会有很多类。二、方便用于直接执行

    至于工具类大家可以直接拷贝ARouter里面的,这里我为了方便大家,我直接贴一份代码了
    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)) {
                                //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("ARouter", "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() + "'");
                    }
                }
            }
            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;
            }
        }
    }
    

    DefaultPoolExecutor

    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) {
            }
        }
    }
    

    线程池工厂类DefaultThreadFactory

    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;
        }
    }
    

    整体架构思路还是比较明白的,若有表诉不清楚的,欢迎大家留言

    相关文章

      网友评论

        本文标题:手写ARouter路由框架

        本文链接:https://www.haomeiwen.com/subject/bylqdctx.html