Android模块化开发

article/2025/10/22 10:09:24

模块化开发项目搭建

1.为什么要模块化开发

随着APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程下的APP架构势必会影响开发效率,增加项目的维护成本,每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发,而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时,最重要的是这样的代码想要做单元测试根本无从下手,所以必须要有更灵活的架构代替过去单一的工程架构。

2.如何模块化

名词  含义
集成模式    所有的业务组件被“app壳工程”依赖,组成一个完整的APP;
组件模式    可以独立开发业务组件,每一个业务组件就是一个APP;
app壳工程  负责管理各个业务组件,和打包apk,没有具体的业务功能;
业务组件    根据公司具体业务而独立形成一个的工程;
功能组件    提供开发APP的某些基础功能,例如打印日志、树状图等;
Main组件   属于业务组件,指定APP启动页面、主界面;
Common组件    属于功能组件,支撑业务组件的基础,提供多数业务组件需要的功能,例如提供网络请求功能;

3、模块化实施流程

1.设置组建模式和集成模式

1. 创建项目1. 创建一个app的module,也就是main组件2. 基本类库commonlib,存放所有模块共用工具,也就是common组件3. 创建依赖包及版本管理文件version.gradle1. 创建gradle文件2. 然后在项目的build.gradle中添加version.gradle的全局配置apply from: 'version.gradle3. ext是自定义属性,把所有关于版本的信息都利用ext放在另一个自己新建的gradle文件中集中管理//版本信息def build_version=[:]build_version.min_sdk=16build_version.target_sdk = 28build_version.build_tools = "27.0.3"ext.build_version = build_versionext.deps = deps4. 创建module模块如:ccplay模块、other模块 
2. 设置application属性和library属性1. 我们在gradle.properties中定义一个常量值 isModule(是否是组件开发模式,true为是,false为否),/**在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来**/2. 然后我们在业务组件的build.gradle中读取 isModule,但是 gradle.properties 还有一个重要属性: gradle.properties 中的数据类型都是String类型,使用其他数据类型需要自行转换;也就是说我们读到 isModule 是个String类型的值,而我们需要的是Boolean值,代码如下:if (isModule.toBoolean()) {apply plugin: 'com.android.application'} else {apply plugin: 'com.android.library'}   3. 当每次改变isModule的值后,都要同步项目才能生效
3. 修改依赖包到共用模块commonlib中dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])testImplementation 'junit:junit:4.12'api deps.support.v7api deps.support.constraintapi deps.support.multidex}
4. 各个模块中依赖使用公共模块,在模块的build.gradle中添加dependenciesimplementation project(':commonlib')

2.处理组件之间AndroidManifest合并问题

问题

开发时 AndroidManifest.xml 文件的位置是不一样的,我们需要在build.gradle 中指定下 AndroidManifest.xml 的位置,AndroidStudio 才能读取到 AndroidManifest.xml,

解决方法

我们可以为组件开发模式下的业务组件再创建一个 AndroidManifest.xml,然后根据isModule指定AndroidManifest.xml的文件路径,让业务组件在集成模式和组件模式下使用不同的AndroidManifest.xml,这样表单冲突的问题就可以规避了

步骤

1. 在子组件的main文件下创建目录module,添加一个清单文件
2. 在子组件的build.gradle中添加配置,在集成模式和组件模式下分别取不同的清单文件的使用sourceSets {main {if (isModule.toBoolean()) {manifest.srcFile 'src/main/module/AndroidManifest.xml'//集成开发模式下排除debug文件夹中的所有Java文件java {exclude 'debug/**'}} else {manifest.srcFile 'src/main/AndroidManifest.xml'}}}
3.整理不同的清单文件的配置**原因**首先是集成开发模式下的 AndroidManifest.xml,前面我们说过集成模式下,业务组件的表单是绝对不能拥有自己的 Application 和 launch 的 Activity的,也不能声明APP名称、图标等属性,总之app壳工程有的属性,业务组件都不能有**处理完成**module目录下的清单文件内容如下<applicationandroid:theme="@style/AppTheme"><activity android:name=".MainActivity"></activity></application>**注意**在清单文件中声明了主题,而且这个主题还是跟app壳工程中的主题是一致的,都引用了common组件中的资源文件,在这里声明主题是为了方便这个业务组件中有使用默认主题的Activity时就不用再给Activity单独声明theme了外面main目录下的清单文件内容如下<applicationandroid: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"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>

3.全局Context的获取及组件数据初始化

当Android程序启动时,Android系统会为每个程序创建一个 Application 类的对象,并且只创建一个,application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。在默认情况下应用系统会自动生成 Application 对象,但是如果我们自定义了 Application,那就需要在 AndroidManifest.xml 中声明告知系统,实例化的时候,是实例化我们自定义的,而非默认的。

但是我们在模块化开发的时候,可能为了数据的问题每一个组件都会自定义一个Application类,如果我们在自己的组件中开发时需要获取 全局的Context,一般都会直接获取 application 对象,但是当所有组件要打包合并在一起的时候就会出现问题,因为最后程序只有一个 Application,我们组件中自己定义的 Application 肯定是没法使用的,因此我们需要想办法再任何一个业务组件中都能获取到全局的 Context,而且这个 Context 不管是在组件开发模式还是在集成开发模式都是生效的。

在模块化工程模型图中,功能组件集合中有一个 Common 组件, Common 有公共、公用、共同的意思,所以这个组件中主要封装了项目中需要的基础功能,并且每一个业务组件都要依赖Common组件,Common 组件就像是万丈高楼的地基,而业务组件就是在 Common 组件这个地基上搭建起来我们的APP的,Common 组件会专门在一个章节中讲解,这里只讲 Common组件中的一个功能,在Common组件中我们封装了项目中用到的各种Base类,这些基类中就有BaseApplication 类。

BaseApplication 主要用于各个业务组件和app壳工程中声明的 Application 类继承用的,只要各个业务组件和app壳工程中声明的Application类继承了 BaseApplication,当应用启动时 BaseApplication 就会被动实例化,这样从 BaseApplication 获取的 Context 就会生效,也就从根本上解决了我们不能直接从各个组件获取全局 Context 的问题;

这时候大家肯定都会有个疑问?不是说了业务组件不能有自己的 Application 吗,怎么还让他们继承 BaseApplication 呢?其实我前面说的是业务组件不能在集成模式下拥有自己的 Application,但是这不代表业务组件也不能在组件开发模式下拥有自己的Application,其实业务组件在组件开发模式下必须要有自己的 Application 类,一方面是为了让 BaseApplication 被实例化从而获取 Context,还有一个作用是,业务组件自己的 Application 可以在组件开发模式下初始化一些数据,例如在组件开发模式下,A组件没有登录页面也没法登录,因此就无法获取到 Token,这样请求网络就无法成功,因此我们需要在A组件这个 APP 启动后就应该已经登录了,这时候组件自己的 Application 类就有了用武之地,我们在组件的 Application的 onCreate 方法中模拟一个登陆接口,在登陆成功后将数据保存到本地,这样就可以处理A组件中的数据业务了;另外我们也可以在组件Application中初始化一些第三方库。

但是,实际上业务组件中的Application在最终的集成项目中是没有什么实际作用的,组件自己的 Application 仅限于在组件模式下发挥功能,因此我们需要在将项目从组件模式转换到集成模式后将组件自己的Application剔除出我们的项目;在 AndroidManifest 合并问题小节中介绍了如何在不同开发模式下让 Gradle 识别组件表单的路径,这个方法也同样适用于Java代码;

处理步骤 1. 在module目录下创建一个debug文件,用于存放不再组件模式下引用的类,例如application,也可以在 debug 文件夹中创建一个Activity,然后组件表单中声明启动这个Activity,在这个Activity中不用setContentView,只需要在启动你的目标Activity的时候传递参数就行,这样就就可以解决组件模式下某些Activity需要getIntent数据而没有办法拿到的情况,代码如下

public class LauncherActivity extends AppCompatActivity {@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);request();Intent intent = new Intent(this, TargetActivity.class);intent.putExtra("name", "avcd");intent.putExtra("syscode", "023e2e12ed");startActivity(intent);finish();
}//申请读写权限
private void request() {AndPermission.with(this).requestCode(110).permission(Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.CAMERA,     Manifest.permission.READ_PHONE_STATE).callback(this).start();
}}
  1. 在commonlib下创建基础的BaseApplication,然后让各个模块继承

    public class BaseApplication extends MultiDexApplication {private static BaseApplication instance;@Override
    public void onCreate() {super.onCreate();instance = this;
    }public static BaseApplication getInstance(){return instance;
    }
    }
    
  2. 各个模块的debug目录下的application中添加BaseApplication的继承,例如

    public class OtherAplication extends BaseApplication {
    }
    
  3. 因为在集成模式下我们使用统一的一个application,所以需要在集成模式下过滤掉模块的application,和一些组件模式下的文件类。在每个组件的build.gradle的sourceSets中过滤掉debug目录下的文件类

    java {exclude 'debug/**'}
    

4.依赖包文件的处理

原因

  1. 每个项目模块都会有很多的依赖包,管理很不方便
  2. 每个子模块都依赖相同的包也很多,需要统一处理

解决方式

  1. 创建一个公共的依赖包管理文件,上面提到的version.gradle
  2. 在文件中定义依赖的类库,及版本号码,自定义ext
  3. 在项目的build.gradle中添加version类库的全局使用
  4. 在基础commonlib模块中添加需要的使用的类库
  5. 每个组件都依赖commonlib,也就相当于依赖了它的类库,方便使用用。

5.组件之间的调用和通信

项目间的通信跳转,我们使用的是阿里巴巴的开源路由项目ARouter

6.模块化时资源名冲突

资源名冲突有哪些?

比如,color,shape,drawable,图片资源,布局资源,或者anim资源等等,都有可能造成资源名称冲突。这是为何了,有时候大家负责不同的模块,如果不是按照统一规范命名,则会偶发出现该问题。 尤其是如果string, color,dimens这些资源分布在了代码的各个角落,一个个去拆,非常繁琐。其实大可不必这么做。因为android在build时,会进行资源的merge和shrink。res/values下的各个文件(styles.xml需注意)最后都只会把用到的放到intermediate/res/merged/../valus.xml,无用的都会自动删除。并且最后我们可以使用lint来自动删除。所以这个地方不要耗费太多的时间。

解决办法

这个问题也不是新问题了,第三方SDK基本都会遇到,可以通过设置 resourcePrefix 来避免。设置了这个值后,你所有的资源名必须以指定的字符串做前缀,否则会报错。但是 resourcePrefix 这个值只能限定 xml 里面的资源,并不能限定图片资源,所有图片资源仍然需要你手动去修改资源名。

个人建议

将color,shape等放到基础库组件中,因为所有的业务组件都会依赖基础组件库。在styles.xml需注意,写属性名字的时候,一定要加上前缀限定词。假如说不加的话,有可能会在打包成aar后给其他模块使用的时候,会出现属性名名字重复的冲突,为什么呢?因为BezelImageView这个名字根本不会出现在intermediate/res/merged/../valus.xml里, 所以不要以为这是属性的限定词!

问题

1.使用butterknife时运行版本不支持问题

Error:Static interface methods are only supported starting with Android N (--min-api 24): void butterknife.Unbinder.lambda$static$0()
Error:Invoke-customs are only supported starting with Android O (--min-api 26)
Error:com.android.builder.dexing.DexArchiveBuilderException: Failed to process C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\butterknife-runtime-9.0.0-SNAPSHOT.aar\ccbbf27ed1b4a4afbd13c27afe9e6d15\jars\classes.jar

解决方式:可以通过在app的build.gradle文件中配置使用java8编译:

android {...compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
}

2.v4包的依赖

Error:Program type already present: android.support.v4.app.FrameMetricsAggregator$FrameMetricsBaseImpl
解决方式使用java8编译
在module的build.gradle中defaultconfig中添加compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}

