之前一直没有写文章的习惯,但是随着开发时间的增长,你会发现你之前遇到好多已经解决过的问题,因为没有记录只是单纯的为了解决问题而解决,结果就导致下次在遇到同样的问题还要费时间去百度,Google的一顿查询,白白浪费时间。正所谓好记性不如烂笔头。好了废话不多说了。
一.ARouter支持哪些功能
1.支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
2.支持多模块工程使用
3.支持添加多个拦截器,自定义拦截顺序
4.支持依赖注入,可单独作为依赖注入框架使用
5.支持InstantRun
6.支持MultiDex(Google方案)
7.映射关系按组分类、多级管理,按需初始化
8.支持用户指定全局降级与局部降级策略
9.页面、拦截器、服务等组件均自动注册到框架
10.支持多种方式配置转场动画
11.支持获取Fragment
12.完全支持Kotlin以及混编(配置见文末 其他#5)
13.支持第三方 App 加固(使用 arouter-register 实现自动注册)
好了介绍完支持的哪些功能,接下来就开始使用下吧!
一.前期准备
首先在App的build.gradle的defaultConfig里面添加如下:
//使用阿里的ARouter配置
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
然后在App的build.gradle的dependencies里面添加ARouter的依赖
//ARouter(进行activity,fragment的跳转以及传值)
implementation 'com.alibaba:arouter-api:1.3.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
** 二.使用配置**
自定义一个Application并继承自Application在onCreate()方法中初始化Arouter
//是否进行ARouter调试(可以通过AppConfig.isDebug=true/false来判断日志的是否开启)
if (AppConfig.isDebug) {
//下面两句话必须放到init前面,否则配置无效
ARouter.openLog(); //打印ARouter日志
ARouter.openDebug(); //开启ARouter的调试模式(如果在InstantRun模式下运行,必须开启调试模式,线上版本需要关闭,否则有安全风险),
}
//官方建议在Application里面进行初始化(使用该注解路径至少两级)
ARouter.init(this);
最 后 不 要 忘 记 在 A n d r o i d M a n i f e s t . x m l 文 件 中 加 入 自 定 义 的 A p p l i c a t i o n \color{red}{最后不要忘记在AndroidManifest.xml文件中加入自定义的Application} 最后不要忘记在AndroidManifest.xml文件中加入自定义的Application
到这里ARouter就可以使用了,下面就简单的用代码实战下吧!
分别创建了2个Activity,1个fragment,1个IInterceptor(拦截器),1个ARouter的服务,1个携带数据时用到的转换器,下面是代码每一步都已经测试过了并且已经加上个人理解的注释了,有需要的可以复制试下
主 界 面 M a i n A c t i v i t y \color{red}{主界面MainActivity} 主界面MainActivity
package com.zd.myarouterdemo;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.core.app.ActivityOptionsCompat;
import androidx.fragment.app.Fragment;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.alibaba.android.arouter.facade.Postcard;
import com.alibaba.android.arouter.facade.annotation.Autowired;
import com.alibaba.android.arouter.facade.callback.NavigationCallback;
import com.alibaba.android.arouter.launcher.ARouter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private AppCompatButton btn, btn1, btn2, btn3, btn4, btn5, btn6, btn7;
/**
* 推荐使用获取ARouter服务
*/
@Autowired(name = AppConfig.SERVICE)
ARouterService aRouterService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ARouter.getInstance().inject(this);
btn = (AppCompatButton) findViewById(R.id.btn);
btn1 = (AppCompatButton) findViewById(R.id.btn1);
btn2 = (AppCompatButton) findViewById(R.id.btn2);
btn3 = (AppCompatButton) findViewById(R.id.btn3);
btn4 = (AppCompatButton) findViewById(R.id.btn4);
btn5 = (AppCompatButton) findViewById(R.id.btn5);
btn6 = (AppCompatButton) findViewById(R.id.btn6);
btn7 = (AppCompatButton) findViewById(R.id.btn7);
btn.setOnClickListener(this);
btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
btn3.setOnClickListener(this);
btn4.setOnClickListener(this);
btn5.setOnClickListener(this);
btn6.setOnClickListener(this);
btn7.setOnClickListener(this);
}
/**
* 都使用了一个拦截器TestInterceptor
*
* @param view
*/
@Override
public void onClick(View view) {
switch (view.getId()) {
// 页面之间跳转使用阿里巴巴ARouter跳转,其中注意带返回值启动的时候请求吗不能为0否则没有回调onActivityResult
// 使用ARouter传递参数的时候如果有传递Bundle跟其他的属性Bundle必须写在前面否则其他的属性注入不了
case R.id.btn:
//直接跳转
ARouter.getInstance().build(AppConfig.TEST).navigation();
break;
case R.id.btn1:
//带进入动画的跳转(注意这里动画一定要在build()之后不然会找不到方法)一下两种方法都可以,可以自行选择
//1.在调用navigation()时,若设置的效果未起效,则在navigation()添加参数,如navigation(this)即可。
// ARouter.getInstance()
// .build(AppConfig.TEST)
// //进入动画(注意这里动画一定要在build()之后不然会找不到方法),若设置的效果未起效,则在navigation()添加参数,如navigation(this)即可。
// //参数1为打开的Activity的进入动画,参数2为当前的Activity的退出动画
// .withTransition(R.anim.top_in, R.anim.top_out).navigation(this);
//2.新版本跳转动画
if (Build.VERSION.SDK_INT >= 16) {
ActivityOptionsCompat compat = ActivityOptionsCompat.
makeScaleUpAnimation(view, view.getWidth() / 2, view.getHeight() / 2, 0, 0);
ARouter.getInstance()
.build(AppConfig.TEST)
.withOptionsCompat(compat)
.navigation();
} else {
Toast.makeText(this, "API < 16,不支持新版本动画", Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn2:
//带动画如果不起作用,请看btn1事件注释
//这里传递基础参数跟原生的一样(key-value)
Bundle bundle = new Bundle(); //得到bundle对象
bundle.putInt("index", 1);
bundle.putString("name", "张三");
bundle.putLong("num", 0x555555L);
ARouter.getInstance()
.build(AppConfig.TEST)
.withInt("index", 1)
.withString("name", "张三")
.withLong("num", 0x555555L)
.withBundle("bundle", bundle)
.withTransition(R.anim.slide_in_left, R.anim.slide_out_right).navigation(this);
break;
case R.id.btn3:
//带动画如果不起作用,请看btn1事件注释
//这里传递对象参数跟原生的一样(key-value)
Person person = new Person("张三", "25");
ARouter.getInstance()
.build(AppConfig.TEST)
.withTransition(R.anim.bottom_in, R.anim.bottom_out)
.withInt("index", 2)
.withObject("person", person)
.navigation(this);
break;
case R.id.btn4:
//带动画如果不起作用,请看btn1事件注释
//这里传递集合参数跟原生的一样(key-value)
List personList = new ArrayList<>();
personList.add(new Person("张三", "25"));
personList.add(new Person("里斯", "23"));
Map map = new HashMap<>();
map.put("person", new Person("王二麻子", "26"));
map.put("list", personList);
ARouter.getInstance()
.build(AppConfig.TEST)
.withTransition(R.anim.bottom_in, R.anim.bottom_out)
.withInt("index", 3)
.withObject("personList", personList)
.withObject("map", map)
.navigation(this);
break;
case R.id.btn5:
//拦截器的使用面向切面编程
//跳转拦截,可以用于登录
ARouter.getInstance().build(AppConfig.TEST).navigation(this, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
Log.e("MainActivity", "找到了");
}
@Override
public void onLost(Postcard postcard) {
Log.e("MainActivity", "找不到了");
}
@Override
public void onArrival(Postcard postcard) {
Log.e("MainActivity", "目标activity打开完成");
}
@Override
public void onInterrupt(Postcard postcard) {
// 被登录拦截了下来了
// 需要调转到登录页面,把参数跟被登录拦截下来的路径传递给登录页面,登录成功后再进行跳转被拦截的页面
Log.e("MainActivity", "被拦截了");
}
});
break;
case R.id.btn6:
//获取Fragment的实例(重复嵌套的情况下可能获取不到,所以不推荐)
Fragment fragment = (Fragment) ARouter.getInstance().build(AppConfig.FRAGMENT).navigation();
Toast.makeText(this, fragment.toString(), Toast.LENGTH_SHORT).show();
break;
case R.id.btn7:
// 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用,无需主动获取
// 2. 使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是byName和byType
aRouterService.toast("第一种推荐的ARouter服务使用方式");
break;
}
}
}
要 跳 转 的 界 面 M a i n A c t i v i t y 的 布 局 文 件 \color{red}{要跳转的界面MainActivity的布局文件} 要跳转的界面MainActivity的布局文件
要 跳 转 的 界 面 T e s t A c t i v i t y \color{red}{要跳转的界面TestActivity} 要跳转的界面TestActivity
package com.zd.myarouterdemo;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.alibaba.android.arouter.facade.annotation.Autowired;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alibaba.android.arouter.launcher.ARouter;
import java.util.List;
import java.util.Map;
/**
* @ClassName: TestActivity
* @Description: 测试页面
* @Author: ljl
* @CreateDate: 2019/9/3 11:22
* @Version: 1.0
*/
@Route(path = AppConfig.TEST)
public class TestActivity extends AppCompatActivity {
private TextView textShow, textShow1, textShow2;
/**
* 注解方式使用(尽量加上name),类型一定要对上不然也会获取不到数据,为null或者0
*/
@Autowired(name = "index")
int id;
@Autowired(name = "name")
String name;
@Autowired(name = "num")
long num;
@Autowired(name = "bundle")
Bundle bundleStr;
@Autowired(name = "person")
Person person;
@Autowired(name = "personList")
List personList;
@Autowired(name = "map")
Map map;
@Autowired(name = "sp")
String sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
textShow = (TextView) findViewById(R.id.tv_show);
textShow1 = (TextView) findViewById(R.id.tv_show1);
textShow2 = (TextView) findViewById(R.id.tv_show2);
//2.注解方式(使用这种方式一定要注意加上ARouter.getInstance().inject(this)这句话,会获取不到数据(int类型为0,String类型为null))
ARouter.getInstance().inject(this);
Toast.makeText(this, sp, Toast.LENGTH_SHORT).show();
if (id != 0) {
if (id == 1) {
//获取传递过来参数分为两种(一种是原始的,另一种就是通过ARouter的注解方式)
//1.原始方式
Bundle bundle = getIntent().getExtras();
//下面是获取传递的Bundle数据(没什么区别)
Bundle bundle1 = getIntent().getBundleExtra("bundle");
textShow.setText("基础参数原始方式:序号:" + bundle1.getInt("index") + ",姓名:" + bundle.getString("name") + ",号码:" + bundle.getLong("num"));
textShow1.setText("基础参数注解方式;序号:" + id + ",姓名:" + name + ",号码:" + num + "bundle数据获取:" + bundleStr.getString("name"));
} else if (id == 2) {
//这里获取对象的时候一定要写实现SerializationService的类(随便写不用调用就行,但是必须要有,不然或获取不到,我这里有的JsonSerializationService类)
textShow2.setText("对象参数:" + person.toString());
} else if (id == 3) {
//这里获取集合的时候一定要写实现SerializationService的类(随便写不用调用就行,但是必须要有,不然或获取不到,我这里有的JsonSerializationService类)
textShow2.setText("集合参数:" + personList.toString() + "==============map参数:对象:" + map.get("person") + "集合:" + map.get("list"));
}
}
}
}
T e s t A c t i v i t y 的 布 局 文 件 \color{red}{TestActivity的布局文件} TestActivity的布局文件
携 带 对 象 , 结 合 , m a p 跳 转 时 用 到 的 解 析 J s o n S e r i a l i z a t i o n S e r v i c e \color{red}{携带对象,结合,map跳转时用到的解析JsonSerializationService} 携带对象,结合,map跳转时用到的解析JsonSerializationService
package com.zd.myarouterdemo;
import android.content.Context;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alibaba.android.arouter.facade.service.SerializationService;
import com.alibaba.fastjson.JSON;
import java.lang.reflect.Type;
/**
* @ClassName: JsonSerializationService
* @Description: 跳转界面带参(传递Object)定义解析Obeject的SerializationService
* @Author: ljl
* @CreateDate: 2019/9/2 10:32
* @Version: 1.0
*/
@Route(path = AppConfig.OBJECT_CUSTOM_JSON)
public class JsonSerializationService implements SerializationService {
@Override
public T json2Object(String input, Class clazz) {
return JSON.parseObject(input, clazz);
}
@Override
public String object2Json(Object instance) {
return JSON.toJSONString(instance);
}
@Override
public T parseObject(String input, Type clazz) {
return JSON.parseObject(input, clazz);
}
@Override
public void init(Context context) {
}
}
获 取 A R o u t e r 的 服 务 用 到 的 A R o u t e r S e r v i c e I P r o v i d e r \color{red}{获取ARouter的服务用到的ARouterServiceIProvider} 获取ARouter的服务用到的ARouterServiceIProvider
package com.zd.myarouterdemo;
import com.alibaba.android.arouter.facade.template.IProvider;
/**
* @ClassName: ARouterServiceIProvider
* @Description: ARouter服务使用
* @Author: ljl
* @CreateDate: 2019/9/3 17:05
* @Version: 1.0
*/
public interface ARouterServiceIProvider extends IProvider {
String toast(String msg);
}
获 取 A R o u t e r 的 服 务 用 到 的 A R o u t e r S e r v i c e \color{red}{获取ARouter的服务用到的ARouterService} 获取ARouter的服务用到的ARouterService
package com.zd.myarouterdemo;
import android.content.Context;
import com.alibaba.android.arouter.facade.annotation.Route;
/**
* @ClassName: ARouterService
* @Description: ARouterService
* @CreateDate: 2019/9/3 16:59
* @Version: 1.0
*/
@Route(path = AppConfig.SERVICE, name = "测试服务")
public class ARouterService implements ARouterServiceIProvider {
@Override
public String toast(String msg) {
return "ARouter服务消息, " + msg;
}
@Override
public void init(Context context) {
}
}
界 面 跳 转 时 用 的 的 拦 截 器 \color{red}{界面跳转时用的的拦截器} 界面跳转时用的的拦截器
package com.zd.myarouterdemo;
import android.content.Context;
import android.util.Log;
import com.alibaba.android.arouter.facade.Postcard;
import com.alibaba.android.arouter.facade.annotation.Interceptor;
import com.alibaba.android.arouter.facade.callback.InterceptorCallback;
import com.alibaba.android.arouter.facade.template.IInterceptor;
/**
* @ClassName: TestInterceptor
* @Description: ARouter拦截器
* // 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查
* // 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行
* @CreateDate: 2019/9/3 16:23
* @Version: 1.0
*/
@Interceptor(priority = 1)
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
String path = postcard.getPath();
Log.e("路径", path);
if (path == AppConfig.TEST) {
//如果是跳到TestActivity,就加个数据,当然也可以做其他操作
postcard.withString("sp", "我是在拦截器中附加的参数");
}
//继续跳转
callback.onContinue(postcard);
//终止跳转
//callback.onInterrupt(null)
//抛出异常
// callback.onInterrupt(RuntimeException("我觉得有点异常"))
// onContinue和onInterrupt至少需要调用其中一种,否则不会继续路由
}
@Override
public void init(Context context) {
Log.e("TestInterceptor", "路由登录拦截器初始化成功"); //只会走一次
}
}
获 取 f r a g m e n t 实 例 时 用 到 的 B l a n k F r a g m e n t \color{red}{获取fragment实例时用到的BlankFragment} 获取fragment实例时用到的BlankFragment
package com.zd.myarouterdemo;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.alibaba.android.arouter.facade.annotation.Route;
/**
* fragment测试
*/
@Route(path = AppConfig.FRAGMENT)
public class BlankFragment extends Fragment {
private OnFragmentInteractionListener mListener;
public BlankFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false);
}
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnFragmentInteractionListener {
void onFragmentInteraction(Uri uri);
}
}
A p p C o n f i g 配 置 \color{red}{AppConfig配置} AppConfig配置
package com.zd.myarouterdemo;
/**
* @ClassName: AppConfig
* @Description: ARouter跳转参数配置
* @Author: ljl
* @CreateDate: 2019/9/3 11:17
* @Version: 1.0
*/
public class AppConfig {
/**
* 是否是测试模式(用于判断是否开启ARouter是否打印Log和openDebug)
*/
public static boolean isDebug = true;
/**
* 这里用于统一管理跳转注解路径(这里要注意一定要至少两级并且前面一定要有斜杠,不要忘记,不然不会出错)
* 1.前面缺少斜杠会出现错误,错误如下
* Process: com.zd.myarouterdemo, PID: 12831
* com.alibaba.android.arouter.exception.HandlerException: ARouter::Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!
*
*
* 2.如果注解路径只有一级时,build会在编译的时候直接报错,错误如下
* 错误: ARouter::Compiler An exception is encountered, [Failed to extract default group! String index out of range: -2]
* 警告: ARouter::Compiler >>> Route meta verify error, group is <<<
* 注: ARouter::Compiler >>> Generated provider map, name is ARouter$$Providers$$app <<<
* 注: ARouter::Compiler >>> Generated root, name is ARouter$$Root$$app <<<
* 1 个错误
* 1 个警告
*/
public static final String TEST = "/app/TestActivity";
public static final String FRAGMENT = "/app/BlankFragment";
//传值的时候对象实体,集合,map使用
public static final String OBJECT_CUSTOM_JSON = "/custom/json";
//ARouter里的Service
public static final String SERVICE = "/custom/service";
}
实 体 对 象 P e r s o n \color{red}{实体对象Person} 实体对象Person
package com.zd.myarouterdemo;
import android.text.TextUtils;
/**
* @ClassName: Person
* @Description: java类作用描述
* @CreateDate: 2019/9/3 14:24
* @Version: 1.0
*/
public class Person {
public String name;
public String age;
public Person() {
}
public Person(String name, String age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
if (TextUtils.isEmpty(name)) {
return "";
} else {
return "name : " + name + "\n age : " + age;
}
}
}
M y A p p l i c a t i o n \color{red}{MyApplication} MyApplication
package com.zd.myarouterdemo;
import android.app.Application;
import android.content.Context;
import com.alibaba.android.arouter.launcher.ARouter;
/**
* @ClassName: MyApplication
* @Description: java类作用描述
* @Author: ljl
* @CreateDate: 2019/9/3 11:17
* @Version: 1.0
*/
public class MyApplication extends Application {
private static Context mContext;
@Override
public void onCreate() {
super.onCreate();
//是否进行ARouter调试
if (AppConfig.isDebug) {
//下面两句话必须放到init前面,否则配置无效
ARouter.openLog(); //打印ARouter日志
ARouter.openDebug(); //开启ARouter的调试模式(如果在InstantRun模式下运行,必须开启调试模式,线上版本需要关闭,否则有安全风险),
}
//官方建议在Application里面进行初始化(使用该注解路径至少两级)
ARouter.init(this);
}
public static Context getContext() {
return mContext;
}
}
最 后 在 写 的 过 程 中 遇 到 过 找 不 到 布 局 文 件 里 的 I d ( 问 题 : C a n n o t r e s o l v e s y m b o l ′ b t n 7 ′ ) , 但 是 编 译 没 问 题 。 \color{red}{最后在写的过程中遇到过找不到布局文件里的Id(问题:Cannot resolve symbol 'btn7'),但是编译没问题。} 最后在写的过程中遇到过找不到布局文件里的Id(问题:Cannotresolvesymbol′btn7′),但是编译没问题。
解决方式:android studio报出一个提示,R类里出现了File size exceeds configured limit (2560000). Code insight features not available.
原因是Intellij IDEA对单个文件的大小默认限制为2500kb, android studio是基于Intellij IDEA的,而Windows系统和OS X系统对文件大小的计算方式不一致,前者是1024进制,后者是1000进制,所以暂时OS X系统上没有出现问题,后来将OS X 系统上的限制大小改小后,就出现一样的问题。
进入android studio的安装目录 ,将里面的idea.max.intellisense.filesize=2500数值更改为5000,重启下Android studio就可以了