组件化 -- 基础

一、概念

组件化:
把一个功能完整的App或模块拆分成多个子模块,每个子模块可以独立编译和运行,也可以任意组合成另一个新的App或模块,每个模块既不相互依赖但又可以相互交互。

组件:
指的是单一的功能组件,如视频组件(VideoSDK)、支付组件(PaySDK)、路由组件(Router)等,每个组件都能单独抽出来制作成SDK。

模块:
指的是独立的业务模块,如直播模块(LiveModule)、首页模块(HomeModule)、即时通信模块(IMModule)等。模块相对于组件来说粒度更大,模块可能包含多种不同的组件。

组件化与模块化:
组件化和模块化的本质思想是一样的,都是为了代码重用和业务解耦。区别在于模块化是业务导向,组件化是功能导向。

组件化与插件化:
组件化是在编译期分模块,插件化是在运行期。插件化用于动态更新模块或者动态修复BUG(热修复技术),相对来说黑科技更多一些。

组件化好处:

  • 业务模块解耦。
  • 提高工程编译速度。
  • 有利于功能复用。

组件化注意事项:

  • 组件化需要更多的时间来进行模块拆分。
  • 小项目没必要组件化,那样只会带来更多的工作量。
  • 组件化需要良好的架构设计,包括业务拆分、组件通信等,需要高水平的架构师统筹全局,经验不足盲目进行组件化反而会适得其反。

二、基础

AndroidManifest合并

每个module都有一份AndroidManifest配置文件,最终生成一个App的时候会将多个AndroidManifest合成一个。
生成路径:app\build\intermediates\manifests\full\debug\AndroidManifest.xml

Application的合并规则:

  • 如果功能module有自定义Application,主module没有自定义Application,这时会引用功能module中的Application。
  • 如果主module有自定义Application,功能module没有自定义Application,这时会引用主module中的Application。
  • 如果功能module有两个自定义Application,那么需要解决冲突(在application标签下添加:tools:replace="android:name",在manifest根标签下添加:xmlns:tools="http://schemas.android.com/tools"),最终会载入后编译的module的Application。
  • 如果主module中有自定义Application,其他功能module也有自定义Application,在主module中添加tools:replace解决冲突后,会发现最后编译的主module的Application在AndroidManifest中。

例子:

//settings.gradle(project)
include ':app', ':modulea'

//build.gradle(project)
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        maven {
            name 'AliYun Jcenter Repository Proxy'
            url 'https://maven.aliyun.com/repository/jcenter'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        maven {
            name 'AliYun Jcenter Repository Proxy'
            url 'https://maven.aliyun.com/repository/jcenter'
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

//build.gradle(module:modulea)
apply plugin: 'com.android.library'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.0"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.0.0'
    testCompile 'junit:junit:4.12'
}

//AndroidManifest(module:modulea)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tomorrow.modulea">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:supportsRtl="true">
        <activity android:name=".ModuleAActivity"/>
    </application>
</manifest>

//ModuleAActivity
public class ModuleAActivity extends AppCompatActivity {
}

//build.gradle(module:app)
apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.0"
    defaultConfig {
        applicationId "com.tomorrow.newfeature"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:26.0.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testCompile 'junit:junit:4.12'

    compile project(':modulea')
}

//AndroidManifest(module:app)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tomorrow.newfeature">
    <application
        android:name=".MainApplication"
        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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

//MainApplication(module:app)
public class MainApplication extends Application {
    private static final String TAG = "MainApplication";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "zwm, onCreate");
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                Log.d(TAG, "zwm, onActivityCreated, activity: " + activity.getLocalClassName());
            }

            @Override
            public void onActivityStarted(Activity activity) {
                Log.d(TAG, "zwm, onActivityStarted, activity: " + activity.getLocalClassName());
            }

            @Override
            public void onActivityResumed(Activity activity) {
                Log.d(TAG, "zwm, onActivityResumed, activity: " + activity.getLocalClassName());
            }

            @Override
            public void onActivityPaused(Activity activity) {
                Log.d(TAG, "zwm, onActivityPaused, activity: " + activity.getLocalClassName());
            }

            @Override
            public void onActivityStopped(Activity activity) {
                Log.d(TAG, "zwm, onActivityStopped, activity: " + activity.getLocalClassName());
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
                Log.d(TAG, "zwm, onActivitySaveInstanceState, activity: " + activity.getLocalClassName());
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                Log.d(TAG, "zwm, onActivityDestroyed, activity: " + activity.getLocalClassName());
            }
        });
    }
}

