您现在的位置是:首页 > 文章详情

阿里ARouter路由实现Android模块化开发

日期:2018-04-23点击:328

概述

从 2016 年开始,模块化在 Android 社区越来越多的被提及。随着移动平台的不断发展,移动平台上的软件慢慢走向复杂化,体积也变得臃肿庞大,为了降低大型软件复杂性和耦合度,同时也为了适应模块重用、多团队并行开发测试等等需求,模块化在 Android 平台上变得势在必行。阿里 Android 团队在年初开源了他们的容器化框架 Atlas 就很大程度说明了当前 Android 平台开发大型商业项目所面临的问题。

那么什么是模块化呢,和我们常说的组件化又有什么联系和区别呢?根据《 Java 应用架构设计:模块化模式与 OSGi 》一书中对模块化的定义:模块化是一种处理复杂系统分解为更好的可管理模块的方式。对于这种概念性的解释,太过生涩难懂,不够直观。

那么究竟何为模块化呢?举个例子,相信随着业务的不断迭代,APK项目已经无限大了,以我们公司的电商项目为例,在迭代了5年后,apk的体积已经40M+,如果使用传统的ant打包大概差不多要近10分钟,如果用增量打包时间也要3-5分钟。但是可以发现,很多老的代码其实我们在最新的版本是不需要的,当然我们可以手动的将这些代码删除,但是又还怕啥时候用到。此时,最好的方法就是将这些模块独立成一个独立的工程,当需要的时候再引入进来,这就是模块化的一个背景。

所以,此处,我们对模块化和组件化做一个简单的定义:
模块化:指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,如订单模块(OrderModule)、特卖模块(SPecialModule)、即时通讯模块(InstantMessagingModule)等等。

组件化:组件是指通用的功能或者UI库可以做成一个功能组件,如地图组件(MapSDK)、支付组件(AnjukePay)、路由组件(Router)等等;

插件化:和模块化差不多,只是它是可以把模块打包成独立的apk,可以动态的加载,删除,独立的插件apk可以在线下载安装,有利于减少apk的体积和实现模块的热修复。目前热门的插件化方案有:阿里的atlas,360公司的RePlugin,滴滴的VirtualAPK等等;

例如,下面是模块化之前和模块化之后的项目的目录结构:
这里写图片描述
模块化的示意图可以用下面的模型表示:
这里写图片描述

模块化要解决的问题

要使用模块化开发Android项目,有以下几点需要注意:

  1. 模块间页面跳转(路由);
  2. 模块间事件通信;
  3. 模块间服务调用;
  4. 模块的独立运行;
  5. 其他注意事项;
    为了方便讲解,我们以下面的项目为例:

这里写图片描述
这是一个常见的首页画面,该页面主要有首页、微聊、推荐和我的组成。我们将该4个Tab单独成4个独立的模块。
这里写图片描述

其中:

  • app模块:主模块,主要进行搭载各个模块的功能;
  • lib_base:对ARouter进行初始化,和放置一些各个模块公用的封装类;
  • module_home,module_caht,module_recom,module_me:分别对应“首页”、“微聊”、“推荐”、“我的”模块。

ARouter模块化开发

ARouter各个模块的gradle配置

app模块是程序的容器,起到程序入口的作用,lib_base作为基础模块,用来将一些公共的库和对ARouter的初始化操作放在这一模块中。因此每个子模块都会用到它里面的内容,所以我们在 lib_base中添加如下内容。

 compile 'com.alibaba:arouter-api:1.2.4' annotationProcessor "com.alibaba:arouter-compiler:1.1.4" compile 'com.android.support:design:27.1.1' compile 'org.simple:androideventbus:1.0.5.1' compile 'com.alibaba:fastjson:1.2.31'

因为我们把拦截器等公用类放在base注册,在编译期间生成路径映射。所以还需要在build中加入如下配置:

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

由于每个子模块都会用到lib_base里面的东西,所以需要在各子模块的build文件中导入(即module_home,module_caht,module_recom,module_me等模块中)如下配置:

//注意,此处也需要引入了com.alibaba:arouter-compiler:1.1.4 annotationProcessor 'com.alibaba:arouter-compiler:1.1.4' compile project(':lib_base')

