-
ARouter
2019-12-24 10:03:44更快捷的同步开发与更简单的单独调试,而ARouter的出现就是让组件间、模块间是实现完全的独立。 ARouter是:阿里巴巴自研路由框架,主要解决组件间、模块间的 界面跳转 问题。 今天用最简单的方式讲解Arouter的...1.前言
- 组件化或者模块化开发模式,已逐渐成为热浪的形式,使用这些模式可以让我们程序更容易的扩展、更方便的维护
更快捷的同步开发与更简单的单独调试,而ARouter
的出现就是让组件间、模块间是实现完全的独立。 ARouter
是:阿里巴巴自研路由框架,主要解决组件间、模块间的 界面跳转 问题。- 今天用最简单的方式讲解Arouter的使用与原理。
- 文章中实例 linhaojian的Github
2.目录
arouter目录.png
3.简介
arouter简介.png
4.原理
4.1 关系分析
-
从A界面跳转到B界面这个过程,我们看看arouter与界面间关系,如下图:
arouter与界面关系.png
- 1.注册
B界面将类的信息,通过key-value的形式,注册到arouter中。
- 2.查询
A界面将类信息与额外信息(传输参数、跳转动画等),通过key传递至arouter中,并查询对应需要跳转类的信息。
- 3.结合
将A界面类信息、参数与B界面的类信息进行封装结合。
- 4.跳转
将结合后的信息,使用startActivity实现跳转。
4.2 流程分析
-
A界面跳转到B界面,arouter做了以下工作:
arouter流程.png
从上图流程中,我们可以发现Arouter中原理:
1.通过apt技术利用注解编译时生成类,封装目标界面类的类信息。
2.在初始化时,把编译生成的类通过key-value的方式存储在arouter中。
3.发送操作者通过key获取到目标界面类的信息。
4.把发送操作者的信息与目标界面类信息进行结合或者关联在一起。
5.实现跳转功能。- 其实简单概括:将需要相互跳转的界面信息传递至arouter中存储关联 & 实现跳转。
5.使用
5.1 跳转界面不带参
- 发送跳转操作
// 1. 普通跳转 ARouter.getInstance().build("/test/activity").navigation();
- 目标界面
// 在支持路由的页面上添加注解(必选) // 这里的路径需要注意的是至少需要有两级,/xx/xx @Route(path = "/test/activity") public class YourActivity extend Activity { ... }
5.2 跳转界面带参
- 发送跳转操作
ARouter.getInstance().build("/test/1") .withLong("key1", 666L) .withString("key3", "888") .withSerializable("key4", new Test("Jack", "Rose")) .navigation();
- 目标界面
@Route(path = "/test/1") public class YourActivity extend Activity { //获取数据三种方式 //1.通过Autowired注解表明key & 需要在onCreate中调用ARouter.getInstance().inject(this);配合使用 @Autowired(name = "key1") public long data; //2.通过Autowired注解 & 将key1作为属性的名称 & 需要在onCreate中调用ARouter.getInstance().inject(this);配合使用 @Autowired() public long key1; //3.通过Bundle获取 getIntent().getExtras().getLong("key1") }
5.3 跳转界面带参(传递Object)
- 定义解析Obeject的SerializationService
/** * 处理传递参数中自定义的Object---》withObject */ @Route(path = "/custom/json") public class JsonSerializationService implements SerializationService { Gson gson; @Override public <T> T json2Object(String input, Class<T> clazz) { return gson.fromJson(input,clazz); } @Override public String object2Json(Object instance) { return gson.toJson(instance); } @Override public <T> T parseObject(String input, Type clazz) { return gson.fromJson(input,clazz); } @Override public void init(Context context) { gson = new Gson(); } }
- 发送跳转操作
ARouter.getInstance().build("/test/1") .withObejct("key4", new Test("Jack", "Rose")) .navigation();
- 目标界面
@Route(path = "/test/1") public class YourActivity extend Activity { ... SerializationService serializationService = ARouter.getInstance().navigation(SerializationService.class); serializationService.init(this); User obj = serializationService.parseObject(getIntent().getStringExtra("key4"), User.class); }
5.4 Uri跳转
- 界面配置
<activity android:name=".activity.SchameFilterActivity"> <!-- Schame --> <intent-filter> <data android:host="m.aliyun.com" android:scheme="arouter"/> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> </intent-filter> </activity>
- 发送跳转操作
Uri testUriMix = Uri.parse("arouter://m.aliyun.com/test/activity2"); ARouter.getInstance().build(testUriMix) .withString("key1", "value1") .navigation();
- 目标界面
@Route(path = "/test/activity2") public class Test2Activity extends AppCompatActivity { }
5.5 跳转结果监听
ARouter.getInstance() .build("/test/activity2") .navigation(this, new NavCallback() { @Override public void onArrival(Postcard postcard) { } @Override public void onInterrupt(Postcard postcard) { Log.d("ARouter", "被拦截了"); } });
5.6 声明拦截器(拦截跳转过程,面向切面编程)
// 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查 // 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行 @Interceptor(priority = 8, name = "测试用拦截器") public class TestInterceptor implements IInterceptor { @Override public void process(Postcard postcard, InterceptorCallback callback) { ... callback.onContinue(postcard); // 处理完成,交还控制权 // callback.onInterrupt(new RuntimeException("我觉得有点异常")); // 觉得有问题,中断路由流程 // 以上两种至少需要调用其中一种,否则不会继续路由 } @Override public void init(Context context) { // 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次 } }
5.7 为目标页面声明更多信息
// 我们经常需要在目标页面中配置一些属性,比方说"是否需要登陆"之类的 // 可以通过 Route 注解中的 extras 属性进行扩展,这个属性是一个 int值,换句话说,单个int有4字节,也就是32位,可以配置32个开关 // 剩下的可以自行发挥,通过字节操作可以标识32个开关,通过开关标记目标页面的一些属性,在拦截器中可以拿到这个标记进行业务逻辑判断 @Route(path = "/test/activity", extras = Consts.XXXX)
5.8 依赖注入解耦
- 注册需要依赖注入的对象
@Route(path = "/provider/testP") public class TestProvider implements IProvider { @Override public void init(Context context) { } public String test(){ return ("Hello Provider!"); } }
- 获取对象 & 使用
ARouter.getInstance().navigation(TestProvider.class).test();
6.总结
- 到此,
ARouter
就讲解完毕。
作者:Linhaojian
链接:https://www.jianshu.com/p/8098961bd30c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 - 组件化或者模块化开发模式,已逐渐成为热浪的形式,使用这些模式可以让我们程序更容易的扩展、更方便的维护
-
Search for ARouter Helper in the Android Studio plugin market, or directly download the arouter-idea-plugin zip installation package listed in the Latest version above the documentation, after ...
-
ARouterDemo
2017-04-25 17:31:18该demo讲解了ARouter的简单使用 -
ARouterDemo源码
2020-10-20 15:11:40组件化开发推荐目前比较流行的ARouter框架,ARouter是由阿里开发团队开源的组件化框架,目前应用比较多,ARouter经过多年广大开发者测验并改进已经比较完善,选择阿里团队一直有维护更新的ARouter更稳妥一些。 -
ARouter浅析
2020-08-18 00:54:25ARouter ARouter是:阿里巴巴自研路由框架,主要解决组件间、模块间的 界面跳转 问题。 基本使用 首先按步骤引入这个库ARouter ARouter.getInstance() .build("/module_second/second") .withString("data", ...ARouter
ARouter是:阿里巴巴自研路由框架,主要解决组件间、模块间的 界面跳转 问题。
原理
- 使用apt在编译阶段生成代码
- 分组+按需加载 路由列表
- 内部使用Intent进行跳转
原理解析
//创建的第一个使用Router注解的Activity @Route(path = "/module_main/two") public class TwoActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_two); Toast.makeText(this, getLocalClassName(), Toast.LENGTH_SHORT).show(); } } //第二个 @Route(path = "/module_comm/comm") public class CommActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_comm); Toast.makeText(this, getLocalClassName(), Toast.LENGTH_SHORT).show(); } } //第三个 @Route(path = "/module_second/second") public class SecondActivity extends AppCompatActivity { @Autowired String data; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ARouter.getInstance().inject(this); TextView textView = new TextView(this); textView.setText(data); setContentView(textView); } }
这三个类分别在不同的module里面
接下来我们编译一下这个项目,看都生成了哪些代码
//第一个module public class ARouter$$Group$$module_main implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/module_main/two", RouteMeta.build(RouteType.ACTIVITY, TwoActivity.class, "/module_main/two", "module_main", null, -1, -2147483648)); } } public class ARouter$$Providers$$app implements IProviderGroup { @Override public void loadInto(Map<String, RouteMeta> providers) { } } public class ARouter$$Root$$app implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("module_main", ARouter$$Group$$module_main.class); } } //第二个module public class ARouter$$Group$$module_comm implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/module_comm/comm", RouteMeta.build(RouteType.ACTIVITY, CommActivity.class, "/module_comm/comm", "module_comm", null, -1, -2147483648)); } } public class ARouter$$Providers$$module_comm implements IProviderGroup { @Override public void loadInto(Map<String, RouteMeta> providers) { } } public class ARouter$$Root$$module_comm implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("module_comm", ARouter$$Group$$module_comm.class); } } //第三个module public class ARouter$$Group$$module_second implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/module_second/second", RouteMeta.build(RouteType.ACTIVITY, SecondActivity.class, "/module_second/second", "module_second", new java.util.HashMap<String, Integer>(){{put("data", 8); }}, -1, -2147483648)); } } public class ARouter$$Providers$$module_second implements IProviderGroup { @Override public void loadInto(Map<String, RouteMeta> providers) { } } public class ARouter$$Root$$module_second implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("module_second", ARouter$$Group$$module_second.class); } } //这里是给用Autowired注解的变量赋值 public class SecondActivity$$ARouter$$Autowired implements ISyringe { private SerializationService serializationService; @Override public void inject(Object target) { serializationService = ARouter.getInstance().navigation(SerializationService.class); SecondActivity substitute = (SecondActivity)target; substitute.data = substitute.getIntent().getStringExtra("data"); } }
我们可以看到基本每个module至少会生成三个类
ARouter$$Providers$$***
是空实现暂时不管它,我们看下ARouter$$Group$$Comm
public class ARouter$$Group$$module_comm implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/module_comm/comm", RouteMeta.build(RouteType.ACTIVITY, CommActivity.class, "/module_comm/comm", "module_comm", null, -1, -2147483648)); } }
这里可以看到在loadInto中是将用Router注解的类以path的值为key,类的class为value存在了Map中,
ps所有@Router注解path值斜杠后第一个值相同的类都会在同一个类中被管理起来
public class ARouter$$Root$$module_comm implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("module_comm", ARouter$$Group$$module_comm.class); } }
这里将Router注解的path的斜杠后的第一个值为key,将
ARouter$$Group$$module_comm.class
为value存在了map中,这样的话我们是不是可以认为ARouter会将Router注解path斜杠后的第一个值相同的类分为了一个组,使用apt生成这个组的管理类,也就是我们现在的ARouter$$Group$$module_comm
在这里我们了解到ARouter是怎么进行分组的,接下来我们看下ARouter是怎么获取到我们的这些类的
ARouter.openLog(); // 打印日志 ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) ARouter.init(getApplication()); // 尽可能早,推荐在Application中初始化
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."); } } protected static synchronized boolean init(Application application) { mContext = application; LogisticsCenter.init(mContext, executor); logger.info(Consts.TAG, "ARouter init success!"); hasInit = true; mHandler = new Handler(Looper.getMainLooper()); return true; } //代码有删减 public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { tr { if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) { logger.info(TAG, "Run with debug mode or new install, rebuild router map."); // These class was generated by arouter-compiler. routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE); if (!routerMap.isEmpty()) { context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply(); } PackageUtils.updateVersion(context); // Save new version name when router map update finishes. } else { logger.info(TAG, "Load router map from cache."); routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>())); } logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms."); startInit = System.currentTimeMillis(); for (String className : routerMap) { 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(Warehouse.groupsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { // Load interceptorMeta ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) { // Load providerIndex ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex); } } } } catch (Exception e) { throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]"); } }
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
我们直接看到这一行public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException { final Set<String> classNames = new HashSet<>(); //拿到了apk安装之后的路径 List<String> paths = getSourcePaths(context); 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(); //便利dex下的类 while (dexEntries.hasMoreElements()) { String className = dexEntries.nextElement(); //找到包名是com.alibaba.android.arouter.routes开头的类 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) { } } } } }); } return classNames; }
这里传进来的
packgeName = com.alibaba.android.arouter.routes
这个函数执行完就能拿到之前我们看到的几个类.因为我们编译之后生成的类的包名恰好是com.alibaba.android.arouter.routes
接下来我们继续看到
LogisticsCenter类的init函数
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { ... for (String className : routerMap) { if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) { ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { ((IInterceptorGroup(Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex); } ... }
这里就是拿到apt生成的类并且调用他们的
loadInto()
当然这里还没有调用具体某个组loadInto所以实际path与类的映射还没加载到内存,这样做达到了节省内存的效果,这里把每个组的管理类的class添加到了Warehouse.groupsIndex
中,后续我们要取出具体组的映射关系也是相当简单的事情到这里ARouter的
init()
事情已经做完了我们在看一下在哪里才会取出具体组的映射呢ARouter.getInstance() .build("/module_second/second") .navigation();
我们通常是这样进行页面的跳转那我们接下来就看下
navigation()
都做了些什么事protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { ... LogisticsCenter.completion(postcard); ... return _navigation(context, postcard, requestCode, callback); .... } public synchronized static void completion(Postcard postcard) { if (null == postcard) { throw new NoRouteFoundException(TAG + "No postcard!"); } RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath()); //当前的map中没有对应这个path的class if (null == routeMeta) { //取出这个path所在组的管理类 Class<? extends IRouteGroup> groupMeta = Warehouse.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(); //将这个组的所有数据添加到map iGroupInstance.loadInto(Warehouse.routes); Warehouse.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() + "]"); } //重新调这个函数,下次走else分子 completion(postcard); // Reload } } else { //填充postcard后续界面跳转需要用到 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<String, String> resultMap = TextUtils.splitQueryParameters(rawUri); Map<String, Integer> paramsType = routeMeta.getParamsType(); if (MapUtils.isNotEmpty(paramsType)) { // Set value by its type, just for params which annotation by @Param for (Map.Entry<String, Integer> params : paramsType.entrySet()) { setValue(postcard, params.getValue(), params.getKey(), resultMap.get(params.getKey())); } // Save params name which need auto inject. 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 implement IProvider Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination(); IProvider instance = Warehouse.providers.get(providerMeta); if (null == instance) { // There's no instance of this provider IProvider provider; try { provider = providerMeta.getConstructor().newInstance(); provider.init(mContext); Warehouse.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; case FRAGMENT: postcard.greenChannel(); // Fragment needn't interceptors default: break; } } }
从上面代码我们看到了ARouter的按需加载,我们在继续看下ARouter是怎么进行跳转的
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { final Context currentContext = null == context ? mContext : context; switch (postcard.getType()) { case ACTIVITY: // Build intent final Intent intent = new Intent(currentContext, postcard.getDestination()); intent.putExtras(postcard.getExtras()); // Set flags. int flags = postcard.getFlags(); if (-1 != flags) { intent.setFlags(flags); } else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } // Set Actions String action = postcard.getAction(); if (!TextUtils.isEmpty(action)) { intent.setAction(action); } // Navigation in main looper. runInMainThread(new Runnable() { @Override public void run() { startActivity(requestCode, currentContext, intent, postcard, callback); } }); break; case PROVIDER: return postcard.getProvider(); case BOARDCAST: case CONTENT_PROVIDER: case FRAGMENT: Class fragmentMeta = postcard.getDestination(); try { Object instance = fragmentMeta.getConstructor().newInstance(); if (instance instanceof Fragment) { ((Fragment) instance).setArguments(postcard.getExtras()); } else if (instance instanceof android.support.v4.app.Fragment) { ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras()); } return instance; } catch (Exception ex) { logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace())); } case METHOD: case SERVICE: default: return null; } return null; }
从这里我们可以看到其实ARouter内部跳转还是使用的Intent,那为什么我们使用组件化开发引用不到别的module中的类但是在ARouter中却可以,那是因为在进行apk的过程中会把所有类在一起进行打包,所以在组件化项目中我们使用ARouter既方便又能达到解耦合的效果岂不美哉
-
ARouter跳转
2019-09-28 16:50:09ARouter跳转介绍代码使用新建Application ,初始化ARouter在要跳转的Activity添加头注释被跳转的页面 介绍 ARouter是阿里开源的一款android路由框架。 通过路由进行界面跳转,区别于 Intent的显隐式跳转。在模块化的...介绍
ARouter是阿里开源的一款android路由框架。
通过路由进行界面跳转,区别于 Intent的显隐式跳转。在模块化的项目中,友好的解决了因模块互相依赖冲突,而界面互相跳转不了的问题,使用ARouter进行跳转,两个 library互相不依赖,也可以相互跳转。代码使用
在要使用ARouter的应用添加 build配置 和 依赖:
defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } }
依赖:
implementation (‘com.alibaba:arouter-api:1.4.1’)
annotationProcessor ‘com.alibaba:arouter-compiler:1.2.2’新建Application ,初始化ARouter
public class App extends Application { @Override public void onCreate() { super.onCreate(); // 打印日志 ARouter.openLog(); // 开启调试模式(如果在InstantRun(就是AndroidStudio2.0以后新增的一个可以减少很多编译时间的运行机制)模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) ARouter.openDebug(); // 初始化尽可能早,推荐在Application中初始化 ARouter.init(this); } }
在要跳转的Activity添加头注释
path:module名+activity名
@Route(path="/app/MainActivity") public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
需要跳转的地方 :
可以传值public void click(View view) { //点击事件内 跳转 ARouter.getInstance() .build("/modules1/Modules1MainActivity") .withString("name","a三") // 传递的值 .navigation(); }
被跳转的页面
同样需要头注释
TextView textView; @Autowired(name = "name") String name; @SuppressLint("WrongViewCast") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_modules1_main); //接收 ARouter.getInstance().inject(this); //name 接受完以后直接设置到控件上 textView=findViewById(R.id.edit_text); textView.setText(name); }
-
ARouterDemo.zip
2020-12-18 00:02:49是继承的阿里的ARouter,以及实现了模块化通过ARouter实现了之间的跳转 -
ARouter使用
2019-12-24 20:59:03ARouter 是阿里开源的,可以看成是 Android 平台中对页面、服务提供路由功能的中间件。 ARouter 直接翻译过来就是路由,可以用来映射页面关系,实现跳转相关的功能。在 Android 中,常被用来进行组件化通讯。 为...简介
ARouter 是阿里开源的,可以看成是 Android 平台中对页面、服务提供路由功能的中间件。
ARouter 直接翻译过来就是路由,可以用来映射页面关系,实现跳转相关的功能。在 Android 中,常被用来进行组件化通讯。
为什么要使用 ARouter
我们知道 Android 中默认为我们提供了跳转的功能,比如 startActivity,startService 等。那为什么还需要路由框架呢?在我看来,主要有以下几点吧:
-
在一些复杂的业务场景下(比如电商),灵活性比较强,很多功能都是运营人员动态配置的,比如下发一个活动页面,我们事先并不知道具体的目标页面,但如果事先做了约定,提前做好页面映射,便可以自由配置。
-
随着业务量的增长,客户端必然随之膨胀,开发人员的工作量越来越大,比如64K问题,比如协作开发问题。App一般都会走向组件化、插件化的道路,而组件化、插件化的前提就是解耦,那么我们首先要做的就是解耦页面之间的依赖关系。
ARouter 基本使用
ARouter github 地址: ARouter
ARouter 的接入也非常简单,一般来说,需要以下几个步骤。
第一步:配置 gradle 文件
android { defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [ moduleName : project.getName() ] } } } } dependencies { // Replace with the latest version compile 'com.alibaba:arouter-api:?' annotationProcessor 'com.alibaba:arouter-compiler:?' ... }
第二步:初始化 SDK
if (isDebug()) { // These two lines must be written before init, otherwise these configurations will be invalid in the init process ARouter.openLog(); // Print log ARouter.openDebug(); // Turn on debugging mode (If you are running in InstantRun mode, you must turn on debug mode! Online version needs to be closed, otherwise there is a security risk) } ARouter.init(mApplication); // As early as possible, it is recommended to initialize in the Application
第三步:添加 @Route 注解
// Add annotations on pages that support routing (required) // The path here needs to pay attention to need at least two levels : /xx/xx @Route(path = ARouterConstants.COM_ACTIVITY1) public class ActivityOne extends AppCompatActivity { ----- } public static final String COM_ACTIVITY1 = COM + "Activity1";
第四步:调用跳转的代码
ARouter.getInstance().build(ARouterConstants.COM_ACTIVITY1).navigation();
Activity 之间的跳转
假设我们现在要从 A 页面跳转到 B 页面,那我们要怎么办呢?
第一步:在目标页面使用 @Route 注解,并指定 path
@Route(path = ARouterConstants.COM_ACTIVITY1) public class ActivityOne extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_one); } }
第二步:调用 navigation 方法实现跳转
ARouter.getInstance().build(ARouterConstants.COM_ACTIVITY1).navigation();
这样,从 A 跳转到 B 的功能便实现了。
传递参数
Arouter 的跳转页非常简单,我们可以调用 PostCard 的 withX 等方法传递相应的参数
比如,我们想传递 String,可以调用 withString,想传递 int,可以调用 withInt ,想传递 Parceable 对象,可以调用 withParcelable。
ARouter.getInstance().build(ARouterConstants.COM_PARSE_ACTIVITY).withString(NAME,"jun") .withInt(AGE,1).withParcelable(PERSON,person).withObject(TEST_OBJ,testObj)
同时 ARouter 还支持传递 Object 对象,只需调用 withObject 方法,同时需要在我们的 moudle 下面增加相关的类。实际上,它的原理是通过将 object 转化成 String,然后存进 intent 中,在解析参数的时候,再通过相应的 key 去除 String,然后转化成 object。
如果你的项目使用的是 Gson,那可以使用下面的类
@Route(path = "/service/json") public class JsonServiceImpl implements SerializationService { private Gson mGson; @Override public void init(Context context) { mGson = new Gson(); } @Override public <T> T json2Object(String text, Class<T> clazz) { checkJson(); return mGson.fromJson(text, clazz); } @Override public String object2Json(Object instance) { checkJson(); return mGson.toJson(instance); } @Override public <T> T parseObject(String input, Type clazz) { checkJson(); return mGson.fromJson(input, clazz); } public void checkJson() { if (mGson == null) { mGson = new Gson(); } } }
如果你的项目使用的是阿里巴巴的 fastjson,那可以在你的项目增加该类。
@Route(path = "/service/json") public class JsonServiceImpl implements SerializationService { @Override public void init(Context context) { } @Override public <T> T json2Object(String text, Class<T> clazz) { return JSON.parseObject(text, clazz); } @Override public String object2Json(Object instance) { return JSON.toJSONString(instance); } @Override public <T> T parseObject(String input, Type clazz) { return JSON.parseObject(input, clazz); } }
解析参数
在 ActivityB 中获取参数有两种方式
- 一种是普通 Activity 那样 getIntent().getXXX,这里就不展开了
- 另外一种是使用 @Autowired 注解的方式,记得需要在接收参数地方ARouter.getInstance.inject(thhis)
@Route(path = ARouterConstants.COM_PARSE_ACTIVITY) public class ParseActivity extends AppCompatActivity { private static final String TAG = "ParseActivity"; @Autowired String name; @Autowired int age; @Autowired Person person; @Autowired TestObj mTestObj; @Autowired // 注意字段的名称必须与 withObject 的 key 一致 TestObj testObj; private android.widget.TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_parse); // 调用 inject 方法,如果传递过来的参数含有,这样使用 @Autowired 的会自动解析 ARouter.getInstance().inject(this); }
实现跳转并获取返回结果
在 activity 的跳转中,我们知道,我们可以用 startActivityForResult 来获取返回结果,那在 ARouter 中要怎么实现呢。
ARouter 中并没有提供这样的接口,但是我们可以采用曲线救国的原理,通过 Postcard 实现
Postcard postcard = ARouter.getInstance().build(ARouterConstants.COM_ACTIVITY_RESULT); LogisticsCenter.completion(postcard); Class<?> destination = postcard.getDestination();
这里得到的 destination 类就是我们要跳转的类,这样 fragment 的 startActivityForResult 就好办了
Intent intent = new Intent(getContext(),destination); startActivityForResult(intent,requestCode);
暴露服务
这里说到的服务不是 Android 四大组件中的 Service,这里的服务是接口开发的概念。即把部分功能或者业务封装起来。
比如,我们想调用某个接口,一般来说,可以这样做。
- 首先我们定义一个接口,并实现该接口,并采用 @Route 注解指定相应的 path:
public interface HelloService extends IProvider { String sayHello(String name); }
// 实现接口 @Route(path = ARouterConstants.SERVICE_HELLO, name = "test service") public class HelloServiceImpl implements HelloService { private Context mContext; @Override public String sayHello(String name) { Toast.makeText(mContext,this.getClass().getSimpleName()+": sayHello"+" "+name,Toast.LENGTH_SHORT).show(); return "hello, " + name; } @Override public void init(Context context) { mContext = context; } }
- 接着调用 Poatcard 的 navigation 方法获取到我们的实例:
HelloService helloService = (HelloService) ARouter.getInstance().build(ARouterConstants.SERVICE_HELLO).navigation(); String result = helloService.sayHello("xujun");
URL 跳转
我们先来看一下我们 URL 跳转的设计
从图中可以看到,我们是用一个中间跳转页面来管理所有 Activity 的跳转的,当接受到跳转指令的时候,中转 Activity 会进行相应的处理,从而跳转到响应的页面。
这样设计的好处是:
- 我们的目标 Activity(页面 A,页面 B 等)不需要对外暴露
中转 Activity:
public class UrlSchemeActivity extends AppCompatActivity { private static final String TAG = "UrlSchemeActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_url); // 直接通过ARouter处理外部Uri final Uri uri = getIntent().getData(); Log.i(TAG, "onCreate: uri=" + uri); ARouter.getInstance().build(uri).navigation(this, new NavCallback() { @Override public void onArrival(Postcard postcard) { finish(); } @Override public void onLost(Postcard postcard) { super.onLost(postcard); Log.i(TAG, "onLost: uri=" + uri); // Toast.makeText(UrlSchemeActivity.this,String.format("找不到可以处理该 // URI %s 的 Activity",uri),Toast.LENGTH_SHORT).show(); // 找不到的时候 finish 掉当前 activity finish(); } }); } }
当我们接收到跳转 uri 的时候,我们将它交给路由 ARouter,去进行分发。
接下来我们来看一下我们在 AndroidManifest 的配置
<activity android:name=".testAcivity.UrlSchemeActivity"> <intent-filter> <data android:host="m.aliyun.com" android:scheme="arouter"/> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> </intent-filter> </activity>
这里面的 host 、scheme 字段很重要。点击 url 会根据这两个字段会调起本地的 Activity 。
接下来,看一下我们的 HTML
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> <title></title> </head> <body> <h2>跳转测试</h2> <h2>自定义Scheme[通常来说都是这样的]</h2> <p> <a href="arouter://m.aliyun.com/test/activity1">arouter://m.aliyun.com/test/activity1 </a> </p> <p> <a href="arouter://m.aliyun.com/test/activity1?url=https%3a%2f%2fm.abc.com%3fa%3db%26c%3dd"> 测试URL Encode情况 </a> </p> <p> <a href="arouter://m.aliyun.com/test/activity1?name=alex&age=18&boy=true&high=180&obj=%7b%22name%22%3a%22jack%22%2c%22id%22%3a666%7d"> arouter://m.aliyun.com/test/activity1?name=alex&age=18&boy=true&high=180& obj={"name":"jack","id":"666"} </a> </p> </body> </html>
注意 a 标签里面的 arouter://m.aliyun.com 分别代表着 scheme 、host ;/com/URLActivity1 就是目标 Activity 的注解。
如果需要接收 URL 中的参数,需要在 Activity 调用自动注入初始化方法;
ARouter.getInstance().inject(this);
需要注意的是,如果不使用自动注入,那么可以不写 ARouter.getInstance().inject(this),但是需要取值的字段仍然需要标上 @Autowired 注解,因为 只有标上注解之后,ARouter 才能知道以哪一种数据类型提取 URL 中的参数并放入 Intent 中,这样您才能在 intent 中获取到对应的参数
其他用法
监听 ARouter 的执行过程,两种方式:
监听单一路由执行:
/** * 测试单一路由,降级监听,例如单一路由失败的话进行后续处理 */ private void ARouterCallBack() { ARouter.getInstance() .build("/com/hq") .navigation(this, new NavCallback() { @Override public void onFound(Postcard postcard) { Log.e(TAG, "onArrival: 找到了 "); } @Override public void onLost(Postcard postcard) { Log.e(TAG, "onArrival: 找不到了 "); Toast.makeText(MainActivity.this, "未找到目标页面 path=" + postcard.getPath() + " group=" + postcard.getGroup() + " 做降级处理,5s后跳转降级页", Toast.LENGTH_LONG).show(); jumpDegradePage(MainActivity.this, postcard); } @Override public void onArrival(Postcard postcard) { Log.e(TAG, "onArrival: 跳转完了 "); } @Override public void onInterrupt(Postcard postcard) { Log.e(TAG, "onArrival: 被拦截了 "); } }); }
在当前的 moudle 中,如果有找到 @Route(path=ARouterConstants.COM_ACTIVITY1) 注解的目标 activity,会先后回调 onFound,onArrival;如果找不到的话,会回调 onLost;如果被拦截了,会回调 onInterrupt 方法。
执行全局路由监听:
首先注册需要跳转的页面:
/** * 测试全局降级监听,全局路由监听,当找不到目标页面时,会调用DefaultDegrade * 走onLost方法,处理找不到页面的后续操作 */ private void ARouterDownDelegate() { ARouter.getInstance().build("/com/hq").navigation(); }
然后实现DegradeService,在这里进行全局路由监听:
/** * 全局降级监听,当找不到目标页面时,会调用这里 */ @Route(path = "/hehe/1a") public class DefaultDegrade implements DegradeService { @Override public void onLost(Context context, Postcard postcard) { Log.e("buder", "onLost"); if (context instanceof Activity) { Toast.makeText(context, "未找到目标页面 path=" + postcard.getPath() + " group=" + postcard.getGroup() + " 做降级处理,5s后跳转降级页", Toast.LENGTH_LONG).show(); } jumpDegradePage(context, postcard); } @Override public void init(Context context) { Log.e("buder", "init"); //跳转目标activity未找到时,创建此降级服务实例,懒加载 } private void jumpDegradePage(final Context context, final Postcard postcard) { new Handler().postDelayed(new Runnable() { @Override public void run() { ARouter.getInstance().build(ARouterConstants.DEGRADE) .navigation(context); } }, 5 * 1000); } }
Interceptor 拦截器
@Interceptor(priority = 8, name = "test interceptor") public class TestInterceptor implements IInterceptor { @Override public void process(Postcard postcard, InterceptorCallback callback) { ... // No problem! hand over control to the framework callback.onContinue(postcard); // Interrupt routing process // callback.onInterrupt(new RuntimeException("Something exception")); // The above two types need to call at least one of them, otherwise it will not continue routing } @Override public void init(Context context) { // Interceptor initialization, this method will be called when sdk is initialized, it will only be called once } }
拦截器的使用面向切面编程
// 在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查 // 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行 @Interceptor(name = "login", priority = 6) public class LoginInterceptorImpl implements IInterceptor { @Override public void process(Postcard postcard, InterceptorCallback callback) { String path = postcard.getPath(); LogUtils.e(path); boolean isLogin = SPUtils.getInstance().getBoolean(RoutePath.SP_IS_LOGIN, false); if (isLogin) { // 如果已经登录不拦截 callback.onContinue(postcard); } else { // 如果没有登录 switch (path) { // 不需要登录的直接进入这个页面 case RoutePath.LOGIN_PATH: case RoutePath.FIRST_PATH: callback.onContinue(postcard); break; // 需要登录的直接拦截下来 default: callback.onInterrupt(null); break; } } } @Override public void init(Context context) {//此方法只会走一次 LogUtils.e("路由登录拦截器初始化成功"); } }
//启动Activity ARouter.getInstance().build(RoutePath.SECOND_PATH) .withString("msg", "ARouter传递过来的需要登录的参数msg") .navigation(this,new LoginNavigationCallbackImpl());//第二个参数是路由跳转的回调
public class LoginNavigationCallbackImpl implements NavigationCallback { @Override //找到了 public void onFound(Postcard postcard) { } @Override //找不到了 public void onLost(Postcard postcard) { } @Override //跳转成功了 public void onArrival(Postcard postcard) { } @Override public void onInterrupt(Postcard postcard) { String path = postcard.getPath(); LogUtils.v(path); Bundle bundle = postcard.getExtras(); // 被登录拦截了下来了 // 需要调转到登录页面,把参数跟被登录拦截下来的路径传递给登录页面,登录成功后再进行跳转被拦截的页面 ARouter.getInstance().build(RoutePath.LOGIN_PATH) .with(bundle) .withString(RoutePath.PATH, path) .navigation(); } }
第一篇参考:https://www.jianshu.com/p/a57dd8c8f10e
第二篇参考:https://www.jianshu.com/p/d5a83ccf1135
项目代码:
ARouter的基础使用 : https://github.com/buder-cp/DesignPattern/tree/master/ARounterDemo-master
ARouter拦截器使用:https://github.com/buder-cp/DesignPattern/tree/master/ARouteLogin-master
-
-
ARouter基础
2019-06-01 23:57:33ARouter基础介绍 目前组件化和模块化开发逐渐成为移动端开发的主要方式,方便调试,更快捷的开发,使得程序更容易扩展,ARouter的出现给我们提供了很大的便利,它是阿里巴巴自己研发的路由框架,主要解决组件间、... -
ARouter 源码学习
2018-04-10 17:32:59ARouter 源码学习 官方文档: Android平台页面路由框架ARouter 阿里巴巴Arouter github地址如下: ARouter gitHub 地址 ARouter我的学习注释GitHub地址: ARouter Arouter 组件化Demo: Android_... -
ARouter 使用教程
2018-07-13 19:34:51ARouter 是阿里开源的,可以看成是 Android 平台中对页面、服务提供路由功能的中间件。 ARouter 直接翻译过来就是路由,可以用来映射页面关系,实现跳转相关的功能。在 Android 中,常被用来进行组件化通讯。 ... -
ARouter+MVVM
2019-01-24 16:04:45该框架采用MVVM设计模式,并整合目前主流的Okhttp+RxJava+Retrofit网络框架和Glide图片加载框架,通过使用ARouter实现组件化,打造一个可以快速开发一个高质量、易维护的Android应用。 -
ARouter踩坑
2020-03-24 18:34:47ARouter踩坑 1、页面跳转交互提示‘There is no route match the path’异常 1、编译的module需要依赖在‘主工程’下面 2、 Kotlin-Route参考 APT编译模式 -
ARouter基础踩坑demo
2018-11-21 17:24:19ARouter基础踩坑demo,简单几个类,先让ARouter跑起来再说。 -
Arouter使用
2019-06-29 15:09:25ARouter使用与原理 简单使用 1.添加依赖 在根目录下的build.gradle中添加如下代码 javaCompileOptions { annotationProcessorOptions { arguments = [moduleName :project.getName()] } } 在app下的build.gradle... -
ARouter踩坑之'ARouter::: ARouter::There is no route match the path [/MyRouter2/ARouter3Activity] '
2020-04-09 19:07:56组件化开发中,使用了ARouter作为路由组件,使用过程中出现了 'ARouter::: ARouter::There is no route match the path [/MyRouter2/ARouter3Activity] ' 原因 使用了@Route注解的module @Route(path = "/... -
ARouter 梳理
2019-10-22 16:17:58ARouter作为项目组件化的一个非常重要的工具,实现了很大的解耦 原理: 简单调用 @Route(path = "/test/activity1", name = "测试用 Activity") public class Test1Activity extends AppCompatActivity { path... -
ARouter源码解析
2019-07-09 23:13:33文章目录ARouter概述ARouter使用ARouter源码分析arouter-annotation注解arouter-compiler注解编译器arouter-api路由控制ARouter 初始化ARouter API跳转拦截器原理参考文档 ARouter概述 ARouter 是一个用于帮助 ... -
Arouter源码分析xmind
2020-10-20 12:38:23Arouter源码分析的xmind文件,直接在xmind打开。重要的类,接口,方法,一一列出,并给出详细介绍和分析。 -
ARouter的坑之Program type already present: com.alibaba.android.arouter.routes.ARouter$$Group$$...
2018-05-05 21:15:50错误信息是这样子的,...Program type already present: com.alibaba.android.arouter.routes.ARouter$$Group$$arouter Message{kind=ERROR, text=Program type already present: com.alibaba.android.arouter.ro... -
ARouter 引用
2019-01-05 09:26:001、功能简述 用于模块间解耦 2、module的gradle接入 android { defaultConfig { javaCompileOptions { annotationProcessorOptions { //ARouter def projectName = project.g... -
Android-一种基于ARouter的使用封装方案实现对ARouter的Retrofit2式使用
2019-08-13 06:18:02一种基于ARouter的使用封装方案,实现对ARouter的Retrofit2式使用 -
ArouterDemo.zip
2019-07-05 19:35:06Arouter模块化开发示例,详情可以参考https://xiangzhihong.blog.csdn.net/article/details/94736340
-
用高斯混合模型分类三维数据
-
NFS 实现高可用(DRBD + heartbeat)
-
js浅拷贝、深拷贝的实现
-
教你如何摆脱负债上岸,超详细(视频课程)
-
2021.3.1blog
-
(超详细笔记记录)从头开始学Spring 其二
-
py课程设计.zip
-
Galera 高可用 MySQL 集群(PXC v5.6 + Ngin
-
C/C++反汇编解密
-
Unity自学01/脚本执行后console中无法显示Debug.Log的内容
-
2021-03-02
-
Linux 串口源代码(纯C++)
-
尚观教育嵌入式4个月培训课堂笔记
-
唯恩.rar电气设备选型资料大全 (适合刚刚入行的电气工程师对设备进行选型规划)详解 报价
-
php软件开发--php进阶
-
华为企业服务风险评估服务主打胶片.ppt
-
springboot之yml配置文件信息加密.docx
-
Vue.js中$router和$route的区别
-
51单片机交通灯设计.rar
-
2013-2020矩阵代数往年试题.zip