Android组件化开发简单示例

70 阅读7分钟

3、 输入模块名称   →  Finish

三、组件的build.gradle文件说明

1、通过以上Module模块的创建,包括main组件在内,一共有5个组件,所以对应的有5个组件build.gradle文件,如下图:

2、 main组件的gradle文件中,apply plugin使用的是com.android.application

3、其他业务模块(组件A、组件B、组件C、common组件等),apply plugin使用的是com.android.library 

四、组件集成

各个组件都建立完成之后,接下来可以把组件集成到main组件中,集成非常简单,只需在main组件的gradle文件中添加dependencies{}配置,添加如下语句:

dependencies {

...

//集成组件A

implementation project(':modulea')

//集成组件B

implementation project(':moduleb')

//集成组件C

implementation project(':modulec')

}

如下图: 

五、组件资源共享

1、在common组件的build.gradle文件中,添加android 配置,如下:

android {

//省略前面的代码...

repositories {

flatDir {

dirs 'libs'

}

}

}

在各个需要调用公共common组件的组件build.gradle文件中,也添加android 配置,如下:

android {

//省略前面的代码..

repositories {

flatDir {

dirs '../common/libs/', 'libs'

}

}

}

2、common组件里引入各种类库的时候必须用api,而不是用implementation,原因:

implementation编译的依赖只作用于当前的module,即common组件模块中使用implementation编译的三方库只对common模块起作用,main组件模块中无法使用该三方库。

3、关于组件资源共享,举个简单示例:例如图片都是存放到公共的common组件的res里,那么如何在组件A、组件B、组件C里使用呢?

使用方法如下:

  • 打开各组件的build.gradle文件,在dependencies{}里添加如下代码即可:

dependencies {

...

implementation project(':common')

}

  • 如此一来,就能在组件A里调用common组件的图片资源了

4、同样的道理, 组件A、 组件B、 组件C的颜色代码也可以直接调用公共common组件里colors.xml的代码

5、我们可以把其他第三方库、自定义view、工具类、公用资源都放进公共common组件里,也就是说组件A、组件B、组件C里build.gradle所引入的类库,都可以放到common组件里的dependencies{}里

6、所以各个业务组件里面的build.gradle文件的dependencies{}配置简化后,就变成了下面这样:

7、通过以上解说,大家应该都明白了吧,图片、xml这些(value目录下的各种xml文件),都可以放到公共common组件里,然后再被其他组件引用。对于全局共用的style.xml文件,我们更应该把它放在common组件中,例如我们的项目theme,本来是放在main组件的style里面,我们可以把它移到common中,这样其他组件调试时,作为一个单独的项目,也能和主项目有一样的主题。总而言之,所有你认为可以被各个组件共享的资源,都可以放在common组件中。

六、往组件里添加Fragment

1、以组件A为例,在组件A里添加一个包fragment

2、在fragment包右键   →  New   →  Fragment   →Fragment(Blank)

3、 填写Fragment碎片名称,勾选创建xml文件,如下:

4、对应的fragment_module_a.xml文件代码:

<FrameLayout xmlns:android="schemas.android.com/apk/res/and…"

android:layout_width="match_parent"

android:layout_height="match_parent">

<ImageView

android:layout_width="match_parent"

android:layout_height="match_parent"

android:scaleType="centerCrop"

android:src="@drawable/a" />

<TextView

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:gravity="center"

android:padding="5dp"

android:text="组件A"

android:background="@color/green"

android:textColor="@android:color/white"

android:textSize="24dp" />

5、其他组件也类似组件A一样,创建一个Fragment碎片,然后添加不同的背景图片即可。

6、main组件里添加导航和Fragment容器,main组件里activity_main.xml的代码如下:

<LinearLayout xmlns:android="schemas.android.com/apk/res/and…"

xmlns:app="schemas.android.com/apk/res-aut…"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:id="@+id/container"

android:orientation="vertical">

<FrameLayout

android:id="@+id/content"

android:layout_width="match_parent"

android:layout_height="0dp"

android:layout_weight="1">

<com.google.android.material.bottomnavigation.BottomNavigationView

android:id="@+id/navigation"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_gravity="bottom"

android:background="?android:attr/windowBackground"

app:menu="@menu/navigation" />

7、res下创建一个menu目录,里面添加一个navigation.xml文件,代码如下:

<item

android:id="@+id/navigation_a"

android:icon="@drawable/home"

android:title="组件A" />

<item

android:id="@+id/navigation_b"

android:icon="@drawable/video"

android:title="组件B" />

<item

android:id="@+id/navigation_c"

android:icon="@drawable/me"

android:title="组件C" />

navigation.xml里调用的icon图片分别放到drawable和drawable-24目录里,最终主APP的MainActivity界面如下图:

8、MainActivity.java的代码如下:

package net.zy13.module.demo;

import androidx.annotation.NonNull;

import androidx.appcompat.app.AppCompatActivity;

import androidx.fragment.app.Fragment;

import androidx.fragment.app.FragmentTransaction;

import android.os.Bundle;

import android.view.MenuItem;

import com.google.android.material.bottomnavigation.BottomNavigationView;

import net.zy13.module.modulea.fragment.ModuleAFragment;

import net.zy13.module.moduleb.fragment.ModuleBFragment;

import net.zy13.module.modulec.fragment.ModuleCFragment;

public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

//定义碎片集合

private Fragment[] fragments = new Fragment[3];

//当前显示的fragment的索引位置

private int currentIndex = 0;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initFragment();

BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);

navigation.setOnNavigationItemSelectedListener(this);

}

/**

  • 初始化Fragment碎片

*/

private void initFragment() {

if (fragments[0] == null) {

fragments[0] = new ModuleAFragment();

getSupportFragmentManager().beginTransaction().add(R.id.content, fragments[0], "moduleA").commit();

} else {

getSupportFragmentManager().beginTransaction().show(fragments[0]);

}

}

/**

  • 导航选择事件

  • @param item

  • @return

*/

@Override

public boolean onNavigationItemSelected(@NonNull MenuItem item) {

switch (item.getItemId()) {

case R.id.navigation_a:

if (currentIndex == 0) return true;//如果已经是当前的fragment,不用切换

FragmentTransaction transition0 = getSupportFragmentManager().beginTransaction();

hideAndShow(0,transition0);

return true;

case R.id.navigation_b:

if (currentIndex == 1) return true;//如果已经是当前的fragment,不用切换

FragmentTransaction transition1 = getSupportFragmentManager().beginTransaction();

if (fragments[1] == null) {

fragments[1] = new ModuleBFragment();

transition1.add(R.id.content, fragments[1], "moduleB");

}

hideAndShow(1,transition1);

return true;

case R.id.navigation_c:

if (currentIndex == 2) return true;//如果已经是当前的fragment,不用切换

FragmentTransaction transition2 = getSupportFragmentManager().beginTransaction();

if (fragments[2] == null) {

fragments[2] = new ModuleCFragment();

transition2.add(R.id.content, fragments[2], "modulec");

}

hideAndShow(2,transition2);

return true;

}

return false;

}

/**

  • 除了指定的fragment不hide,其他fragment全hide

  • @param expectIndex 指定的fragment在fragments中的位置

  • @param transition

*/

private void hideAndShow(int expectIndex,FragmentTransaction transition) {

for (int i = 0; i < fragments.length; i++) {

if (i != expectIndex && fragments[i] != null) {

transition.hide(fragments[i]);

}

}

transition.show(fragments[expectIndex]);

transition.commit();

currentIndex = expectIndex;

}

}

9、Run运行项目,最终效果如下图,点击导航,可以切换到对应的组件上:

七、各个组件单独开发(测试)

前面我们把各个组件集成到main组件中,现在我们把组件拆分出来,单独开发,开发测试完成后,再把组件集成到main组件中,最后发布。组件单独出来开发的方法就是:在build.gradle文件中,把apply plugin: 'com.android.library',改成apply plugin: 'com.android.application',也就是把其library模式改为application模式,因为只有application才可以单独运行,library必须依靠application才能运行。

那么问题来了?

组件单独开发时,我们需要改build.gradle的apply plugin模式,等要集成到main组件时,又得改回来,如果这样子手工去改,组件一多,修改起来比较麻烦,也不优雅。优雅的解决办法就是设置一个开关,打开时,就是application模式,可以单独开发;关闭时,就是library模式,可以集成到main组件中。现在按我下面的步骤来实现:

1、在项目根目录下,有一个build.gradle文件,在这个文件最末尾添加一个ext {}配置,然后在ext配置里设定一个常量isDebug,值设为true

ext {

/**

  • 组件调试模式

  • isDebug = true 是组件模块,说明是单独的App

  • isDebug = false是集成模式,说明是依赖Lib

  • 每次更改“isDebug”的值后,需要点击 "Sync Project" 按钮

*/

isDebug = true

}

2、build.gradle里设置了isDebug常量后,我们项目中的其他build.gradle文件都可以把这个常量读取出来,所以我们可以在其他组件的build.gradle文件中,读取该常量的值,动态设置apply plugin,代码如下:

if(isDebug){

apply plugin: 'com.android.application'

}else{

apply plugin: 'com.android.library'

}

3、这样子设置之后,当我们需要切换模式时,只需要修改项目根目录下build.gradle文件中isDebug常量的值,修改完成之后,点击Project sync按钮同步一下即可。如果有报错,那么还有个地方需要修改一下,就是main组件的build.gradle文件,我们把module的模式改成了application,main组件就不能引入application,引入的话就会报错,所以当是debug调试模式时,这里就不引入该组件,以免报错。所以在集成组件前,要先判断是什么模式,如下图:

4、接下来还得修改 AndroidManifest.xml,当把一个module设置为application时,AndroidManifest.xml需要包含一个app所需要的属性,例如app的icon、theme、launch Activity这些属性设置,而当module为library时,这些属性就都不需要用到,所以当我们处于不同模式时,AndroidManifest.xml文件的配置也得不同。方法如下:

(1)、Android目录模式切换到Project目录模式

(2)、 在各个组件的src文件夹中新创建一个debug目录,再把我们用于debug调试的AndroidManifest.xml文件放进去

(3)、调试用的AndroidManifest.xml文件可以直接复制manifests目录里的,然后添加application的基本信息,如下:

<manifest xmlns:android="schemas.android.com/apk/res/and…"

package="net.zy13.module.modulea">

<application

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:roundIcon="@mipmap/ic_launcher_round"

android:supportsRtl="true"

android:theme="@style/AppTheme">

以上内容会有很多错误提示,其实提示的无非就是资源找不到,既然前面我们已经创建了公共的common组件,那么我们只需要把main组件中相应的资源移动到common组件中就可以了。

5、接下来在各个组件的build.gradle文件中,指定不同模式下使用的AndroidManifest.xml文件,在android {}里添加如下代码:

sourceSets {

main {

if (isDebug) {

manifest.srcFile 'src/debug/AndroidManifest.xml'

}else{

manifest.srcFile 'src/main/AndroidManifest.xml'

//集成开发模式下排除debug文件夹中的所有Java文件

java {

exclude 'debug/**'

}

}

}

}

6、以上设置完成,并且sync project(同步项目)之后,各个组件会是这样的目录结构:

7、在各个组件里创建一个用于调试启动的MainActivity活动,然后把fragment加载到activity_main.xml里,所对应的activity_main.xml布局文件如下:

结尾

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,在这里免费分享给大家,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~