这本是一篇应该写在去年的文章,但仅仅由于...懒,这篇文章在草稿箱里静静的躺了一年多,被无限期推迟到了如今。最近恰好完成了公司项目的路由改造,借此机会来对这篇文章作一个告终。java
在开始以前咱们先来思考一下这个问题。为何要在项目中引入路由?相信你们的答案可能会有所不一样,可是应该也不外乎如下几点:android
想必不少开发者引入路由的目的都是由于要实现项目组件化。咱们知道,组件化的项目各个业务模块之间没有相互的依赖关系。不一样业务模块之间的通讯最好的解决方案就是支持页面路由。git
可能有些小伙伴会有疑问,App内部直接经过Intent跳转不是很好吗,为何要画蛇添足引入路由呢?固然,一般状况下经过Intent跳转也无伤大雅。可是在某些状况下,好比像下图这样的一个页面: github
一般能够看到不少应用支持从浏览器唤醒App并跳转到对应的页面。作到比较好的如知乎,体验过知乎的小伙伴应该知道,知乎能够从浏览器唤醒App而且直接在App中打开当前在浏览器中浏览的内容。咱们知道,从外部唤起App须要给Activity添加Schema。而若是App内部有许多Activity须要支持外部唤起,咱们不可能为这些Activity都添加Schema。那么此时咱们就能够单独设置一个支持Schema的Activity,浏览器能够经过Schema唤起这个Activity。而在这个Activity中会接收浏览器传过来的URL,而后根据URL进行路由分发,经过URL路由到对应的页面便可。json
其实很不想在这篇文章中长篇大论如何使用ARouter,由于ARouter的官方文档上已经很是详细的告诉了开发者如何去使用,只要仔细的阅读ARouter的文档基本上绝大部分问题均可以获得解决。可是为了照顾没有使用过ARouter的小伙伴,这里仍是再啰嗦一下。若是你对ARouter的使用已经很是熟悉了那么你能够忽略此章节,直接到下一章了。小程序
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// 替换成最新版本, 须要注意的是api
// 要与compiler匹配使用,均使用最新版能够保证兼容
implementation 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
复制代码
这里须要注意,若是你的项目有多个业务模块,那么每一个模块都须要在gradle中添加以上配置。api
if (isDebug()) { // 这两行必须写在init以前,不然这些配置在init过程当中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(若是在InstantRun模式下运行,必须开启调试模式!线上版本须要关闭,不然有安全风险)
}
ARouter.init(mApplication); // 尽量早,推荐在Application中初始化
复制代码
// 在支持路由的页面上添加注解(必选)
// 这里的路径须要注意的是至少须要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
复制代码
// 1. 应用内简单的跳转(经过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
复制代码
不少状况下须要经过URL跳转,ARouter支持直接经过URL跳转:浏览器
Uri uri= Uri.parse(url);
ARouter.getInstance().build(uri).navigation();
复制代码
// 为每个参数声明一个字段,并使用 @Autowired 标注
// URL中不能传递Parcelable类型数据,经过ARouter api能够传递Parcelable对象
@Route(path = "/test/activity")
public class Test1Activity extends Activity {
@Autowired
public String name;
@Autowired
int age;
// 经过name来映射URL中的不一样参数
@Autowired(name = "girl")
boolean boy;
// 支持解析自定义对象,URL中使用json传递
@Autowired
TestObj obj;
// 使用 withObject 传递 List 和 Map 的实现了
// Serializable 接口的实现类(ArrayList/HashMap)
// 的时候,接收该对象的地方不能标注具体的实现类类型
// 应仅标注为 List 或 Map,不然会影响序列化中类型
// 的判断, 其余相似状况须要一样处理
@Autowired
List<TestObj> list;
@Autowired
Map<String, List<TestObj>> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
// ARouter会自动对字段进行赋值,无需主动获取
Log.d("param", name + age + boy);
}
}
// 若是须要传递自定义对象,新建一个类(并不是自定义对象类),而后实现 SerializationService,并使用@Route注解标注(方便用户自行选择序列化方式),例如:
@Route(path = "/yourservicegroupname/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);
}
}
复制代码
除了使用@Autowired注解注入参数外,还能够与普通页面跳转同样经过getIntent()获取参数。安全
以上就是ARouter的一些基本用法,了解这些基本用法以后并不等于已经掌握了ARouter。由于当你实际用到项目中的时候可能会面临诸多问题。bash
若是你只是简单的写一个ARouter使用的Demo,那么可能上一章的内容已经足够了。可是当你在项目中引入ARouter后各类各样的问题便会接踵而至。
这是在项目中引入ARouter后面临的第一个问题。一般状况下,大部分App不登陆即可以进入主页面,在跳转须要用户权限的页面时会首先跳转到登陆页面引导用户登陆。我相信大部分的开发在最初时候都写过相似这样的代码:
if (isLogin) {
goToDestination();
} else {
goToLogin();
}
复制代码
在每次跳转页面的时候都须要进行是否登陆的判断,这样的代码显然有很大的弊端。而ARouter为咱们提供了面向切面的登陆拦截功能,ARouter的文档上给了咱们一个例子:
// 比较经典的应用就是在跳转过程当中处理登录事件,这样就不须要在目标页重复作登录检查
// 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行
@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初始化的时候调用该方法,仅会调用一次
}
}
复制代码
若是你按着官方文档上这样写,那么你大几率会碰到不少问题。列举以下: 如何处理有些页面须要登陆拦截,有些页面不须要登陆拦截? 若是你添加了拦截器,那么在每次路由跳转时都会优先走到拦截器中,在拦截器的process()方法中你能够经过判断当前是否登陆来决定是否继续该路由操做,若是已经登陆,那么直接经过 callback.onContinue(postcard)继续当前路由,而若是没有登陆,那么就将目的页面修改成登陆页。可是,不要忘了,添加拦截器后全部的路由操做都会优先走到这里,而咱们的需求是只有须要用户权限的时候才须要跳转到登陆页,不然即便没有登陆依然能够跳转到目的页。此时咱们应该怎么办? 若是你仔细的看了ARouter的开发文档,你可能注意到在@Route的注解有一个int类型的extras参数。如此咱们即可以经过这个参数来对Activity进行标记是否须要登陆:
@Route(path = PATH_TEST, extras = IGNORE_LOGIN)
public class TestActivity extends BaseTitleCompatActivity {}
复制代码
接下来,在拦截器中能够拿到extras参数,以此来肯定该页面是否须要登陆:
if(UserInfoTools.isLogin() || IGNORE_LOGIN == postcard.getExtra()) { // 已经登陆或者不须要拦截的状况
// 继续当前路由
callback.onContinue(postcard);
} else { // 未登陆且须要登陆的状况
// 路由到登陆页面
ARouter.getInstance().build(RoutingTable.PATH_GUEST_LOGIN).navigation();
...
}
复制代码
到这里这个问题解决了,可是当你兴致勃勃的运行起来App,在未登陆的状况下点击跳转到须要用户权限的页面,你憧憬着跳转页面会被拦截到登陆页,可是你又被无情的事实打脸了。居然页面毫无反应?因而你断点、打Log发现ARouter.getInstance().build(RoutingTable.PATH_GUEST_LOGIN).navigation()这句代码确实执行了,可是为何没有跳转到登陆页?因而你苦思冥想,忽然灵光一闪,哇!是由于这一句路由也会走到了拦截器里,如此岂不成了一个死循环。因而你Google如何解决,发现原来须要调用greenChannel()来避免出现死循环。因而有了以下代码:
if(UserInfoTools.isLogin() || IGNORE_LOGIN == postcard.getExtra()) { // 已经登陆或者不须要拦截的状况
// 继续当前路由
callback.onContinue(postcard);
} else { // 未登陆且须要登陆的状况
// 路由到登陆页面
ARouter.getInstance().build(RoutingTable.PATH_GUEST_LOGIN).greenChannel().navigation();
...
}
复制代码
修改以后你怀着和刚才同样的心情兴致勃勃的运行起来App,心想,此次必定没问题。好!点击按钮....居然成功跳转到了登陆页面。因而你兴奋起来,疯狂的点击这些页面,发现都没问题。但是...当你点了几回以后忽然发现,页面跳转无效了!!你简直不敢相信本身的眼睛,刚才明明是好好的...因而你在此陷入了沉思。 好吧,此次直接公布答案了,那是由于你须要将原来的路由打断,而之因此前几回有效大概猜想是由于greenChannel()去开启了多个channel,而ARouter的channel是有限的,所以在点击几回以后路由再次失效了。因而修改后代码以下:
if(UserInfoTools.isLogin() || IGNORE_LOGIN == postcard.getExtra()) { // 已经登陆或者不须要拦截的状况
// 继续当前路由
callback.onContinue(postcard);
} else { // 未登陆且须要登陆的状况
// 路由到登陆页面
ARouter.getInstance().build(RoutingTable.PATH_GUEST_LOGIN).greenChannel().navigation();
callback.onInterrupt(null);
}
复制代码
关于登陆拦截看似简单,实则使用时候居然会碰到这么多问题!相信第一次使用时都会被虐的掉眼泪。
在某些状况可能出现一个页面对应多个路径的状况。出现这种状况的缘由多是前期路由没有规划好,致使后边版本的路由路径作了修改。从而出现了一个Activity对应多个页面的状况。为了兼容旧版路由,咱们不得不处理这种状况。可是,Route的注解中path是惟一的,并不能经过@Route注解解决一个Activity对应多个路径的状况。此时就须要用到ARouter的重写URL的功能。只须要实现PathReplaceService 接口,在重写的方法中对URI或者Path进行替换便可,注意,这个类必定要加@Route注解。代码参考以下:
@Route(path = "/lost/service")
public class ARouterLostReplaceService implements PathReplaceService {
@Override
public String forString(String path) { // 对于path处理与uri相似
return path;
}
@Override
public Uri forUri(Uri uri) {
String path = uri.getPath();
if(PATH_LOST1.equals(path)) {
uri = replaceUriPath(uri, PATH_REAL1);
} else if(PATH_LOST2.equals(path)) {
uri = replaceUriPath(uri, PATH_REAL2);
}
return uri;
}
@Override
public void init(Context context) {
}
/**
* 替换URI中的path
*
* @param uri 被替换的uri
* @param path 要替换的path
* @return 替换后的uri
*/
private Uri replaceUriPath(Uri uri, String path) {
StringBuilder resultUrl = new StringBuilder(uri.getScheme() + "://" + uri.getHost() + path);
String[] split = uri.toString().split("\\?");
if(split.length >= 2) {
resultUrl.append("?").append(split[1]);
}
return Uri.parse(resultUrl.toString());
}
}
复制代码
在路由跳转时可能会出现找不到Path对应页面的状况,对于这种状况能够经过实现DegradeService 接口来处理,一样这个类也必需要添加@Route注解。这样当路由跳转时找不到路径就会走到这个类的onLost方法中,此时就能够在这个方法中来作相应的处理了。
// 实现DegradeService接口,并加上一个Path内容任意的注解便可
@Route(path = "/lost/path")
public class DegradeServiceImpl implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
// 能够在此处统一处理,好比跳转到首页
}
@Override
public void init(Context context) {
}
}
复制代码
不少人对于Schema协议比较陌生,可是若是说URL你们必定都很是熟悉。其实URL就是一种Schema协议。Schema协议一般由四部分组成:
[scheme]://[host]/[path]?[query]
scheme:表示协议名称
host:Schema所做用的地址域
path:Schema指定的路径
query:携带的参数
复制代码
拿百度搜索的URL来举例子:www.baidu.com/s?wd=要搜索的关键…
schema::https
host: www.baidu.com
path: /s
query:wd=要搜索的关键字
复制代码
了解了Schema协议后,其实咱们彻底能够按照Schema协议的格式来自定义一个Schema连接,以下:
myApp://www.myApp.com/main/home?id=1
咱们本身定义的Schema连接的对应关系为:
schema::myApp
host:www.myApp.com
path:/main/home
query:id=1
复制代码
经过浏览器打开App其实就是经过Schema连接来实现的。咱们就以上一节中自定义的Schema连接为例来实现浏览器打开App。首先在项目中添加一个RouterActivity,RouterActivity在AndroidManifest中的配置以下:
<activity
android:name=".activity.RouterActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myApp" />
</intent-filter>
</activity>
复制代码
咱们在AndroidManifest中为RouterActivity添加了schema,此时在HTML中写入如下代码:
<a href="myApp://www.myApp.com/main/home?id=1">打开APP</a>
复制代码
经过点击HTML页面的"打开App"即可启动RouterActivity。而且RouterActivity启动后能够经过Intent获取到启动的URI。代码以下:
#RouterActivity
@Override
protected void onCreate(Bundle data) {
super.onCreate(data);
Uri launchUri = getIntent().getData();
dispatchRouterUri(launchUri);
}
复制代码
至此,咱们已经能够经过App来打开项目的RouterActivity。
上一节中咱们经过HTML打开了RouterActivity,并在RouterActivity中拿到了跳转的URI,那么接下来咱们即可以根据URI的信息打开对应的页面了。可是在开启路由跳转以前为了保险起见须要对URI进行一些校验。详细代码以下:
private void dispatchRouterUri(Uri launchUri) {
if(RoutingTable.isValidRouterUri(launchUri)) { // 判断是不是合法的URI,这里只有URI携带了Path才算合法
if(App.isRootActivityLaunched()) { // app已启动
if(RoutingTable.isWxUri(launchUri)) { // 若是是微信的URI那么目的地是要跳转到小程序的(此处为项目中的需求)
RoutingTable.openMiniProgram(this, launchUri);
finish();
return;
}
// 经过ARouter路由到目的页面
ARouter.getInstance().build(launchUri).navigation();
} else { // app未启动, 保存router uri, 幷尝试启动app
SharedPreferUtil.put(Constants.ROUTER_URI, launchUri.toString());
launchApp();
}
} else { // 走到此处多是由于URI没有携带Path,即并不是要跳转目的页面,而是要启动APP 。所以直接启动App便可
launchApp();
}
finish();
}
复制代码
上面代码中,咱们对URI作了一系列校验,根据不一样的URI作不一样的处理。同时咱们应该也注意到了,若是APP已经启动了,那么就能够直接跳转对应的页面了,而若是App没有启动,那么则是先将URI保存到了SharedPreference中,接着启动了App。那么此时App启动后会在MainActivity中读取SharedPreference中的配置,若是读取到URI的信息,那么就先将此数据从SharedPreference中移除,而后经过ARouter跳转到URI指定的页面去。MainActivity中的部分代码以下:
#MainActivity
private void resumeRoute() {
// Continue for interrupted router uri
String interruptedLaunchUriString =
SharedPreferUtil.get(Constants.ROUTER_URI, null);
// 移除SharedPreference中的URI,避免下次打开MainActivity错误跳转
SharedPreferUtil.remove(Constants.ROUTER_URI);
Uri launchUri = null;
if(interruptedLaunchUriString != null) { // Activity未启动的状况下 经过外部Scheme跳转非MainActivity
launchUri = Uri.parse(interruptedLaunchUriString);
}
if(launchUri == null) {
return;
}
// 经过路由跳转到URI对应的页面
ARouter.getInstance().build(launchUri).navigation();
}
复制代码
关于ARouter的路由方案所涉及的内容至此已经所有讲完了。