博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
阿里ARouter路由实现Android模块化开发
阅读量:5835 次
发布时间:2019-06-18

本文共 10325 字,大约阅读时间需要 34 分钟。

概述

从 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.4annotationProcessor '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=trueisNeedChatModule=false#isNeedChatModule=falseisNeedRecomModule=false#isNeedRecomModule=falseisNeedMeModule=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拦截要去掉,不然会有多个桌面入口。

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
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的功能远不止于此,后面将为大家一一讲解,并最终自己实现一个模块间的路由。

你可能感兴趣的文章
《写给大忙人看的java se 8》笔记
查看>>
倒计时:计算时间差
查看>>
Linux/windows P2V VMWare ESXi
查看>>
Windows XP倒计时到底意味着什么?
查看>>
运维工程师在干什么学些什么?【致菜鸟】
查看>>
Linux中iptables详解
查看>>
java中回调函数以及关于包装类的Demo
查看>>
maven异常:missing artifact jdk.tools:jar:1.6
查看>>
终端安全求生指南(五)-——日志管理
查看>>
Nginx 使用 openssl 的自签名证书
查看>>
创业维艰、守成不易
查看>>
PHP环境安装套件:快速安装LAMP环境
查看>>
CSS3
查看>>
ul下的li浮动,如何是ul有li的高度
查看>>
C++ primer plus
查看>>
python mysqlDB
查看>>
UVALive 3942 Remember the Word Tire+DP
查看>>
从微软的DBML文件中我们能学到什么(它告诉了我们什么是微软的重中之重)~目录...
查看>>
被需求搞的一塌糊涂,怎么办?
查看>>
c_数据结构_队的实现
查看>>