Fork me on GitHub

安卓组件化开发指南系列(二)

安卓组件化开发指南系列(二)

上一篇文章中提到的是关于组件化的一些配置,这篇讲下组件化开发中可能会遇到的坑

1.AndroidManifest的合并问题

1.1 问题提出

正如我们之前提到的,每个人在开发自己业务组件会单独进行编译运行,则每个组件会有自己的一份AndroidManifest文件,其中肯定会有每个组件的application类和一些属性,以及四大组件注册,权限等。因此可想而知,当我们合并每个业务组件的时候,势必会产生冲突,那么要怎么解决呢?从上篇文章的设置一个开关来判断各个组件是否单独运行的思路,我们可以有如下解决方案。

1.2 解决思路

根据上一篇文章中设置的isSigleModuleBuild的值进行设置两份不同的AndroidManifest文件,一张用于单独组件开发运行使用,一张用于合并到主工程时使用,当我们的组件注册四大组件和添加权限的时候,两个文件都要添加。

1.3 代码实现

  • 在每个业务组件的main文件夹中建立一个manifest文件夹,再在其中建立两个文件夹(一般名为debug和release,见名知意),在debug文件夹中放置的是组件单独运行所需的AndroidManifest文件,release文件夹放置的是合并时候的AndroidManifest文件。
  • debug和release下的AndroidManifest文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//举例
//集成模式下
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yuzeduan.mylibrary">

<application>
<activity android:name="..." />
</application>
</manifest>

//组件模式下Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yuzeduan.mylibrary">
<application
android:name="..."
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/AdmTheme">

<activity android:name="...">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
  • 对比上面的两个文件,我们可以发现:
    • debug文件夹下的文件中有自定义的application类,和一些application属性,而release中没有。
    • debug文件夹下的该activity设置主activity,而release中没有。
  • 在每个module的bulid.gradle中添加如下代码
1
2
3
4
5
6
7
8
9
sourceSets {
main {
if (isSigleModuleBuild.toBoolean()) {
manifest.srcFile 'src/main/manifest/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/manifest/release/AndroidManifest.xml'
}
}
}

1.4 补充点

  • 一个app只能有一个入口Activity,因此有一个module中的集成AndroidManifest文件要有定义一个入口Activity,其他的用于集成的AndroidManifest文件的Activity不能定义为入口Activity。
  • 四大组件的声明除了主Activity有点特殊之外,其他都是和之前声明那样即可,合成的时候,会集成到主moduleAndroidManifest文件中。
  • 关于权限的声明,不同module允许重复声明同一个权限,集成的时候相同的权限只会被声明一次。也可以在BaseModule中声明全部的权限,这样就不用在每个业务module中重复去声明权限。
  • 关于shareUid,它的作用是拥有同一个UserId的多个app可以配置成运行在同一个进程中的,所以默认可以互相访问数据,然而只有在主module中声明的sharedUserId才会最终被打包。

2. Application的冲突

2.1 问题提出

众所周知,当app启动的时候,系统会为每个app创建一个Application类的对象,而且只创建一个,Application类的对象的生命周期是最长的,它的生命周期就等于这个程序的生命周期。一般情况下,如果我们自定义application,系统会自动帮我们生成Application对象,如果自定义的话,就需要在AndroidManifest中进行声明我们自定义的Application。

在组件化开发中,如果我们需要在业务组件中获取全局的context,此时,在业务组件中自定义application,并在debug的AndroidManfiest中进行声明可以解决。然而,进行集成的时候,便会出现各个组件的Application的冲突,并且原先组件的Application是不可用的了。因此,现在的问题是如何实现在每个业务组件中可以获取到全局的context,并且在组件开发模式和在集成开发模式都是适用的

2.2 解决思路

从问题的角度出发,每个业务组件都要实现的,因此我们可以自然而然的想到我们的基础组件。

  • 我们可以定义一个BaseApplication继承自Application,在该类中提供获取全局context的接口,在业务组件中继承BaseAplication,自定义Application,用于在组件开发模式下使用,此时我们就需要在debug文件夹中的AndroidManifest中声明自定义的application,而正是这个声明,当程序运行时候,我们的BaseAppication就会被动实例化,于是从BaseApplication中获取到的context便会生效了。
  • 组件开发模式解决了,现在要解决集成开发模式。自定义的application是不能用的了,因此它集成的时候会被忽略(下面会说)。此时,我们就需要在主module中进行自定义我们的Application类并继承自BaseApplication,并在其AndroidManifest中进行声明,这样子集成之后,当整个项目运行之后,BaseApplication会被被动实例化,所以各个业务组件中获取的全局context便会生效。