同样,也需要在各子模块的build中加入如下配置。

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

然后在app模块(也即是主模块)对各个子模块进行依赖。

compile project(':module_home') compile project(':module_chat') compile project(':module_recom') compile project(':module_me')

子模块依赖规则配置

对于模块化项目,每个单独的Module 都可以单独编译成 APK。在开发阶段需要单独打包编译,项目发布的时候又需要它作为项目的一个 Module 来整体编译打包。简单的说就是开发时是 Application,发布时是 Library。所以,需要在子模块中做如下的配置:

if(isBuildModule.toBoolean()){ apply plugin: 'com.android.application' }else{ apply plugin: 'com.android.library' }

同理,Manifest.xml 也需要有两套:

if (isBuildModule.toBoolean()) { manifest.srcFile 'src/main/debug/AndroidManifest.xml' } else { manifest.srcFile 'src/main/release/AndroidManifest.xml' }

同时,每个子模块的defaultConfig还需要增加如下配置:

defaultConfig { if (!isNeedMeModule.toBoolean()) { applicationId "com.xzh.module_me" } }

而上面的isBuildModule.toBoolean()判断条件,读取的是项目根目录下的gradle.properties配置文件。

# 是否需要单独编译 true表示需要,false表示不需要 isNeedHomeModule=false #isNeedHomeModule=true isNeedChatModule=false #isNeedChatModule=false isNeedRecomModule=false #isNeedRecomModule=false isNeedMeModule=false #isNeedMeModule=false

然后根据上面的编译配置在app模块中添加如下依赖:

if (!isNeedHomeModule.toBoolean()) { compile project(':module_home') } if (!isNeedChatModule.toBoolean()) { compile project(':module_chat') } if (!isNeedRecomModule.toBoolean()) { compile project(':module_recom') } if (!isNeedMeModule.toBoolean()) { compile project(':module_me') }

如果需要单独运行某个模块时,只需要修改gradle.properties对应的配置即可。例如,需要单独运行module_home模块时,只需要开启对于的配置即可isNeedHomeModule=true。

配置注意

由于配置后项目只有一个入口和启动文件(即app模块的MainActvity),所以其他子模块的MainActivity的intent-filter拦截要去掉,不然会有多个桌面入口。

<activity android:name=".MainActivity"> <!--<intent-filter>--> <!--<action android:name="android.intent.action.MAIN" />--> <!--<category android:name="android.intent.category.LAUNCHER" />--> <!--</intent-filter>--> </activity>

ARouter使用

以上面的效果实现为例,在MainActivity中使用TabLayout+Adapter的形式搭建4个Tab页面。代码如下:

public class MainActivity extends AppCompatActivity { private ViewPager mMViewPager; private TabLayout mToolbarTab; private int[] tabIcons = { R.drawable.tab_home, R.drawable.tab_weichat, R.drawable.tab_recommend, R.drawable.tab_user }; private String[] tab_array; private DemandAdapter mDemandAdapter; private List<Fragment> fragments = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { initData(); initView(); setViewPagerAdapter(); setTabBindViewPager(); setItem(); } private void initData() { tab_array = getResources().getStringArray(R.array.tab_main); fragments.clear(); fragments.add(FragmentUtils.getHomeFragment()); fragments.add(FragmentUtils.getChatFragment()); fragments.add(FragmentUtils.getRecomFragment()); fragments.add(FragmentUtils.getMeFragment()); } private void initView() { mMViewPager = (ViewPager) findViewById(R.id.mViewPager); mToolbarTab = (TabLayout) findViewById(R.id.toolbar_tab); } private void setViewPagerAdapter() { mDemandAdapter = new DemandAdapter(getSupportFragmentManager(),fragments); mMViewPager.setAdapter(mDemandAdapter); } private void setTabBindViewPager() { mToolbarTab.setupWithViewPager(mMViewPager); } private void setItem() { for (int i = 0; i < mToolbarTab.getTabCount(); i++) { mToolbarTab.getTabAt(i).setCustomView(getTabView(i)); } } public View getTabView(int position) { View view = LayoutInflater.from(this).inflate(R.layout.item_tab, null); ImageView tab_image = view.findViewById(R.id.tab_image); TextView tab_text = view.findViewById(R.id.tab_text); tab_image.setImageResource(tabIcons[position]); tab_text.setText(tab_array[position]); return view; } }

