作者: Dawish_大D
简书: http://www.jianshu.com/u/40e1f22c2c53
(一)Android官方MVVM框架实现组件化之整体结构
(二)Android官方MVVM框架实现组件化之ARouter串联各模块
目前的项目结构图置顶:Demo的Github地址: https://github.com/Dawish/GoogleArchitectureDemo
ARouter
路由器简直是MVVM组件化的一个天赐之物,非常适合在组件化中使用,可以很方便滴获取Fragment、跳转Activity、拦截跳转、启动服务等等。
ARouter
官方地址:https://github.com/alibaba/ARouter
上面结构图中,module_girls
、module_news
和lib_commoin
中都可能存在Activity
,Fragment
或者Service
这些可以被ARouter
支持的组件,其实ARouter
的跳转是根据我们指定的路径去匹配的,只要路径是匹配的就可以跳转、获取或者是启动,最方便的是还支持参数携带,本文章只是讲一下在MVVM
中ARouter
最方便的使用场景,更多使用详情还请参考官方说明和官方Demo
,官方说得很清楚了。
一、组件化中ARouter使用配置注意事项
本演示项目中App
、Module
和lib
一个有七个,如果是你每一个都用到了ARoute
r的功能,那么就需要在每一个模块的build.gradle
中添加配置,记住是每一个。
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
如果你的模块需要打包成apk
或者是aar
之类的,并且你开取了代码混淆,那么需要在每一个模块的proguard-rules.pro
文件中添加代码keep
:
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
# 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现
-keep class * implements com.alibaba.android.arouter.facade.template.IProvider
二、Activity的跳转和拦截
首先我们需要在公共库 lib_common
中添加一个类来存放ARouter
的跳转获取使用的path
:
package google.architecture.common.base;
/**
* Created by danxx on 2017/11/27.
* 路由path
*
* Aty : Activity
* Fgt : Fragment
*
*/
/**
* Created by danxx on 2017/11/27.
* 路由path
*
* Aty : Activity
* Fgt : Fragment
*
*/
public class ARouterPath {
/**妹子列表Activity*/
public static final String GirlsListAty = "/girls/aty/list";
/**妹子列表动态Activity*/
public static final String DynaGirlsListAty = "/girls/dynaty/list";
/**新闻列表Activity*/
public static final String NewsListAty = "/news/aty/list";
/**妹子列表Fragment*/
public static final String GirlsListFgt = "/girls/aty/fgt/list";
/**新闻列表Fragment*/
public static final String NewsListFgt = "/news/fgt/list";
/**关于Fragment*/
public static final String AboutFgt = "/about/fgt/fragment";
}
需要被跳转的Activity增加Router注解,就是声明已下自己的path路径:
@Route(path = ARouterPath.GirlsListAty)
public class ActivityGirls extends BaseActivity {
GirlsAdapter girlsAdapter;
ActivityGirlsBinding activityGirlsBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("Module_ActivityGirls");
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activityGirlsBinding = DataBindingUtil.setContentView(ActivityGirls.this,R.layout.activity_girls);
GirlsViewModel girlsViewModel = new GirlsViewModel(ActivityGirls.this.getApplication());
girlsAdapter = new GirlsAdapter(girlItemClickCallback);
activityGirlsBinding.setRecyclerAdapter(girlsAdapter);
subscribeToModel(girlsViewModel);
}
GirlItemClickCallback girlItemClickCallback = new GirlItemClickCallback() {
@Override
public void onClick(GirlsData.ResultsBean fuliItem) {
Toast.makeText(ActivityGirls.this, fuliItem.getDesc(), Toast.LENGTH_SHORT).show();
}
};
/**
* 订阅数据变化来刷新UI
* @param model
*/
private void subscribeToModel(final GirlsViewModel model){
//观察数据变化来刷新UI
model.getLiveObservableData().observe(this, new Observer<GirlsData>() {
@Override
public void onChanged(@Nullable GirlsData girlsData) {
Log.i("danxx", "subscribeToModel onChanged onChanged");
model.setUiObservableData(girlsData);
girlsAdapter.setGirlsList(girlsData.getResults());
}
});
}
@Override
protected void onResume() {
super.onResume();
}
}
有兴趣的可以看一下ActivityGirls 的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ActivityGirls">
<data>
<import type="android.view.View"/>
<variable
name="girlsViewModel"
type="google.architecture.coremodel.viewmodel.GirlsViewModel"/>
<variable
name="recyclerAdapter"
type="android.support.v7.widget.RecyclerView.Adapter"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cardview_light_background"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/girls_list_wrapper">
<android.support.v7.widget.RecyclerView
android:id="@+id/girls_list"
android:contentDescription="girls list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:adapter="@{recyclerAdapter}"
app:layoutManager="LinearLayoutManager" />
</FrameLayout>
</FrameLayout>
</LinearLayout>
</layout>
ARouter
的拦截很简单的,写一个类实现IInterceptor
接口,用Interceptor
注解写上拦截器的等级和名字就可以了,不需要额外的去注册拦截器,在ARouter
执行跳转时会先执行拦截器。关于java
AOP
注解的使用请看我之前的文章《大话AOP与Android的爱恨情仇》
/**
* Created by danxx on 2017/11/27.
*
* 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查
* 拦截器会在跳转之前执行,多个拦截器会按优先级顺序依次执行
*
* priority就是优先级 可以设置多个级别的拦截器都活一次执行
* 创建一个实现IInterceptor接口的类就是一个拦截器,不用做额外的配置了
*/
@Interceptor(priority = 8, name = "测试用拦截器")
public class RouterInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
if(postcard.getPath().contains(ARouterPath.GirlsListAty)){
Log.d("danxx", "拦截到向ActivityGirls跳转");
//自定义处理
}else {
Log.d("danxx", "非拦截跳转执行path: "+postcard.getPath());
}
callback.onContinue(postcard); // 处理完成,交还控制权
// callback.onInterrupt(new RuntimeException("我觉得有点异常")); // 觉得有问题,中断路由流程
// 以上两种至少需要调用其中一种,否则不会继续路由
}
@Override
public void init(Context context) {
// 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次
Log.d("danxx", "RouterInterceptor init");
}
}
跳转的时候就厉害了哦,可以支持很多的参数携带,Activity
的跳转还可以自动以转场动画:
private ItemClick itemClick = new ItemClick() {
@Override
public void onClick(int id) {
switch (id){
case R.id.toGirls:
Log.i("danxx", "onClick toGirls");
//跳转到GirlsActivity
ARouter.getInstance()
.build(ARouterPath.GirlsListAty)
/**可以针对性跳转跳转动画*/
.withTransition(R.anim.activity_up_in, R.anim.activity_up_out)
.navigation(ActivityMain.this);
break;
case R.id.toNews:
Log.i("danxx", "onClick toNews");
//跳转到NewsActivity
ARouter.getInstance()
.build(ARouterPath.NewsListAty)
/**可以针对性跳转跳转动画*/
.withTransition(R.anim.activity_up_in, R.anim.activity_up_out)
/**设置跳转回到*/
.navigation(ActivityMain.this, 2, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
Log.i("danxx", "ARouter onFound 找到跳转匹配路径");
}
@Override
public void onLost(Postcard postcard) {
Log.i("danxx", "ARouter onLost 没有匹配到跳转路径");
}
@Override
public void onArrival(Postcard postcard) {
Log.i("danxx", "ARouter onArrival 成功跳转");
}
@Override
public void onInterrupt(Postcard postcard) {
Log.i("danxx", "ARouter onInterrupt 跳转被中断");
}
});
break;
case R.id.toDynamic:
Log.i("danxx", "onClick toNews");
//跳转到ActivityDynamicGirls (模拟动态url)
ARouter.getInstance()
.build(ARouterPath.DynaGirlsListAty)
.withString("fullUrl", "http://gank.io/api/data/%E7%A6%8F%E5%88%A9/20/1")
.withTransition(R.anim.activity_up_in, R.anim.activity_up_out)
/**支持携带requestCode参数,跳转回调onActivityResult方法*/
.navigation(ActivityMain.this, 3);
break;
}
}
};
三、Fragment的获取
看一下需要被获取Fragment:
/**
* @Desc FragmentGirls
*/
@Route(path = ARouterPath.GirlsListFgt)
public class FragmentGirls extends BaseFragment {
FragmentGirlsBinding girlsBinding;
GirlsAdapter girlsAdapter;
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ARouter.getInstance().inject(FragmentGirls.this);
girlsBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_girls,container,false);
girlsAdapter = new GirlsAdapter(girlItemClickCallback);
girlsBinding.setRecyclerAdapter(girlsAdapter);
final GirlsViewModel girlsViewModel = new GirlsViewModel(getActivity().getApplication());
subscribeToModel(girlsViewModel);
return girlsBinding.getRoot();
}
GirlItemClickCallback girlItemClickCallback = new GirlItemClickCallback() {
@Override
public void onClick(GirlsData.ResultsBean fuliItem) {
Toast.makeText(getContext(), fuliItem.getDesc(), Toast.LENGTH_SHORT).show();
}
};
/**
* 订阅数据变化来刷新UI
* @param model
*/
private void subscribeToModel(final GirlsViewModel model){
//观察数据变化来刷新UI
model.getLiveObservableData().observe(FragmentGirls.this, new Observer<GirlsData>() {
@Override
public void onChanged(@Nullable GirlsData girlsData) {
Log.i("danxx", "subscribeToModel onChanged onChanged");
model.setUiObservableData(girlsData);
girlsAdapter.setGirlsList(girlsData.getResults());
}
});
}
}
获取Fragment
的时候也很单纯:
BaseFragment fragmentNews = (BaseFragment) ARouter.getInstance().build(ARouterPath.NewsListFgt).navigation();
BaseFragment fragmentGirls = (BaseFragment) ARouter.getInstance().build( ARouterPath.GirlsListFgt).navigation();
BaseFragment fragmentAbout = (BaseFragment) ARouter.getInstance().build( ARouterPath.AboutFgt ).navigation();
四、ARouter所谓的服务获取
一开始看到这个,我还以为是获取Android中
的Service
,其实不是的,ARouter
所谓的服务就是直接或者是间接实现ARouter
提供的IProvider
接口的类。
首先我们可以先写一个继承IProvider
接口的接口,我们可以在自己的接口中增加自己想要的方法:
/**
* Created by danxx on 2017/11/28.
*
* ARouter所谓的服务就是直接或者是间接实现ARouter提供的IProvider接口的类
*
*/
public interface TestService extends IProvider {
/**增加自己想要的方法*/
String sayHello(String name);
}
实现这个接口:
/**
* Created by danxx on 2017/11/28.
* 实现了一个测试用的服务接口
*/
@Route(path = "/service/test", name = "测试服务")
public class TestServiceImpl implements TestService {
@Override
public String sayHello(String name) {
Log.d("danxx", "TestServiceImpl sayHello : "+name);
return "TestServiceImpl TestServiceImpl TestServiceImpl";
}
@Override
public void init(Context context) {
Log.d("danxx", "TestServiceImpl TestServiceImpl init");
}
}
使用的时候可以有多种方式了:
服务声明:
@Autowired(name = "/service/test")
TestService testService1;
TestService testService2;
TestService testService3;
服务使用:
//注入才可以自动初始化Autowired注解声明的变量
ARouter.getInstance().inject(ActivityMain.this);
//注解的方法
testService1.sayHello("Autowired invoke 233");
//类寻找的方法
testService2 = ARouter.getInstance().navigation(TestService.class);
testService2.sayHello("navigation invoke 233");
//路径匹配的方式
testService3 = (TestService) ARouter.getInstance().build("/service/test").navigation();
testService3.sayHello("build invoke 233");
官方推荐注解的方式,比较靠谱一些。
MVVM中使用ARouter优点总结:
个模块之间更加低耦合,各模块之间不需要关注彼此,要埋头开发自己的功能就行了。
Activity
的跳转、Fragment
的获取、ARouter所谓的Service
的获取,都变得很隐式,执行之前不知道对方具体是谁,只要对方注册了对应的path
路径就可以了。支持
Activity
的跳转的拦截,我测试似乎不支持Fragment
的获取拦截,有些遗憾,在不满足情况的条件下模块可以对外屏蔽。支持
URL
跳转,方便H5
和原生混合开发。
...
五、无耻的预告
下一篇讲解DataBinding在MVVM中的使用,关于更多的ARouter请参考官方说明和官方Demo:
ARouter Github地址: https://github.com/alibaba/ARouter