我的面试准备

Android路由框架-ARouter

2018-12-26  本文已影响187人  苏州韭菜明

探索Android路由框架-ARouter

博客中代码已上传github,点击此处即可到达

ARouter:一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

ARouter官方项目地址

一、功能介绍

  1. 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
  2. 支持多模块工程使用
  3. 支持添加多个拦截器,自定义拦截顺序
  4. 支持依赖注入,可单独作为依赖注入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射关系按组分类、多级管理,按需初始化
  8. 支持用户指定全局降级与局部降级策略
  9. 页面、拦截器、服务等组件均自动注册到框架
  10. 支持多种方式配置转场动画
  11. 支持获取Fragment
  12. 完全支持Kotlin以及混编(配置见文末 其他#5)
  13. 支持第三方 App 加固(使用 arouter-register 实现自动注册)
  14. 支持生成路由文档
  15. 提供 IDE 插件便捷的关联路径和目标类

二、典型应用

  1. 从外部URL映射到内部页面,以及参数传递与解析
  2. 跨模块页面跳转,模块间解耦
  3. 拦截跳转过程,处理登陆、埋点等逻辑
  4. 跨模块API调用,通过控制反转来做组件解耦

三、对比与接入

原生跳转方式的不足

显式跳转,

Intent intent = new Intent(activity, XXActivity.class);

由于需要直接持有对应class,从而导致了强依赖关系,提高了耦合度

隐式跳转,譬如

Intent intent = new Intent(); 

intent.setAction(“com.android.activity.MY_ACTION”);

action等属性的定义在Manifest,导致了扩展性较差,规则集中式管理,导致协作变得非常困难。Manifest文件会有很多过滤配置,而且非常不利于后期维护。

添加依赖

20181225111341.png

注意,需要将library也添加进入依赖

android {
        defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

 // 替换成最新版本, 需要注意的是api
    // 要与compiler匹配使用,均使用最新版可以保证兼容
    implementation ('com.alibaba:arouter-api:1.4.1'){
        exclude group: 'com.android.support'
    }//(support 是我自己添加,排除依赖冲突)
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
    implementation project(':modulerone')//示例中个人lib
    implementation project(':modulertwo')//示例中个人lib

初始化SDK

20181225112539.png
if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化

别忘记了在清单文件里面配置自定义的Application和Activity。

项目依赖导入和初始化就已经完成了,下面就开始正式的功能使用以及简单的封装。

开始使用:

简单页面跳转

@Route(path = "/app/MainActivity")
public class MainActivity extends AppCompatActivity {
    TextView tv_mvp, tv_arouter;

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ARouter.getInstance().inject(this);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv_mvp = findViewById(R.id.tv_entry_mvp);
        tv_arouter = findViewById(R.id.tv_entry_arouter);
        tv.setText(JNIManage.getInstance().getNums());
        setOnClicks();
    }

    private void setOnClicks() {
        tv_mvp.setOnClickListener((view) -> {
            Intent intent = new Intent(this, MVPActivity.class);
            startActivity(intent);
        });
        tv_arouter.setOnClickListener((view -> {
 //用过ARouter的同学应该知道,用ARouter启动Activity应该是下面这个写法:
                            ARouter.getInstance().build("/moduleone/ModulerMainActivity").navigation();
        }));
    }
}
@Route(path = "/moduleone/ModulerMainActivity")
public class ModulerMainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_moduler_main);
        ARouter.getInstance().inject(this);
    }
}

其中,build里面是页面的标签路径,对应的就是目标Activity的这里,也就是类注释标签路径要一致:

带参数的界面跳转

​ 带参数的跳转是很常见的功能,Android可以通过Bundle去传递参数,如果使用ARouter框架,它传递参数通过以下去操作:

ARouter传递对象的时候,首先该对象需要Parcelable或者Serializable序列化,Android Studio已经有一些插件帮我们自动生成Parcelable序列化了(因为Android用Parcelable序列化优势会更加明显一些)

字符串、char、int等基本数据类型当然都是可以传递

当然,它也可以直接传Bundle、数组、列表等很多对象,

20181225151556.png

携带参数的界面跳转,简单使用如下

        tv_arouter.setOnClickListener((view -> {
            ARouter.getInstance().build("/moduleone/ModulerMainActivity")
                    .withString("name", "DeLux")
                    .withInt("age", 25)
                    .withParcelable("book", new Books("prince", 25, 20189, "HuNanPress"))
                    .navigation();
        }));

其中,第一个参数代表的是参数的key,第二个参数对应的是我们要传递的属性值,也就是value

那么目标界面如何获取传递过来的值?

这个时候,我们需要在目标界面,使用Autowired注解


@Route(path = "/moduleone/ModulerMainActivity")
public class ModulerMainActivity extends AppCompatActivity {
    private TextView mShowMsg;

    @Autowired
    String name;
    @Autowired
    int age;
    @Autowired(name = "book")
    Books books;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_moduler_main);
        mShowMsg = findViewById(R.id.tv_module_two_show);
        // ARouter会自动对字段进行赋值,无需主动获取
        ARouter.getInstance().inject(this);
        printMsg();
    }

    private void printMsg() {
        mShowMsg.setText("name " + name + " age " + age + " book is " + books.toString());

    }
}

所以为了规避每一个可能会遇到的风险,建议在@Autowired里面 都写上与之对应具体的key名。

界面跳转动画

直接调用withTransition,里面传入两个动画即可(R.anim.xxx,R.anim.xxx)

20181225163945.png

使用URI进行跳转

ARouter框架也可以使用URI进行匹配跳转,代码也很少,只需匹配路径一致即可完成跳转:

        tv_arouter.setOnClickListener((view -> {
            ARouter.getInstance().build(Uri.parse("/moduleone/ModulerMainActivity"))
                    .withString("name", "DeLux")
                    .withInt("age", 25)
                    .withParcelable("book", new Books("ShuiXu", 396, 3012345, "HuNanPress"))
                    .navigation();
        }));

进阶用法之拦截器:

拦截器是ARouter这一款框架的亮点。ARouter中的拦截器就实现了这种功能,可以在跳转过程中添加自定义的功能,比如添加携带参数,判断是否需要登录等,是针对AOP切面编程思想的实现。

ARouter的拦截器,是通过实现 IInterceptor接口,重写init()和process()方法去完成拦截器内部操作的。

首先我们定义两个拦截器:

@Interceptor(priority = 7)
public class SaidInterceptor implements IInterceptor {
    private static final String TAG = "LoginInterceptor";

    private Context mContext;

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        Log.i(TAG, "SaidInterceptor process");
    }

    @Override
    public void init(Context context) {
        mContext = context;

        Log.i(TAG, "SaidInterceptor init");

    }
}
@Interceptor(priority = 1)
public class LoginInterceptor implements IInterceptor {
    private static final String TAG = "LoginInterceptor";

    private Context mContext;

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {

        String name=Thread.currentThread().getName();
        Log.i(TAG, "LoginInterceptor begain to process"+"thread name is"+name);
        if (postcard.getPath().equals("/moduleone/ModulerMainActivity")){
            Log.i(TAG, "LoginInterceptor process is interceptor ");
        }
    }

    @Override
    public void init(Context context) {
        mContext = context;

        Log.i(TAG, "LoginInterceptor init");

    }
}
2-26 08:05:32.562 7115-7138/ming.com.andcode I/LoginInterceptor: LoginInterceptor init
12-26 08:05:32.562 7115-7138/ming.com.andcode I/LoginInterceptor: SaidInterceptor init
12-26 08:05:43.127 7115-7115/ming.com.andcode I/LoginInterceptor: Interceptor onFound
12-26 08:05:43.129 7115-7137/ming.com.andcode I/LoginInterceptor: LoginInterceptor begain to processthread name isARouter task pool No.1, thread No.3
12-26 08:05:43.129 7115-7137/ming.com.andcode I/LoginInterceptor: LoginInterceptor process is interceptor 

根据实验得知,使用Interceptor类注解的priority数值越小,越先执行,优先级越高。(四大组件中的广播,优先级的取值是 -1000到1000,数值越大优先级越高)

通过Postcard可以获取到路径的组以及全路径,那么,路径的组(Group)又是什么?是这样,一般来说,ARouter在编译期框架扫描了所有的注册页面/字段/拦截器等,那么很明显运行期不可能一股脑全部加载进来,这样就太不和谐了。所以就使用分组来管理,我们的类标签里面的注释,对于group默认是 “ ”(空字符串)如下图:

20181226080846.png

在 Group简单使用 这张图上面,根据日志,打印了分组的信息,可以发现Group的值默认就是第一个 / /(两个分隔符) 之间的内容。

​ 那么,我们也可以自定义分组,来进行界面跳转,所以ARouter又提供了一种解决方案:

自定义分组 实现跳转界面

如果使用自定义分组来跳转界面,只需要在源代码改动以下三个位置:

1:类注解新增 group,赋值我们自定义的组名

    tv_arouter.setOnClickListener((view -> {
            ARouter.getInstance().build("/moduleone/ModulerMainActivity", "module")
                    .withString("name", "DeLux")
                    .withInt("age", 25)
                    .withParcelable("book", new Books("ShuiXu", 396, 3012345, "HuNanPress"))
                    .navigation(MainActivity.this, new NavigationCallback() {
                        @Override
                        public void onFound(Postcard postcard) {
                            String group = postcard.getGroup();
                            String path = postcard.getPath();
                            Log.i(TAG, "Interceptor onFound group is " + group + " path is " + path);
                        }

                        @Override
                        public void onLost(Postcard postcard) {
                            Log.i(TAG, "Interceptor onLost");
                        }

                        @Override
                        public void onArrival(Postcard postcard) {
                            Log.i(TAG, "Interceptor onArrival");
                        }

                        @Override
                        public void onInterrupt(Postcard postcard) {
                            Log.i(TAG, "Interceptor onInterrupt");
                        }
                    });
        }));
@Route(path = "/app/MainActivity", group = "demo")
public class MainActivity extends AppCompatActivity {
    
}
@Route(path = "/moduleone/ModulerMainActivity",group = "module")
public class ModulerMainActivity extends AppCompatActivity {
    
}
12-26 08:26:20.646 7488-7511/ming.com.andcode I/LoginInterceptor: LoginInterceptor init
12-26 08:26:20.647 7488-7511/ming.com.andcode I/LoginInterceptor: SaidInterceptor init
12-26 08:26:28.948 7488-7488/ming.com.andcode I/LoginInterceptor: Interceptor onFound group is module path is /moduleone/ModulerMainActivity
12-26 08:26:28.950 7488-7512/ming.com.andcode I/LoginInterceptor: LoginInterceptor begain to processthread name isARouter task pool No.1, thread No.5
12-26 08:26:28.950 7488-7512/ming.com.andcode I/LoginInterceptor: LoginInterceptor process is interceptor 

通过Postcard可以获取到路径的组以及全路径,那么,路径的组(Group)又是什么?是这样,一般来说,ARouter在编译期框架扫描了所有的注册页面/字段/拦截器等。

ARouter如何实现类似startActivityForResult()?

这种应用场景也是很常见的,那ARouter该如何实现?(需将项目中拦截器注释掉,因为本博客拦截器未做其他处理,将跳转拦截了)

第一步:为了方便看效果,我们在第一个Activity设置requestCode 为1314。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case 1314:
                Log.i(TAG, "onActivityResult  resultCode is " + requestCode+" resultCode is "+resultCode);
                break;
            default:
                break;
        }
    }

第二步:需要在跳转的navigation方法(这是一个方法重载)里面的第二个参数,设置我们定义的requestCode,(通过匹配requestCode 来实现该功能)

 tv_arouter.setOnClickListener((view -> {
            ARouter.getInstance().build("/moduleone/ModulerMainActivity", "module")
                    .withString("name", "DeLux")
                    .withInt("age", 25)
                    .withParcelable("book", new Books("ShuiXu", 396, 3012345, "HuNanPress"))
                    .navigation(MainActivity.this,1314);
        }));

第三步:在第二个界面的setResult方法里面,写上对应的resultCode,这里就不展示Intent数据了


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_moduler_main);
        mShowMsg = findViewById(R.id.tv_module_two_show);
        // ARouter会自动对字段进行赋值,无需主动获取
        ARouter.getInstance().inject(this);
        printMsg();
        setResult(1314);
    }

综合上面三个步骤,项目编译运行,跳转到第二个界面然后返回上一个界面,日志成功打印:

12-26 08:42:02.145 8060-8080/ming.com.andcode I/LoginInterceptor: LoginInterceptor init
12-26 08:42:02.146 8060-8080/ming.com.andcode I/LoginInterceptor: SaidInterceptor init
12-26 08:42:26.819 8060-8083/ming.com.andcode I/LoginInterceptor: LoginInterceptor begain to processthread name isARouter task pool No.1, thread No.4
12-26 08:42:26.819 8060-8083/ming.com.andcode I/LoginInterceptor: LoginInterceptor process is interceptor 
12-26 08:43:48.675 8293-8293/ming.com.andcode I/LoginInterceptor: onActivityResult  has receieve the msg requestCode is 1314 resultCode is 1314

ARouter路由框架的基本使用就介绍到这里,下一篇我们将仿照ARouter自定义一个路由框架,大家一起来嗨。

代码已上传github,点击此处即可到达

上一篇下一篇

猜你喜欢

热点阅读