//MainActivity(module:app)
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "zwm, onCreate");
        setContentView(R.layout.activity_main);
    }
}

//app\build\intermediates\manifests\full\debug\AndroidManifest(module:app)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tomorrow.newfeature"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="15"
        android:targetSdkVersion="26" />

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:name="com.tomorrow.newfeature.MainApplication"
        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" >
        <activity android:name="com.tomorrow.newfeature.MainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.tomorrow.modulea.ModuleAActivity" />

        <meta-data
            android:name="android.support.VERSION"
            android:value="26.0.0" />
    </application>

</manifest>

组件间通信机制

如果要使用EventBus或者类似RxBus的事件总线,可以使用以下方案,最大程度地完成事件总线的解耦。


事件总线架构

其中XXXBus独立为一个module,Base module依赖XXXBus对事件通信的解耦,抽离事件到XXXBus事件总线模块。以后添加事件的Event的实体都需要在上面创建。

组件间跳转

使用路由机制,例如ARouter。

动态创建

动态创建Fragment解耦

组件化Fragment架构

使用跨模块获取Fragment非常适合在单Activity+多Fragment的App架构中使用。
方法1:使用反射机制跨模块获取Fragment。
方法2:使用ARouter跨模块获取Fragment。

动态初始化Application

某些功能模块需要做一些初始化操作。
方法1:通过主module的Application获取各个module的初始化文件,然后通过反射初始化的Java文件来调用初始化方法。
方法2:通过在主module的Application中继承Base Module的Application来实现,主module的Application将注册每个module的初始化文件,然后通过Base Module中的Application来对初始化文件做启动封装。

组件化存储

关系型数据库架构

组件化权限

我们将normal级别的权限申请都放到Base module中,然后在各个module中分别申请dangerous的权限。这样分配的好处在于当添加或移除单一模块时,隐私权限申请也会跟随移除,能做到最大程度的权限解耦。当项目需要适配到Android 6.0以上的动态权限申请时,需要在Base module中添加自己封装的一套权限申请工具,其他组件层的module都可以使用这套工具。


基础权限架构

路由拦截:


跳转时进行权限拦截验证

以ARouter为例,模块跳转前会遍历Intercept,然后通过判断跳转路径来找到需要拦截的对象。

组件化的静态变量

//strings.xml(module:modulea)
<resources>
    <string name="app_name">ModuleA</string>
    <string name="modulea_str">ModuleAStr</string>
</resources>

//modulea\build\generated\source\r\debug\com\tomorrow\R.java(module:modulea)
public final class R { 
    ...
    public static final class string {
        ...
        public static int app_name=0x7f050021; //在功能module中是静态变量,不是常量
        public static int modulea_str=0x7f050022; //在功能module中是静态变量,不是常量
        ...
    }
    ...
}

//strings.xml(module:app)
<resources>
    <string name="app_name">NewFeature</string>
    <string name="app_str">AppStr</string>
</resources>

//app\build\generated\source\r\debug\com\tomorrow\modulea(module:app)
public final class R { 
    ...
    public static final class string {
        ...
        public static final int app_name = 0x7f060021; //在Application module中是静态常量
        public static final int modulea_str = 0x7f060023; //在Application module中是静态常量
        ...
    }
    ...
}

//app\build\generated\source\r\debug\com\tomorrow\newfeature(module:app)
public final class R { 
    ...
    public static final class string {
        ...
        public static final int app_name=0x7f060021; //在Application module中是静态常量
        public static final int app_str=0x7f060022; //在Application module中是静态常量
        public static final int modulea_str=0x7f060023; //在Application module中是静态常量
        ...
    }
    ...
}

各个功能module会生成aar文件,并且被引用到Application module中,最终合并为apk文件。当各个次级module在Application module中被解压后,在编译时资源R.java会被重新解压到build/generated/source/r/debug/包名/R.java中,id属性会被添加声明为final修饰符。

当每个组件中的aar文件汇总到App module中时,也就是编译的初期解析资源阶段,其每个module的R.java释放的同时,会检测到全部的R.java文件,然后通过合并,最终会合并为唯一的一份R.java资源文件。

注意:在功能module中,静态变量并没有被赋予final属性。这样会导致在功能module中,凡是规定必须使用常量的地方都无法直接使用R.java中的变量,包括switch-case和注解。

例如,在ButterKnife框架中,注解中只能使用常量:

//编码
@BindView(R2.id.edittext)
public EditText username;

