| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> ARouter系列2:源码分析,html5移动开发框架 -> 正文阅读 |
|
[移动开发]ARouter系列2:源码分析,html5移动开发框架 |
super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_interceptor); } } 1.5、Test1Interceptor @Interceptor(priority = 7) public class Test1Interceptor implements IInterceptor { Context mContext; @Override public void process(Postcard postcard, InterceptorCallback callback) { if (MainActivity.AROUTER_PATH_TEST3.equals(postcard.getPath())){ // 这里的弹窗仅做举例,代码写法不具有可参考价值 final AlertDialog.Builder ab = new AlertDialog.Builder(MainActivity.getActivity()); ab.setCancelable(false); ab.setTitle(“温馨提醒”); ab.setMessage(“想要跳转到TestInterceptorActivity么?(触发了”/inter/test1"拦截器,拦截了本次跳转)"); ab.setNegativeButton(“继续”, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { callback.onContinue(postcard); } }); ab.setNeutralButton(“算了”, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { callback.onInterrupt(null); } }); ab.setPositiveButton(“加点料”, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { postcard.withString(“extra”, “我是在拦截器中附加的参数”); callback.onContinue(postcard); } }); MainLooper.runOnUiThread(new Runnable() { @Override public void run() { ab.create().show(); } }); }else { callback.onContinue(postcard); } } @Override public void init(Context context) { mContext = context; LogUtils.e(Test1Interceptor.class.getName()+" has init."); } } 1.6、其他 public class Person implements Parcelable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } protected Person(Parcel in) { name = in.readString(); age = in.readInt(); } @Override public String toString() { return “Person{” + “name=’” + name + ‘’’ + “, age=” + age + ‘}’; } public static final Creator CREATOR = new Creator() { @Override public Person createFromParcel(Parcel in) { return new Person(in); } @Override public Person[] newArray(int size) { return new Person[size]; } }; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); } } public class MainLooper extends Handler { private static MainLooper instance = new MainLooper(Looper.getMainLooper()); protected MainLooper(Looper looper) { super(looper); } public static MainLooper getInstance() { return instance; } public static void runOnUiThread(Runnable runnable) { if(Looper.getMainLooper().equals(Looper.myLooper())) { runnable.run(); } else { instance.post(runnable); } } } 2、源码分析 ====== ARouter是通过APT生成代码在框架内部进行操作,那么,项目编译生成的文件位置在那里? 既然生成了这些源码,我们就先随便点点看看这些都是啥? /**
public class ARouter G r o u p Group Groupapp implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/app/Test1Activity", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, “/app/test1activity”, “app”, null, -1, -2147483648)); atlas.put("/app/Test2Activity", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, “/app/test2activity”, “app”, new java.util.HashMap<String, Integer>(){{put(“test”, 10); put(“name”, 8); put(“age”, 3); }}, -1, -2147483648)); atlas.put("/app/Test3Activity", RouteMeta.build(RouteType.ACTIVITY, TestInterceptorActivity.class, “/app/test3activity”, “app”, null, -1, -2147483648)); } } /**
public class ARouter R o o t Root Rootapp implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put(“app”, ARouter G r o u p Group Groupapp.class); } } 具体源码大家可以自行查看,不过多粘贴了。 这里简简单单随便截图了APT生成的部分源码,是不是感觉跟上一篇文章使用到的代码很多相似性呐~比如拦截器的优先级是1、跳转匹配的路径也是一样的、跳转传递的参数、定义的组名等等。既然这么多一样的那肯定是在内部某部分进行封装使用,带着这个问题我们开始逐步分析。 首先,我们从该框架使用到的注解开始分析(因为注解是使用这个框架的起点) 2.1、注解分析 首先,我们知道要使用ARouter的首先需要在类的注释上面写上 @Route 这个注解,点进源码看看 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.CLASS) public @interface Route { String path(); String group() default “”; String name() default “”; int extras() default Integer.MIN_VALUE; int priority() default -1; } 使用该注解标注的类将被自动添加至路由表中。而且,ARouter 并非仅提供页面(Activity)的路由功能,还可以用来路由模块想要暴露给其他模块调用的接口。也就是说?@Route?不仅可用于 Activity 类,还可用于模块对外接口的实现类。那么具体它可以实现那些类型? 从上面的源码可以看到,除了拦截器,里面通过实现接口重写方法,方法里面都有一个RouteMeta,那么我们点进去RouteMeta这个类看看: /**
*/ public class RouteMeta { private RouteType type; // Type of route private Element rawType; // Raw type of route private Class<?> destination; // Destination private String path; // Path of route private String group; // Group of route private int priority = -1; // The smaller the number, the higher the priority private int extra; // Extra data private Map<String, Integer> paramsType; // Param type private String name; private Map<String, Autowired> injectConfig; // Cache inject config. public RouteMeta() { } } 注释的意思是:它包含基本路由信息。 RouteType,就是路由的类型。那么,这款路由框架的具体路由类型又有那些?带着这个疑问,点进RouteType?源码看看。 public enum RouteType { ACTIVITY(0, “android.app.Activity”), SERVICE(1, “android.app.Service”), PROVIDER(2, “com.alibaba.android.arouter.facade.template.IProvider”), CONTENT_PROVIDER(-1, “android.app.ContentProvider”), BOARDCAST(-1, “”), METHOD(-1, “”), FRAGMENT(-1, “android.app.Fragment”), UNKNOWN(-1, “Unknown route type”); } 首先这是一个枚举,这些就是框架可以具体使用到的路由类型 说完Route 这个注解,我们在来看看@Interceptor?拦截器注解, @Target({ElementType.TYPE}) @Retention(RetentionPolicy.CLASS) public @interface Interceptor { /**
*/ int priority(); /**
*/ String name() default “Default”; } 哦,我们发现拦截器的注解就2个方法,第一个是定义优先级的,第二个就是拦截器的名字。 接着,@Autowired 注解: @Target({ElementType.FIELD}) @Retention(RetentionPolicy.CLASS) public @interface Autowired { // Mark param’s name or service name. String name() default “”; // If required, app will be crash when value is null. // Primitive type wont be check! boolean required() default false; // Description of the field String desc() default “”; } 我们知道这个注解是界面跳转时参数传递用的。Activity 中使用该注解标志的变量,会在页面被路由打开的时候自动赋予传递的参数值。 2.2、初始化分析 我们知道,ARouter框架使用的第一个步骤,是要先初始化,也就是调用:ARouter.init( Application.this );这个API,那么,它的初始化究竟是做了那些东西?我们先点进源码看看: /**
*/ 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.”); } } 那么,实际上它调用的是绿色矩形内的代码,绿色矩形内有两个初始化,一个是?_ARouter.init(application),还有一个是 _ARouter.afterInit( )。 我们首先点进 **_ARouter . init()**看看, A:红色箭头的是类注释,翻译过来就是:ARouter核心( 外观模式 ) B:绿色箭头代表的是一个线程池,对线程池概念不是很清楚的朋友可以点进?必须要理清的Java线程池?先了解一下 C:蓝色箭头代表的是 这个_ARouter init()实际是调用的LogisticsCenter里面的init方法。 首先,什么是外观模式? 简单点理解就是,通过创建一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以通过调用外观类的方法来调用内部子系统中所有方法。大概意思就是这样,想深入理解的话可以自行查阅资料。 其次,这个线程池做了什么功能? 点进去DefaultPoolExecutor这个类看看: 由源码得知就是创建了一个数组阻塞队列的线程池 最后,我们点进LogisticsCenter,看看里面的init方法执行了什么操作(因为源码太长,我就分别截图) 图一: 图二: 通过LogisticsCenter - init(1)这幅源码图可以得知:如果是Debug模式,则执行从if代码块; 如果是Release模式:通过PackageUtils.isNewVersion(context)判断当前应用是否是第一次启动。接着,获取arouter-compiler生成的文件,然后将该文件,存储在sp中,下次启动应用的时候,直接从sp缓存中读取。 然后,在LogisticsCenter - init(2)红色矩形的代码块可以得知:首先遍历arouter-compiler生成的文件,将他们按照类型分别存储到Warehouse的对应字段中。也就是,如果类名前缀符合文件拼接规则,比如为com.alibaba.android.arouter.routes.ARouter$$Root的文件,就将其添加到具体的Warehouse里面的集合中。也就是对应的这里 那么,这个文件拼接规则(也就是字符串拼接)是? Warehouse又是什么?点进源码看看 其中,Warehouse的类注释写的非常好,翻译过来就是:路由元数据和其他数据的存储。这个类本质就是路由文件映射表。里面提供了各种HashMap集合(Map不允许重复的key),去存储SP存储的值。 我们再看看这里存储拦截器的集合,UniqueKeyTreeMap,这个类是存储拦截器的,我们看看它做了什么? 原来,这个UniqueKeyTreeMap,就是继承了TreeMap,哦,我们知道TreeMap是一种有序的集合(底层帮我们排序)所以数值越小,它就优先添加拦截器。这样也就解释了为什么拦截器属性值设置的越低,优先级越高。 综上,针对?_ARouter init( )?这个初始化的源码我们可以得知以下: A:初始化这一操作,表面上是_ARouter ,实则是LogisticsCenter 在帮我们管理逻辑 B:LogisticsCenter 内部通过先存储SP,然后遍历、匹配(满足条件则添加到具体的集合中,按照文件的前缀不同,将他们添加到路由映射表Warehouse的groupsIndex、interceptorsIndex、providersIndex?中) C:具体的路由清单是Warehouse ,不仅保存实例,还给我们提供了缓存。也就是说?同一个目标(RouteMeta、IProvider、IInterceptor)仅会在第一次使用的时候创建一次,然后缓存起来。后面都是直接使用的缓存。 初始化还有一个API,_ARouter.afterInit( ) ; 这个API在上面的截图也有,那么这个API是用来干什么的?我们点进去看看。 首先,它实例化了一个InterceptorService。这里的InterceptorService下面会说。 这里的build方法,最终返回的是一个Postcard对象: ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81NDE3NDMwLTBiMjAxNjE4ZmUyZ
mRiOGMucG5n?x-oss-process=image/format,png) 从源码可以得知,实际上它返回的却是,_ARouter.getInstance().build(path)这个方法,这个方法是一个方法重载(一般用的最多的就是这一个,也就是默认分组,不进行自定义分组),跟进去看看(源码很长,分为以下两个截图): 发现这里有一个PathReplaceService(也就是红色矩形),这个PathReplaceService又是什么,点进去看看 这个类注释翻译过来就是:预处理路径。这个接口是IProvider的子类 让我们在看回绿色箭头,其中,navigation(clazz)这种方式是属于根据类型查找,而build(path)是根据名称进行查找。如果应用中没有实现PathReplaceService这个接口,则pService=null。PathReplaceService可以对所有的路径进行预处理,然后返回一个新的值(返回一个新的String和Uri)。绿色箭头有一个extractGroup()方法,点进去看看: extractGroup(path)这个方法,核心逻辑是红色矩形内的代码,这个方法主要是获取分组名称。切割path字符串,默认为path中第一部分为组名。这就证明了如果我们不自定义分组,默认就是第一个分号的内容。 分析完了build,我们在看看navigation 继续点进源码看看,发现进入了_ARouter这个类里面的navigation方法: |
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 4:00:34- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |