转载地址: https://blog.youkuaiyun.com/mingyunxiaohai/article/details/78124400
前两年安卓插件化开发在国内进行的如火如荼,出现了不少插件化的框架,360,携程等都开源了比较好的插件化框架,插件化开发确实是可以给开发者带来很大的方便。不过插件化需要解决的问题太多,动态加载类,资源等等,首先就需要对安卓的打包机制,运行机制等等了解的很清楚。对个人的技术要求非常高,这些开源框架或多或少都会有一些bug,在我们改框架的实现机制不理解的情况下,出现的一些bug会让我们手无足措。而且对于这种可以动态更改线上apk的框架谷歌是不推荐的,不经过审核的东西总是存在一些风险。
插件化解决的问题:
(1)不同的模块分开开发
(2)动态更新apk。
组件化开发
(1)不同的模块分开开发
相对来说组件化比插件化简单很多,首先对于线上的apk不会有任何影响,不会有不可控的问题出现,所有的问题编译的时候就会暴露出来。
我理解的组件化开发:
(1)有一个library模块,里面包含了我们日常开发用到的通用工具,如网络框架,图片框架,各种util等等。
(2)每个业务模块都依赖library可以用library中的公共工具。
(3)每个模块之间没有耦合,可以随时加入或者撤除每个模块
(4)每个业务模块可以单独的调试,这样当我们的app已经很大的时候,编译一次是很耗时的,单独调试每个模块会增大我们的开发效率。
(5)多个业务模块可以多个团队同时开发,单独调试。
(6)单独测试摸个模块会更加简单。
所以如果要使用组件化开发,首先需要跟我们的app的业务结合,我们的app需要有很多的不同业务模块,加入只有很少的业务或者业务相同,我感觉也没必要使用组件化。
下面开始实践:
既然是组件化开发当然有多个组件啦,假如我们有两个独立的业务模块《天气模块》《段子模块》。OK捡完工程后的目录结构:
module_library:包含各种工具,网络请求,图片处理,各种util
module_duanzi:段子模块
module_weather:天气模块
依赖关系就是: app依赖 module_duanzi和module_weather ,module_duanzi和module_weather都依赖module_library。不用担心studio打包的时候是不会重复依赖的
既然我们建了这么多的模块
首先遇到的第一个问题就是每个模块的build.gradle文件中的各种版本如编译版本,sdk版本啊等等,需要统一的版本号,这个时候就用到了一个全局的文件 gradle.properties 这个文件中的配置我们可以在所有的build.gradle中访问到。就如同static 静态变量。
所以gradle.properties中就可以这么写
isDebug= false
# 编译的 SDK 版本
COMPILE_SDK_VERSION=25
# 构建工具的版本,其中包括了打包工具aapt、dx等,如API20对应的build-tool的版本就是20.0.0
BUILDTOOLS_VERSION=26.0.0
# 支持包的版本
SUPPORT_LIB_VERSION=25.3.0
# 最小支持sdk
MIN_SDK_VERSION=18
# 当前targetSdkVersion sdk
TARGET_SDK_VERSION=25
# butterknife 版本
BUTTERKNIFE_VERDION = 8.8.0
# Arouter
A_ROUTER_API=1.2.1.1
A_ROUTER_COMPILER=1.1.2.1
#A_ROUTER_ANNOTATION=1.0.3
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
然后在build.gradle中调用 ,部分代码如下:
compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)
buildToolsVersion BUILDTOOLS_VERSION
minSdkVersion Integer.parseInt(MIN_SDK_VERSION)
targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)
compile "com.android.support:appcompat-v7:${SUPPORT_LIB_VERSION}"
compile "com.jakewharton:butterknife:${BUTTERKNIFE_VERDION}"
annotationProcessor "com.jakewharton:butterknife-compiler:${BUTTERKNIFE_VERDION}"
compile "com.alibaba:arouter-api:${A_ROUTER_API}"
annotationProcessor "com.alibaba:arouter-compiler:${A_ROUTER_COMPILER}"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
这样当版本变化的时候,只需更改gradle.properties中的版本号所有的module中的版本号都可以改变
第二个问题组件之间怎么跳转。组件之没有联系,主工程依赖组件,可以通过intent跳转,假如哪天真有一个硬性的问题需要组件之间需要组件之间通讯就无法做到了。而主工程跟组件通过intent连接,不能做到很好的隔离。这时候可以使用路由(路由的原理就跟我们请求一个网页一样,根据一个网址就可以跳转过去了,路由也是,我们可以给我我们的activity配置一个路径,别的地方就可以根据这个路径访问到我们的activity) 路由框架可以使用阿里的ARouter https://github.com/alibaba/ARouter。具体使用方法点击前面的链接,进入 ARouter项目github主页,有详细介绍。
这样我们在每个module的入口activity上配置访问路径
@Route(path = "/duanzi/DuanZiActivity")
public class DuanZiHuActivity extends BaseActivity {}
@Route(path = "/weather/WeatherActivity")
public class WeatherActivity extends BaseActivity {}
- 1
- 2
- 3
- 4
在主工程中一句话跳转就可以啦
@OnClick({R.id.weather,R.id.zhihu})
public void onViewClicked(View view) {
switch (view.getId()){
case R.id.weather:
ARouter.getInstance().build("/weather/WeatherActivity").navigation();
break;
case R.id.zhihu:
ARouter.getInstance().build("/duanzi/DuanZiActivity").navigation();
break;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
组件内的跳转页可以通过ARouter实现啦。不过我觉得还是当我们组件之间跳转的时候使用,或者使用ARouter 的特定的场景(比如我们APP没的推广的界面,活动界面等复杂场景),组件内部还是使用intent。
第三个问题 ButterKnife的使用
ButterKnife在github上的star现在有一万八千多。使用的人更多啦,我们一般是在我们的主工程中使用,那ButterKnife在library中怎么用呢 进入ButterKnife的github主页https://github.com/JakeWharton/butterknife作者已经给出了方法
可以看到在library中使用R2来引用。使用的时候我们可以通过正常的方法或使用插件生成代码后把R手动改成R2之后重新编译一下工程就好啦。
这里需要注意:
ARouter 和 ButterKnife都使用了编译时注解机制。
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
- 1
- 2
根据实践,每个使用到他们功能的模块都需要在build.gradle 写上面的注解管理器的依赖。
第四个问题 防止资源命名重复
module多了之后,每个module中都有自己的资源文件,为了防止命名的重复,我们可以在build.gradle中配置resourcePrefix参数,强制命名的前缀。
比如我们在module_weather的build.gradle文件的android下面的defaultConfig中添加
resourcePrefix "weather_"
- 1
代表这个module中的文件的命名都要以weather_为前缀 。
第五个问题 组件的单独调试
模块化开发的很大的一个优点就是组件的单独调试了,那么怎么单独调试呢。
我们看到 app模块的build,gradle文件的最上面是
apply plugin: 'com.android.application'
- 1
而module模块的build,gradle 最上面是
apply plugin: 'com.android.library'
- 1
说明 app模块是一个正常的app工程,module模块只是一个app的library,所以只有app可以运行,正常情况下我们把module中的library改成application 就可以把它变成一个app工程了。但是一个app的运行需要有一个入口activity啊。我们的 module的manifest中没有入口activity啊。当然我们可以给它加入一个入口activity,但是如果我们的module模块很多的话,每次都这么改多累啊而且容易出错。这个时候就得配置一下啦。
还记的最开始的时候说的gradle全局文件gradle.properties 。在里面定义一个变量isDebug= false 判断是不是调试模式。
然后去每个module的build.gradle中配置。
比如module_weather 中
if (isDebug.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
- 1
- 2
- 3
- 4
- 5
manifest每次更改太麻烦,可以建一个debug文件夹指定一个manifest 比如:
里面指定module的入口activity。另外还得指定application的主题不然会报错。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.chs.weather">
<application
android:name="com.chs.library.base.BaseApplication"
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
>
<activity android:name=".WeatherActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
然后build.gradle中指定manifest的路径
sourceSets {
main {
if (isDebug.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
//release 时 debug 目录下文件不需要合并到主工程
exclude 'debug/**'
exclude '**/debug/**'
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
OK 终于配置完了。这个时候我们将isDebug改为ture,重新编译后看到
这时候我们选择运行module_weather就可以当成一个新的app在手机上运行调试了。
运行效果: