本文共 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项目,有以下几点需要注意:
其中:
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拦截要去掉,不然会有多个桌面入口。
以上面的效果实现为例,在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 Listfragments = 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();
如果跨模块跳转需要返回数据,即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进行跨模块通信,首先在需要接受的地方定义一个订阅者。
@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的功能远不止于此,后面将为大家一一讲解,并最终自己实现一个模块间的路由。