3.关于多模块资源文件重名的问题

java.lang.NoSuchFieldError: No field textView of type I in class Lcom/ncr/ncrs/other/R$id; or its superclasses (declaration of 'com.ncr.ncrs.other.R$id' appears in /data/app/com.ncr.ncrs.ncrstudent-2/base.apk)

解决方式:修改每个模块的资源文件,不要有相同命名的文件,给每个资源文件前加一个module的前缀


http://chatgpt.dhexx.cn/article/XrhCwv2k.shtml

相关文章

vue模块化开发

1.前端代码化雏形和CommonJS JavaScript原始功能 在网页开发的早期&#xff0c;js制作作为一种脚本语言&#xff0c;做一些简单的表单验证或者动画实现&#xff0c;代码量比较少&#xff0c;只要写在script标签里面就可以了 随着ajax异步请求的出现&#xff0c;慢慢形成了前…

模块化编程

1.一般编程方式&#xff1a;所有函数放在“.c”文件里。 &#xff08;缺点&#xff1a;若使用的模块功能比较多&#xff0c; 则一个文件内会有很多的代码&#xff0c; 不…

一次跟你说清楚,什么是组件化开发?什么是模块化开发?

网上有许多讲组件化开发、模块化开发的文章&#xff0c;但大家一般都是将这两个概念混为一谈的&#xff0c;并没有加以区分。而且实际上许多人对于组件、模块的区别也不甚明了&#xff0c;甚至于许多博客文章专门解说这几个概念都有些谬误。 想分清这两个概念我觉得结合一下软件…