2.3 代码实现

  • BaseApplication的实现便不提了,主要是实现获取全局context和进行一些第三方库的初始化。
  • 在集成的时候如何忽略我们自定义的Application类?此时又要想起我们刚刚根据isSigleModuleBuild进行更换AndroidManifest的实现,我们可以在release模式下,忽略掉我们的自定义Application。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//我们可以在java文件夹中建立一个debug文件夹,并把自定义的Application放进去,并修改下面代码
sourceSets {
main {
if (isSigleModuleBuild.toBoolean()) {
manifest.srcFile 'src/main/manifest/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/manifest/release/AndroidManifest.xml'
//release模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
}
}
}

2.4 补充点

  • 在我们进行组件开发模式时候,我们的组件能够单独运行,则运行时候,系统会默认创建一个Application类对象(如果不是自定义),然而,由于集成之后依旧能获取到全局context,我们就必须去自定义Application继承BaseApplication。其实,自定义Application还有一个作用,便是可以在组件开发模式下,为我们的业务组件进行初始化一些需要的数据
  • 在debug文件夹也可创建Activity,用于给我们组件的目标Activity提供数据,它不需要进行显示,只需要模拟一个外界的角色,使我们的目标Activity获取到数据(适用于目标Activity需要外界提供数据启动的情况),同样的,集成时候,它不需要被集成来项目里,并且,release下的AndroidManifest也不需要对它的声明。

3. 第三方库重复依赖

3.1 问题提出

  • 在我们的组件化模型中,会出现一种情况,就是例如我们现在每个业务组件全部依赖基础组件,而我们的app的主module在集成模式下也会依赖基础组件,并依赖各个业务组件,此时是不是出现了业务组件被重复依赖了。其实不然,因为在打包进app的时候,这些组件都会是打包成aar包,然后被app壳工程依赖,在构建app的过程中,Gradle会自动将重复的aar包排除。
  • 虽然组件不会出现重复依赖,但是我们的第三方库却会产生重复依赖,尽管我们已经使用了基础组件提供第三方库的统一依赖,极大的减少了重复依赖库的情况,但是还是有一种情况是无法避免的,那就是我们依赖的第三方库也会有依赖的库,而在我们的module中同样也依赖了这些库,这就出现了第三方库的重复依赖了。

3.2 解决思路

  • 我们也可以在引进第三方库的时候,设置不引入它所依赖的库来解决重复依赖。
1
2
3
//假如我们的module有下面依赖
implementation 'com.android.support:appcompat-v7:25.0.0'
implementation 'com.allenliu.versionchecklib:library:2.0.5'
  • 在我们第三方库中其实已经依赖了我们所依赖的support库,此时就出现了该库的重复依赖了,此时我们在依赖我们的第三方库的时候把support库不引入即可。
1
2
3
4
implementation ('com.allenliu.versionchecklib:library:2.0.5') {
exclude group: 'com.android.support.v4' //用包名
exclude module: 'support-v4'//根据组件名排除
}
  • 还有一种情况就是你导入了两个第三方的库,那如果这两个库刚好都是依赖了另外同一个第三方库,此时有如下的解决策略
    • 如果两个库都是远程依赖这个冲突的库,则可以使用上面exclude的方法进行解决。
    • 如果一个库是用远程依赖,一个是用jar包依赖这个冲突的库,则可以在引用那个使用远程依赖的库的时候,使用exclude解决。
    • 如果两个库都是使用jar包的形式本地依赖这个冲突的库,只能手动下载其中一个库,然后删除掉它这个冲突库的jar包。

4.ButterKnife的坑

在组件化使用ButterKnife会出现找不到资源的问题。

如何解决?

  • 在全局的build,gradle中引入ButterKnife的时候,不要用最新的8.5.1而应该使用8.4.0
1
2
3
4
dependencies {
...
classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'
}
  • 在模块的build.gradle依赖,因为我们是在BaseModule,因此就指它,此处依赖可以使用最新的版本的8.5.1。
1
2
implementation 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
  • 在模块中使用的时候,使用R2代替R,绑定view的时候。
1
@BindView(R2.id.textView);
  • 使用onClick方法的时候,绑定时用R2,查找的时候使用R,且不能使用switch-case,只能使用if-else
1
2
3
4
@OnClick({R2.id.textView, R2.id.textView1)
public void onViewClick(View view){
if..else..
}
-------------本文结束感谢您的阅读-------------

本文标题:安卓组件化开发指南系列(二)

文章作者:AllenYu

发布时间:2018年11月27日 - 23:11

最后更新:2018年12月01日 - 14:12

原始链接:http://yuzeduan.github.io/2018/11/27/安卓组件化开发指南系列(二)/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。