然后,使用ARouter来获取到各个模块的Fragment。

public class FragmentUtils { public static Fragment getHomeFragment() { Fragment fragment = (Fragment) ARouter.getInstance().build(RouteUtils.Home_Fragment_Main).navigation(); return fragment; } public static Fragment getChatFragment() { Fragment fragment = (Fragment) ARouter.getInstance().build(RouteUtils.Chat_Fragment_Main).navigation(); return fragment; } public static Fragment getRecomFragment() { Fragment fragment = (Fragment) ARouter.getInstance().build(RouteUtils.Recom_Fragment_Main).navigation(); return fragment; } public static Fragment getMeFragment() { Fragment fragment = (Fragment) ARouter.getInstance().build(RouteUtils.Me_Fragment_Main).navigation(); return fragment; } }

而FragmentUtils使用了RouteUtils来定义具体的跳转协议。

public class RouteUtils { public static final String Home_Fragment_Main = "/home/main"; public static final String Chat_Fragment_Main = "/chat/main"; public static final String Recom_Fragment_Main = "/recom/main"; public static final String Me_Fragment_Main = "/me/main"; }

上面的子模块使用的是Fragment,所以,在子模块中要使用Route说明。例如:

@Route(path = RouteUtils.Chat_Fragment_Main) public class MainFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_weichat, null); return view; } @Override public void onDestroyView() { super.onDestroyView(); } }

跨模块跳转

假如要实现跨模块跳转,首先在RouteUtils定义

public static final String Me_Login = "/me/main/login";

ARouter要跳转Activity,就在这个Activity上加入注解。

@Route(path = RouteUtils.Me_Login) public class LoginActivity extends AppCompatActivity{ }

然后在需要跳转的地方添加如下代码:

ARouter.getInstance().build(RouteUtils.Me_Login).navigation();

实现ForResult返回数据

如果跨模块跳转需要返回数据,即Activity的StartActivityForResult,则可以使用下面的方式。

ARouter.getInstance().build(RouteUtils.Chat_ForResult).navigation(this, 666); //666即为Code

接收数据数据:

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case 666: String name = data.getStringExtra("name"); UIUtils.showToast(name + ",resultCode===>" + resultCode); break; default: break; } }

然后,接受返回数据:

Intent intent = new Intent(); intent.putExtra("name", "ForResult返回的数据"); setResult(999, intent); finish();

使用Eventbus跨模块通信

使用Eventbus进行跨模块通信,首先在需要接受的地方定义一个订阅者。

@Subscriber(tag = EvenBusTag.GOTO_EVENTBUS) public void onEvent(String s) { UIUtils.showToast(s); }

然后在发送方使用EventBus发送消息。例如:

@Route(path = RouteUtils.Me_EventBus) public class EventBusActivity extends AppCompatActivity implements View.OnClickListener { /** * eventBus数据接收页面 */ private TextView mTextView; /** * eventBus返回数据 */ private Button mBtnBackData; private String name; private long age; private EventBusBean eventbus; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_event_bus); ARouter.getInstance().inject(this); initData(); initView(); } private void initData() { name = getIntent().getStringExtra("name"); age = getIntent().getLongExtra("age", 0); eventbus = getIntent().getParcelableExtra("eventbus"); } private void initView() { mTextView = (TextView) findViewById(R.id.textView); mBtnBackData = (Button) findViewById(R.id.btn_back_data); mBtnBackData.setOnClickListener(this); mTextView.setText("name=" + name + ",\tage=" + age + ",\tproject=" + eventbus.getProject() + ",\tnum=" + eventbus.getNum()); } @Override public void onClick(View v) { int i = v.getId(); if (i == R.id.btn_back_data) { EventBus.getDefault().post(name, EvenBusTag.GOTO_EVENTBUS); finish(); } else { } } }

其实,ARouter的功能远不止于此,后面将为大家一一讲解,并最终自己实现一个模块间的路由。

原文链接:https://yq.aliyun.com/articles/584246
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章