//当编译成class文件后,R值将被替换为常量
@BindView(2131492969)
public EditText username;

ButterKnife框架使用替换的方法,将R.java文件复制一份,命名为R2.java,然后给R2.java的变量加上final修饰符,在相关的地方直接引用R2资源。

组件化的资源汇合

组件化中,Base module和功能module的根本是Library module,编译时会依次通过依赖规则进行编译,最底层的Base module会被先编译成aar文件,然后上一层编译时因为通过compile依赖,也会将依赖的aar文件解压到模块的build中。全部的功能module都依赖了Base module,但是Application module最终还是得将功能module的aar文件汇总后,才能开始编译操作。最终,只有一个Base module的资源在目录中,并且其他各种依赖包也只有唯一一份。

查看依赖树:

gradlew module_name:dependencies
gradlew module_name:dependencies > dependencies.txt

有些传递依赖标注了*号,表示这个依赖被忽略了,这是因为其他顶级依赖中也依赖了这个传递的依赖,Gradle会自动分析并下载最合适的依赖。

资源冲突:
1.AndroidManifest冲突问题
AndroidManifest中引用了Application的app:name属性,当出现冲突时,需要使用tools:replace="android:name"来声明Application是可被替换的。某些AndroidManifest.xml中的属性被替代的问题,可以使用tools:replace来解决冲突。

2.包冲突
当包冲突出现时,可以先检查依赖报告,使用gradle dependencies的命令查看依赖目录树。依赖标注了*号,表示这个依赖被忽略了。这是因为其他顶级依赖也依赖于这个传递的依赖。如果想使用优先级比较低的依赖,可以使用exclude排除依赖的方式:

compile('com.facebook.fresco:fresco:0.10.0') {
    exclude group:'com.android.support', module:'support-v4'
}

3.资源名冲突
在多module开发中,因为无法保证多个module中全部资源的命名是不同的。假如出现相同的情况,就有可能造成资源引用错误的问题。

因为无法保证不同module中的资源名不同,那么Gradle会简单粗暴地使用替换策略:后编译的模块会覆盖之前编译的模块的资源字段中的内容。Application module最后汇总在一起时,资源会先被策略逻辑调整,后加载的资源地址会把前面加载的资源地址替换掉,引起资源引用的错误。

解决的办法有两种:

  • 当资源出现冲突时使用重命名的方式解决。这就要求我们在一开始命名的时候,不同模块间的资源命名都不一样。这是代码编写规范的约束。
  • Gradle的命名提示机制。使用resourcePrefix字段:android { resourcePrefix "组件名_" }

所有的资源名必须以指定的字符串作为前缀,否则会报错,而且resourcePrefix这个值只能限定XML中的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。

组件化混淆

组件化混淆

这里将固定的第三方库混淆放到Base module proguard-rule.pro中,每个module独有的引用库混淆放到各自的proguard-rule.pro中。最后在App module的proguard-rule.pro文件中放入Android基础属性混淆声明,例如四大组件和全局混淆(引用Base module基类)等一些配置。这样将可以最大限度地完成混淆解耦工作。

版本参数优化

//common_config.gradle
project.ext {
    //设置App配置
    setAppDefaultConfig = {
        extension ->
            //引用Application插件库
            extension.apply plugin: 'com.android.application'
            extension.description "app"
            setAndroidConfig extension.android
            setDependencies extension.dependencies
    }

    //设置Lib配置
    setLibDefaultConfig = {
        extension ->
            //引用lib插件库
            extension.apply plugin: 'com.android.library'
            extension.description "lib"
            setAndroidConfig extension.android
            setDependencies extension.dependencies
    }

    //设置Android配置
    setAndroidConfig = {
        extension ->
            extension.compileSdkVersion 26
            extension.buildToolsVersion "26.0.0"
            extension.defaultConfig {
                minSdkVersion 15
                targetSdkVersion 26

                testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//                javaCompileOptions {
//                    annotationProcessorOptions { //路由每个模块的名称
//                        arguments = [moduleName: extension.project.getName()]
//                    }
//                }
            }
//            extension.dataBinding { //开启dataBinding,可以选用
//                enabled = true
//            }
    }

    //设置依赖
    setDependencies = {
        extension ->
            extension.compile fileTree(dir: 'libs', include: ['*.jar'])
            //每个module都需要引用路由的apt插件库才能生成相应代码,这里无须重复编写每个module
//            extension.annotationProcessor 'com.alibaba:arouter-compiler:1.1.1'
    }
}

