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

概述

从 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等等;

例如,下面是模块化之前和模块化之后的项目的目录结构:
阿里ARouter路由实现Android模块化开发
模块化的示意图可以用下面的模型表示:
阿里ARouter路由实现Android模块化开发

模块化要解决的问题

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

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

阿里ARouter路由实现Android模块化开发
这是一个常见的首页画面,该页面主要有首页、微聊、推荐和我的组成。我们将该4个Tab单独成4个独立的模块。
阿里ARouter路由实现Android模块化开发

其中:

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

上一篇:如何看待EOS使用的 DPOS 机制


下一篇:Android性能分析工具简介