51好读  ›  专栏  ›  开发者全社区

Android架构之App组件化方案详细实践与总结

开发者全社区  · 公众号  · android  · 2017-03-20 07:14

正文

相关阅读:

吊炸天!74款APP完整源码!

【干货】最新阿里Android面试题总结(附答案)

论读Android源码的重要性——Hook技术之View点击劫持

1、Android组件化项目

在Android项目组件化之前,我们的项目都是像下图那样,一个单一工程下,根据不同的业务分几个文件夹,把需要的第三方库依赖下就开始开发了,这样的代码耦合严重,牵一发而动全身,删除某处代码就会到处报错,如果不解决掉报错的地方,就没法编译打包,而且这样的代码只适合于个人开发,尤其团队开发合并代码的时候那真是一个麻烦,相信大家都会深有体会,如果项目很大的话,修改一点简单的页面都要重新编译,Android编译速度大家也都见识过,每次打包都很耗时,并且这样的代码想做单元测试也是无从下手。

所以Android项目组件化就迫在眉睫了,组件化的方向就是由一个项目工程拆分成若干个模块工程,由App主工程提供统一的入口,每个业务独立的模块共享项目的Common依赖库。

2、Android组件化项目实施步骤

1)第一步:配置可自动将组件在Application和Library属性之间切换的方法

我们都知道Android Studio中的Module主要有两种属性,分别为 :

  • application属性,可以独立运行的Android程序,也就是我们的APP;

    apply plugin: ‘com.android.application’

  • library属性,不可以独立运行,一般是Android程序依赖的库文件;

    apply plugin: ‘com.android.library’

当我们在开发单独组件的时候,这个组件应该处于application模式,而当我们要将单独组件合并到主工程的时候,就需要将单独组从application模式改为library模式,也许你可以每次切换的时候都去build.gradle文件中去修改,但是你的项目要是有十几个组件的时候,你确定一个个去改?所以我们必须有一种能够动态切换组件模式的方法,做到一次修改,全局组件生效,这个问题就需要通过配置Gradle来解决了。

在Android Studio项目的根目录下有一个 gradle.properties 文件,这个文件主要用来配置Gradle settings的,例如JVM参数等,想要了解这个文件的更多作用请查看 http://www.gradle.org/docs/current/userguide/build_environment.html
,我们今天需要关注的是这个文件的一个特点:我们在 gradle.properties 中配置的字段都可以在 build.gradle 文件中直接读取出来,不用任何多余的代码。

现在我们在 gradle.properties 添加了一行代码,定义一个属性isModule(是否是组件开发模式,true为是,false为否):

# 每次更改“isModule”的值后,需要点击 "Sync Project" 按钮isModule=true

然后我们在组件的 build.gradle 文件中读出这行代码:

if (isModule.toBoolean()) {    
       apply plugin: 'com.android.application'}
         else {  
              apply plugin: 'com.android.library'}

因为 gradle.properties 中的数据类型都是String类型,而这里我们需要的是boolean值,所以这里要将String转换为boolean值,如果是‘组件开发模式”就将这个组件应用为application模式,如果不是就将这个组件应用为library模式,也就是一个库。

这样我们的第一个问题就解决了,首先我们在 gradle.properties 中定义一个属性isModule,然后在每个组件的 build.gradle 中把这个属性读取出来,每当我们需要从组件开发模式和APP整体开发模式转换时,只需要修改“isModule”的值即可,当然注释中也说了修改为这个属性值后,要点击AndroidStudio上的 “Sync Project”按钮同步下整个项目才能生效。

2)第二步:解决组件AndroidManifest和主工程AndroidManifest合并的问题

每个组件是由不同的成员单独开发的,这个时候组件就是一个独立的APP,那么这个组件就会有自己的“AndroidManifest.xml”,但是Android程序只有一个“AndroidManifest.xml”,当我们要把组件作为Library合并到主工程的时候,组件的“AndroidManifest.xml”和主工程的“AndroidManifest.xml”就会产生冲突,因为他们都有自己实现application类以及一些属性,还有自己的MAIN Activity,如果直接把张表合并到一起势必产生冲突。

解决思路就是:每个组件维护两张表,一张用于组件单独开发时使用,另一张用于合并到主工程的注册表中,每当增加一个Android系统的四大组件时都要同时给两张表中添加。


我们在上一节讲了可自动在组件的Application和Library属性之间切换的方法,有了这种方法,维护两张表就很方便了,首先在组件的main文件夹(和java文件夹平级)下创建两个文件夹,如下图:

然后在每个组件的 *build.gradle 中添加如下的代码:

sourceSets {
    main {        
           if (isModule.toBoolean()) {            manifest.srcFile 'src/main/debug/AndroidManifest.xml'        } else {            manifest.srcFile 'src/main/release/AndroidManifest.xml'            //release模式下排除debug文件夹中的所有Java文件            java {                exclude 'debug/**'            }        }    } }

这些代码的意思是:当在组件开发模式下,组件的注册表文件使用debug文件夹下的,其他情况使用release文件夹下的注册表文件;那么这两张表的区别在哪里呢?

下面的表示debug文件夹中的:

<application
    android:name="debug.CarApplication"
    android:icon="@mipmap/ic_car_launcher"
    android:label="@string/car_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity
        android:name=".query.QueryActivity"
        android:configChanges="orientation|screenSize|keyboard"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="adjustPan|stateHidden">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        intent-filter>
    activity>
    <activity
        android:name=".scan.ScanActivity"
        android:screenOrientation="portrait" />
application>

下面的表是release文件夹中的:

 <application android:theme="@style/AppTheme">

    <activity
        android:name=".query.QueryActivity"
        android:configChanges="orientation|screenSize|keyboard"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme"
        android:windowSoftInputMode="adjustPan|stateHidden" />
    <activity
        android:name=".scan.ScanActivity"
        android:screenOrientation="portrait" />
application>
  1. debug文件夹中注册表的标签中指定了具体application类,而release文件夹中的则没有,

  2. debug文件夹中注册表的标签中添加一些application属性,而release文件夹中的则什么都没有添加;

  3. debug文件夹中的注册表指定QueryActivity为MAIN Activity,也就是要启动的 Activity,而release文件夹中的则没有;

3)第三步:解决组件和主工程的Application冲突问题以及组件单独开发初始化(共享)数据问题

当android程序启动时,android系统会为每个程序创建一个Application类的对象,并且只创建一个,application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。在默认情况下应用系统会自动生成Application 对象,但是如果我们自定义了Application,那就需要告知系统,实例化的时候,是实例化我们自定义的,而非默认的。但是我们在组件化开发的时候每一个组件可能都会有一个自己的Application类的对象,如果我们在自己的组件中开发时需要获取全局的Context,一般都会直接获取application对象,但是当所有组件要打包合并在一起的时候就会出现问题,因为最后程序只有一个Application,我们组件中自己定义的Application肯定没法使用,总不能每次打包的时候都把全局的application改一遍吧?

解决思路:首先创建一个叫做Common的Library,这个Common库中主要包含整个项目用到公共基类、工具类、自定义View等,例如BaseActivity、BaseFragment、BaseApplication等,并且我们的每一个组件都要依赖这个Common库,现在主要讲Common库中的BaseApplication怎么定义,下面是BaseApplication中的部分代码:

    public class BaseApplication extends Application {        
       private static BaseApplication sInstance;        
       public static Context context;        
       public static BaseApplication getIns() {            
                  return sInstance;        }        
         
         @Override        public void onCreate() {          
               super.onCreate();            sInstance = this;            context = this.getApplicationContext();            
               if (isAppDebug(context)) {    //只有debug模式才会打印日志                Logger.init("Demo").logLevel(LogLevel.FULL);            } else {                Logger.init("Demo").logLevel(LogLevel.NONE);            }        }    }

因为每个组件都依赖了Common库,所以每个组件都能够获取到BaseApplication.context,但是Android程序默认的是系统自己的Application这个类,要想使用自己的就要继承Application并且在AndroidManifest.xml中声明,因此我们先在自己的组件中创建一个组件Application并且继承于BaseApplication,然后在debug文件中的AndroidManifest.xml中声明:

public class CarApplication extends BaseApplication {    
   
     @Override    public void onCreate() {        
           super.onCreate();        login();    } }

这样我们就可以在组件中使用全局的Context:BaseApplication.context了,但是还有一个问题,我们在自己的组件中定义了CarApplication,那么组件合并到主工程后,主工程也有自己的Application,这样又冲突了,其实这个问题第二节的代码就已经写出来了,我们只是在组件开发时才使用CarApplication,那么我们在合并到主工程的时候把这个代码排除掉不就行了嘛,直接上图:

我们在java文件夹下再建一个debug文件夹,把组件自己的application放在这个文件夹中,然后在 build.gradle 添加这行代码:

这样在合并到主项目时debug文件夹下的java文件就全部被排除了。并且你可以在组件的Application中做一些初始化的操作,比如登陆,然后把数据保存下来,供组件使用。

4)第四步:解决library重复依赖以及Sdk和依赖的第三方库版本号控制问题

重复依赖问题其实在开发中经常会遇到,比如你 compile 了一个A,然后在这个库里面又 compile 了一个B,然后你的工程中又 compile 了一个同样的B,就依赖了两次。

默认情况下,如果是 aar 依赖,gradle 会自动帮我们找出新版本的库而抛弃旧版本的重复依赖。但是如果你使用的是 project 依赖,gradle 并不会去去重,最后打包就会出现代码中有重复的类了。

Library重复依赖的解决办法就是给整个工程提供统一的依赖第三方库的入口,在上一节讲解决Application冲突问题时我们建了一个Common库,这个库还有一个作用就是用来为整个项目提供统一的依赖第三方库的入口,我们把项目常用或者必须用到的库全部在Common库的 build.gradle 中依赖进来,例如Android support Library、网络库、图片加载库等,又因为每个组件都要依赖这个Common库,所以的 build.gradle 中就不在需要依赖任何其他库了,这样我们就有了统一的依赖第三方库的入口,添加、删除和升级库文件都只需要在Common库中去处理就好了。

下面是组件 build.gradle 的依赖配置:

dependencies






请到「今天看啥」查看全文