//build.gradle(module:app)
apply from: "${rootProject.rootDir}/common_config.gradle"
project.ext.setAppDefaultConfig project

android {
    defaultConfig {
        applicationId "com.tomorrow.newfeature"
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile 'com.android.support:appcompat-v7:26.0.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testCompile 'junit:junit:4.12'

    compile project(':modulea')
}

//build.gradle(module:modulea)
apply from: "${rootProject.rootDir}/common_config.gradle"
project.ext.setLibDefaultConfig project

android {
    defaultConfig {
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile 'com.android.support:appcompat-v7:26.0.0'
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testCompile 'junit:junit:4.12'
}

调试优化

业务模块调试,将单一模块做成App启动,然后用于调试测试,这样保证了单独模块可以分离调试。

步骤如下:
1.业务模块是Library module,独立调试需要将模块做成Application module才能引入App构建流程。
即:com.android.library -> com.android.application

2.每个Application都要配置ApplicationId。

setAppDefaultConfig = {
        extension->
        //引用Application插件库
        extension.apply plugin:'com.andorid.application'
        extension.description "app"
        setAndroidConfig extension.android
        setDependencies extension.dependencies
        //为每个Module配置不同的Application ID
        extension.android.defaultConfig{
                applicationId applicationId+"."+extension.getName()
        }
}

这里的extension.getName()相当于project.getName(),在默认的ApplicationId后添加module名字,用于区分不同module产生的单独的App。

3.在src中建立debug文件夹。
debug文件夹同main文件夹中的目录类似,用于放置调试需要的AndroidManifest.xml文件、Java文件、res资源文件。
AndroidManifest文件中需要设置默认启动的Activity文件:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

4.在common_config中需要声明单独模块调试变量。
例如,设置一个isNewsDebug变量:

project.ext {
    isNewsDebug = false
}

在模块的build.gradle中将变量isNewsDebug作为开关:

if(project.ext.isNewsDebug){
        project.ext.setLibDefaultConfig project //当前为lib
} else{
        project.ext.setAppDefaultConfig project //当前为app
}

在sourceSets资源配置中,配置AndroidManifest的地址和res资源的地址:

sourceSets{
    main{
        if(project.ext.isNewsDebug){
                manifest.srcFile 'src/debug/AndoridManifest.xml'
                res.srcDirs =['src/debug/res','src/main/res']
        } else {
            manifest.srcFile 'src/main/AndoridManifest.xml'
            resources {
                //排除debug文件夹下的所有文件
                exclude 'src/debug/*'
            }
        }
    }
}

编译构建时Gradle会选取debug内的资源。

5.原App module需要移除已经单独调试的模块依赖。

dependencies {
    if(!project.ext.isNewsDebug){
        compile project(':news")
    }
}

调试的模块已经从Library module变更为Application module,该Application module不能被其他Application module依赖,只能依赖Library module,这是Android Gradle设计的规则。通过isNewsDebug变量开关来判断引用。

资源引用配置

Gradle有多种资源引用的方式:
1.使用sourceSets的方式来指定文件的路径。sourceSets还可以指定更多的资源属性的路径。

sourceSets {
    main {
        manifest.srcFile 'AndroidManifest.xml' //AndoridManifest
        java.srcDirs = ['src'] //Java文件路径
        resources.srcDirs = ['src'] //全部资源文件路径
        aidl.srcDirs = ['src'] //aidl文件路径
        renderscript.srcDirs = ['src'] //renderscript文件路径
        res.srcDirs = ['res'] //res资源文件路径
        assets.srcDirs = ['assets'] //asset资源文件路径
    }
}

sourceSets可以指定不同资源引用的文件夹。[]里面是列表,可以通过","来分隔资源地址。

2.可以动态添加res资源,在buildType和productFlavor中定义resValue变量。

resValue "string", "app_name", "serverGank"

resValue只能动态添加资源,无法替换资源。如果资源名称重复,Gradle会提示重复定义资源。

3.可以指定特定尺寸的资源,也可以在buildType和productFlavor中定义。

resConfigs "hdpi", "xhdpi", "xxhdpi"

4.可以通过build.gradle定义manifestPlaceholders的变量,再通过AndroidManifest的meta-data的方式间接让Java代码读取AndroidManifest的变量。

//build.gradle
productFlavors {
    client {
        manifestPlaceholders=[
            channel:"10086",
            verNum:"1",
            app_name:"Gank"
        ]
    }
}

//AndroidManifest.xml
<application
    android:name=".MainApplication"
    android:allowBackup="true"
    android:label="${app_name}" //app名引用
    <meta-data android:name="verNum" android:value="${verNum}"/> //版本号声明
    <meta-data android:name="channel" android:value="${channel}"> //渠道名声明
    ...

5.通过build.gradle编译生成的BuildConfig文件,可以直接让代码读取到BuildConfig中的值。

//build.gradle
productFlavors {
    client {
        buildConfigField "String", "advice_url", "\"https://www.baidu.com\""
    }
}

//编译时生成代码
public final class BuildConfig {
    ...
    public static final String advice_url = "https://www.baidu.com";
    ...
}

Gradle 4.1依赖特性

当升级到Android Studio 3.0之后,Gradle版本也随之升级到4.1版本。新建一个工程时,build.gradle依赖方式默认变为implementation,而不是compile。

implementation的作用依然是提供依赖,但有别于compile,implementation并不能跨模块传递依赖。这样做能很好地隐蔽内部接口的实现,保持封装的隐蔽性。


implementation依赖关系

Gradle 4.1依然提供同compile一样功能的引用接口,就是API引用,其使用方式和compile相同。


API依赖关系

Gradle 4.1对依赖函数接口做了很多的变更。Gradle 4.1以下版本使用provided依赖,4.1版本改为compileOnly函数,使用方式也是一样的。

组件化中,因为App能随意使用Base module的接口方法,在Base module中建议使用API依赖第三方的库,而在Business业务模块中依赖时也使用API的方式。虽然这样牺牲了编译速度,但是省却一部分的调用封装,也能保证兼容Gradle 4.1以下的组件化项目。

如果要完全做到App和Base module之间的解耦,需要定制一个独立给App提供底层支持的接口module,其功能是封装基础的base接口给App module使用。


Gradle 4.1组件化依赖

组件化SDK

制作jar文件

task clearJar(type: Delete) {
    delete 'libs/testjar.jar' //SDK是jar包的名字,任意命名
}

task makeJar(type: org.gradle.api.tasks.bundling.Jar) {
    //指定生成的jar名
    baseName 'testjar'
    //从哪里打包class文件
    from('build/intermediates/classes/debug/')
    //打包到jar后的目录结构
    into('build/outputs/')
    //去掉不需要打包的目录和文件
    exclude('test/', 'BuildConfig.class', 'R.class')
    //去掉R开头的文件
    exclude{
        it.name.startsWith('R');
    }
}

makeJar.dependsOn(clearJar, build)

运行gradlew makeJar命令就可以生成jar文件:
testjar\build\outputs\aar\testjar-debug.aar
testjar\build\outputs\aar\testjar-release.aar
testjar\build\libs\testjar.jar ==> 生成的jar文件

制作aar文件

在Library module中只要使用assemble命令就可以生成工程的aar文件。

导入jar文件

导入libs中的jar文件:

dependencies {
    compile fileTree(include: ['*.jar', dir: 'libs']) //导入文件夹中所有jar文件
    compile files('libs/xxx.jar') //加载单一库
}

导入aar文件

导入libs中的aar文件:

repositories {
    flatDir {
        dirs 'libs' //指定module本地引用库的路径
    }
}

dependencies {
    compile(name: 'xxx', ext: 'aar')
}

三、思想

组件化需要解决的几大问题:

  • 代码解耦。
  • 组件单独运行。
  • 数据传递。
  • UI跳转。
  • 组件的生命周期。
  • 集成调试。
  • 代码隔离。

四、架构

模块化模型

模块化模型

应用层:生成App和加载初始化操作。
模块层:每个模块相当于一个业务,通过module来分隔每个业务的逻辑,一个模块由多个不同的页面逻辑组成。
基础层:基础组件的整合,提供基础组件能力给业务层使用。
组件层:将图片加载、网络HTTP、Socket等基础功能划分为一层。
基础库层:更加基础的库类依赖,此层非必需,例如RxJava、EventBus等一些代码结构优化的库,还有自己编写的封装类。

基础库层可以转移到基础层和组件层中,这样可以减少层级。
基础层可以只是一个空壳,起到隔离模块层和组件层的入口的作用,可以作为中转,封装一些必须要使用的组件功能,隐蔽一些实现细节。
模块层的业务逻辑需要考虑业务之间的信息交互和转发的实现。

这个结构适合中型App的搭建,当业务需要重新细分重构时,可以考虑使用这种架构方式。这种架构要求组件独立复用,模块能够不依赖于其他模块实现。

从零开始搭建Android组件化框架

相关链接

《Android组件化架构》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容