前端模块化开发

前端模块化开发 什么是模块化&#xff1f; 模块化是指解决一个复杂问题时&#xff0c;自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说&#xff0c;模块是可组合、分解和更换的单元 编程领域中的模块化&#xff0c;就是遵守固定的规则&#xff0c;把一个大文件拆成…

模块化开发

模块化开发 1. 模块化开发最终的目的是将程序划分成 一个个小的结构 。 2. 这个结构中编写属于 自己的逻辑代码 &#xff0c;有 自己的作用域 &#xff0c;定义变量名词时不会影响到其他的结构。 3. 这个结构可以将自己希望暴露的 变量、函数、对象等导出 给其结构使用&#xf…

HttpClient CloseableHttpClient GetMethod PostMethod http

pom依赖 <!--HttpClient的依赖--><dependency><groupId>commons-httpclient</groupId><artifactId>commons-httpclient</artifactId><version>3.1</version></dependency><!--CloseableHttpClient的依赖--><de…

JAVA小工具-05-HttpClient/PostMethod上传文件(解决中文文件名乱码问题)

言于头:本节讨论的是在项目中利用HttpClient/PostMethod相关api进行上传文件操作时&#xff0c;会出现上传中文文件名乱码问题。为解决这个问题&#xff0c;下面是总结的一个HTTP工具类以及测试用例。 public class HttpUtils {public static final String UTF_8 "UTF-8&…

解决PostMethod的中文乱码

解决HttpClient的PostMethod的中文乱码问题 问题场景&#xff1a; 解决代码&#xff1a; 请求时设定编码格式&#xff1a; post.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "utf-8"); 完整代码&#xff1a; /*** 封装请求参数&#xff0…

php 取整,PHP取整的方法有哪些

本篇文章主要给大家介绍PHP取整的四种方法。 PHP实现取整的问题&#xff0c;不仅在我们学习PHP过程中会遇到&#xff0c;在我们PHP面试过程中也是常见的考点之一。 下面我们结合简单的示例给大家总结介绍PHP取整的四种方法。 第一种方法&#xff1a;直接取整&#xff0c;舍弃小…

php 如何取整,解析php取整的几种方式

解析php取整的几种方式 floor 舍去法取整 语法格式:float floor ( float value )返回不大于value 的下一个整数&#xff0c;将value 的小数部分舍去取整。floor() 返回的类型仍然是float&#xff0c;因为float 值的范围通常比integer 要大。 echo floor(4.3); // 4 echo floo…

VUE 数据分页

只要涉及到数据查询&#xff0c;通常我们都会进行分页查询。 假设你的表中有上百万条记录&#xff0c;不分页的话&#xff0c;我们不可能一次性将所有数据全部都载入到前端吧&#xff0c;那前后端都早就崩溃了。 结合 Spring Spring 和 Vue 都提供了开箱即用的分页功能。 S…

前端Vue分页及后端PageHelper分页综合运用

分页显示数据对项目开发中尤为重要&#xff0c;同时能提升用户体验&#xff0c;下面的前端css、js是我引用这篇文章的《使用Vue开发一个分页插件》&#xff0c;我在这个的基础上结合了后端稍微完善了一下&#xff0c;修改了disable的样式&#xff0c;在里面加了pointer-events:…

antd design vue分页组件

我们在使用分页组件的时候可以有两种方法&#xff1a; 第一种是直接用表格()的自定义:pagination属性最方便&#xff1b;如下图所示&#xff1a; 第二种是分页组件 这里我总结的是第二种方法的使用&#xff0c;由于是 Ant Design Vue 的组件&#xff0c;所以必须安装Ant Desig…

Vue分页页码栏设计

Vue分页页码栏设计 效果展示HTML数据需要函数需要运用 效果展示 HTML <div class"page_bar no-select"><ul class"clearfix"><li class"iconfont":class"{vh : currentPage 1}"click"subCurrentPage">&…

超级详细:一个漂亮的Vue分页器组件的实现

整篇分两个部分&#xff1a; 思路部分&#xff1a;讲解怎么实现分页器组件【大把时间看-建议】 后面部分&#xff1a;按照步骤&#xff0c;直接引入组件【没有时间看-建议】 思路&#xff1a;基于连续页码进行判断 需要添加分页器的组件&#xff08;Search组件&#xff09;中…

vue实现分页vue分页查询怎么实现

效果图&#xff1a; 代码&#xff1a; 复制过去即可运行 <!DOCTYPE html> <html lang"en" xmlns:th"http://www.w3.org/1999/xhtml"> <head><meta charset"UTF-8"><title>Title</title><!-- <scrip…

vue分页单位设置为中文格式

根据我搭建前端项目时遇到的问题做一个记录&#xff0c;我下载了一个vue-element-admin前端项目demo&#xff0c;但是默认情况下此demo分页展示时为如下图所示&#xff1a; 遇到此种情况想要调整为中文显示时&#xff0c;如下中文显示案例&#xff1a; 此时需要修改demo项目中…

vue分页器的封装

1.需要注册为全局组件 //main.js // 封装分页器为全局组件 Vue.component(Pagination.name,Pagination); 2.父组件的使用分页器&#xff0c;需要传递相应的参数和自定义事件 <Pagination :pageNo"searchParam.pageNo" :pageSize"searchParam.pageSize"…

django与vue分页

后端django进行自定义分页 1.编写自定义配置文件 from rest_framework.pagination import LimitOffsetPaginationclass LimitPagination(LimitOffsetPagination):max_limit 2 # 最大limit限制&#xff0c;默认Nonedefault_limit 2 # 默认限制&#xff0c;和page_size作用…

Django+vue 分页展示

这里提供两种分页方法 一种是手写分页,不常用,但是明白一下分页的逻辑实现 第二种是用heyui提供的组件.很多功能都给封装好了,用起来也比较美观. 手写分页 后端接口 class GoodList(APIView):def get(self, request):# 当前页page int(request.GET.get(page, 1))# 一页有多…