搭建组件化框架

转载请声明地址:https://blog.youkuaiyun.com/skyunicorn/article/details/88897492

浅谈组件化框架(底部有demo链接)

什么是组件化?组件化在我的理解就是:一个app是由一个壳工程包含着多个模块工程拼接而成,所有的模块工程都依赖于同一个common库,而每个子模块之间又相对独立,可以不依赖于其他模块独立运行,方便程序员分工负责各自模块开发。

懒得画图了,直接从网上找了个图(找不到图的具体出处,很多组件化讲解都用的这个图),大概就是图里的这个意思。
在这里插入图片描述
一个大的app使用组件化,可以拆分为多个子app,独立开发调试可以减少编译的成本,大大提高了开发效率。废话不多说,下面我仔细讲解如何搭建一个组件化的框架。

一、使用Gradle统一配置依赖版本

1.在项目根目录新建config.gradle(公共配置文件,管理三方库、版本号)文件
在这里插入图片描述
2.在根目录build里配置config.gradle

apply from: ‘config.gradle’
在这里插入图片描述
3.编写config.gradle

这个文件的主要目的是控制app作为集成模式还是组件模式,并统一管理所有依赖库的版本号,避免项目使用的库出现不同版本。

ext {
    isAlone = false;//false:作为集成模式存在,true:作为组件模式存在

    // 各个组件版本号的统一管理
    android = [
            compileSdkVersion         : 28,
//            buildToolsVersion         : "28.0.2",
            minSdkVersion             : 23,
            targetSdkVersion          : 23,
            versionCode               : 1,
            versionName               : '1.0.0',
            sourceCompatibilityVersion: JavaVersion.VERSION_1_8,
            targetCompatibilityVersion: JavaVersion.VERSION_1_8
    ]
//
    libsVersion = [
            APPCOMPAT_V7_VERSION = "28.0.0",
            SUPPORT_V4_VERSION = "28.0.0",
            CONSTRAINT_LAYOUT_VERSION = "1.1.3",
            JUNIT_VERSION = "4.12",
            RUNNER_VERSION = "1.0.2",
            ESPRESSO_VERSION = "3.0.2",
            AROUTER_API_VERSION = "1.2.4",
            AROUTER_COMPILER_VERSION = "1.1.4",
            MULTIDEX_VERSION = "1.0.1",
            GLIDE_VERSION = "4.8.0",
            GLIDE_COMPILER_VERSION = "4.8.0",
            BUTTERKINFE_VERSION = "8.4.0",
            BUTTERKINFE_COMPILER_VERSION = "8.4.0",
            COMPRESS_HELPER_VERSION = "1.0.5",
    ]

    // 依赖库管理
    supportDependencies = [
            appcompat_v7        : "com.android.support:appcompat-v7:${rootProject.APPCOMPAT_V7_VERSION}",
            support_v4        : "com.android.support:support-v4:${rootProject.SUPPORT_V4_VERSION}",
            constraint_layout   : "com.android.support.constraint:constraint-layout:${rootProject.CONSTRAINT_LAYOUT_VERSION}",
            junit               : "junit:junit:${rootProject.JUNIT_VERSION}",
            runner              : "com.android.support.test:runner:${rootProject.RUNNER_VERSION}",
            espresso            : "com.android.support.test.espresso:espresso-core:${rootProject.ESPRESSO_VERSION}",
            // 路由通讯(用于组件间通信的实现)
            arouter_api         : "com.alibaba:arouter-api:${rootProject.AROUTER_API_VERSION}",
            arouter_compiler    : "com.alibaba:arouter-compiler:${rootProject.AROUTER_COMPILER_VERSION}",
            // 分包
            multidex            : "com.android.support:multidex:${rootProject.MULTIDEX_VERSION}",
            // glide
            glide               : "com.github.bumptech.glide:glide:${rootProject.GLIDE_VERSION}",
            glide_compiler      : "com.github.bumptech.glide:compiler:${rootProject.GLIDE_COMPILER_VERSION}",
            // butterknife
            butterknife         : "com.jakewharton:butterknife:${rootProject.BUTTERKINFE_VERSION}",
            butterknife_compiler: "com.jakewharton:butterknife-compiler:${rootProject.BUTTERKINFE_COMPILER_VERSION}",
            // 压缩工具
            compress_helper     : "com.github.nanchen2251:CompressHelper:${rootProject.COMPRESS_HELPER_VERSION}",
    ]
}

4.编写壳app下的build文件

apply plugin: 'com.android.application'
// AS3.2不再支持apt
//apply plugin: 'com.neenbedankt.android-apt'

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion

    defaultConfig {
        applicationId "com.skyunicorn.demo.frame_rrd_mvp"
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

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

    compileOptions {
        sourceCompatibility rootProject.ext.android.sourceCompatibilityVersion
        targetCompatibility rootProject.ext.android.targetCompatibilityVersion
    }

    sourceSets {
        main {
            //控制两种模式下的资源和代码配置情况
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java', 'src/main/module/java']
            res.srcDirs = ['src/main/res', 'src/main/module/res']
        }
    }
}

def librarys = rootProject.ext.supportDependencies

dependencies {
	// 公用的库我放到了lib_common里,所以这里不在引用
    //implementation fileTree(dir: 'libs', include: ['*.jar'])
    //implementation librarys["appcompat_v7"]
    //implementation librarys["constraint_layout"]
    testImplementation library["junit"]
    androidTestImplementation library["runner"]
    androidTestImplementation library["espresso"]

    implementation project(":lib_common")
//    implementation project(":mod_news")

    // butterknife
    implementation library["butterknife"]
    annotationProcessor library["butterknife_compiler"]
}

二、新建组件模块

1.新建组件
在这里插入图片描述

在这里插入图片描述

生成的组件和app结构一致
在这里插入图片描述

2.公共库build

公共库的目的是对其他mod和壳工程提供统一管理的工具、基类等文件,这里使用的依赖库主要使用api方式。api和implementation的主要区别是:api依赖的库可以传递,即依赖lib_common的mod可以直接使用这些库,而implementation依赖的库,其他mod即便依赖了lib_common也无法使用。

//控制组件模式和集成模式(公共库不需要)
//if (rootProject.ext.isAlone) {
//    apply plugin: 'com.android.application'
//} else {
apply plugin: 'com.android.library'
//}

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion

    defaultConfig {
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        //集成模式下Arouter的配置,用于组件间通信的实现
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }

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

    compileOptions {
        sourceCompatibility rootProject.ext.android.sourceCompatibilityVersion
        targetCompatibility rootProject.ext.android.targetCompatibilityVersion
    }

    sourceSets {
        main {
            //控制两种模式下的资源和代码配置情况(公共库不需要)
//            if (rootProject.ext.isAlone) {
//                manifest.srcFile 'src/main/module/AndroidManifest.xml'
//                java.srcDirs = ['src/main/java', 'src/main/module/java']
//                res.srcDirs = ['src/main/res', 'src/main/module/res']
//            } else {
            manifest.srcFile 'src/main/AndroidManifest.xml'
//            }
        }
    }

}

def librarys = rootProject.ext.supportDependencies

dependencies {
    // 公用库用api,单独使用的库用implementation
    api fileTree(dir: 'libs', include: ['*.jar'])
    api librarys["appcompat_v7"]
    api librarys["constraint_layout"]
//    testImplementation librarys["junit"]
    androidTestImplementation librarys["runner"]
    androidTestImplementation librarys["espresso"]

   
    api librarys["multidex"]

    // Arouter路由
    api librarys["arouter_api"]
    annotationProcessor librarys["arouter_compiler"]

    // Glide
    api librarys["glide"]
    annotationProcessor librarys["glide_compiler"]

    // butterknife
    api librarys["butterknife"]
    annotationProcessor librarys["butterknife_compiler"]
    
}

3.子mod的build

//控制组件模式和集成模式(公共库不需要)
if (rootProject.ext.isAlone) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion

    defaultConfig {
        if (rootProject.ext.isAlone) {
            applicationId "com.skyunicorn.demo.mod_news"
        }

        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        if (!rootProject.ext.isAlone) {
            //集成模式下Arouter的配置,用于组件间通信的实现
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [moduleName: project.getName()]
                }
            }
        }
    }

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

    compileOptions {
        sourceCompatibility rootProject.ext.android.sourceCompatibilityVersion
        targetCompatibility rootProject.ext.android.targetCompatibilityVersion
    }

    // 配置资源约束
    //不同模块对于资源的命名可能会有冲突,为了防止不同模块的资源应为命名冲突而被错误的覆盖,就需要一种机制能够检查、提示、修改冲突的资源。
    resourcePrefix "${project.name}_"

    sourceSets {
        main {
            //控制两种模式下的资源和代码配置情况(公共库不需要)
            if (rootProject.ext.isAlone) {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java.srcDirs = ['src/main/java', 'src/main/module/java']
                res.srcDirs = ['src/main/res', 'src/main/module/res']
            } else {
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
            }
        }
    }


}

def librarys = rootProject.ext.supportDependencies

dependencies {
    testImplementation librarys["junit"]
    androidTestImplementation librarys["runner"]
    androidTestImplementation librarys["espresso"]

    implementation project(":lib_common")

    if (!rootProject.ext.isAlone) {
        //集成模式下需要编译器生成路由通信的代码
        annotationProcessor librarys["arouter_compiler"]
    }
}

注意子model和lib_common的这里是不一样的:
在每个mod的src/main下建立独立打包清单文件,配置后独立打包使用的清单文件就是src/main/AndroidManifest.xml,而作为组件使用的就是src/main/release/AndroidManifest.xml

if (rootProject.ext.isAlone) {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/module/java']
res.srcDirs = ['src/main/res', 'src/main/module/res']
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}

三、配置ARouter(路由跳转)
ARouter是用来实现跨模块通信功能
详细内容可以参考https://blog.youkuaiyun.com/huangxiaoguo1/article/details/78753555 这篇文章

1.首先在lib_common里配置arouter
在这里插入图片描述
2.在BaseApplication(在lib_common库)中注册ARouter

public class BaseApplication extends MultiDexApplication {

    private static BaseApplication instance;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        initARouter(this);
    }

    public static Context getInstance(){
        return instance;
    }

    private void initARouter(BaseApplication baseApplication) {

        if(AppUtils.isApkInDebug(instance)){
            // 打印日志
            ARouter.openLog();
            // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!)
            // 线上版本需要关闭,否则有安全风险
            ARouter.openDebug();
        }

        ARouter.init(this);
    }
}

3.配置RouteUtils(在lib_common库)

public class RouteUtils {

    public static final String News_Activity_Main = "/mod_news/main";

    // 获取news模块的fragment
    public static final String News_Fragment_Main = "/mod_news/main";
}

4.配置ActivityUtils和FragmentUtils(在app壳工程中)

public class ActivityUtils {

    public static Activity getActivity(String path) {
        Activity activity = (Activity) ARouter.getInstance().build(path).navigation();
        return activity;
    }

    public static void getActivityForResult(String path, Activity activity, int requestCode) {
        ARouter.getInstance().build(path).navigation(activity, requestCode);
    }

    public static Activity getNewsMainActivity() {
        return getActivity(RouteUtils.News_Activity_Main);
    }

}

public class FragmentUtils {

    public static Fragment getNewsMainFragment() {
        Fragment fragment = (Fragment) ARouter.getInstance().build(RouteUtils.News_Fragment_Main).navigation();
        return fragment;
    }
}

四、MainActivity使用

public class MainActivity extends BaseActivity {

    @BindView(R.id.btn_to_news)
    Button btnToNews;
    @BindView(R.id.btn_to_imgs)
    Button btnToImgs;


    @Override
    protected int getContentViewLayoutId() {
        return R.layout.activity_main;
    }

    @OnClick({R.id.btn_to_news, R.id.btn_to_imgs})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn_to_news:
                ActivityUtils.getNewsMainActivity();
                break;
            case R.id.btn_to_imgs:
                ActivityUtils.getImageMainActivity();
                break;
        }
    }
}

五、集成编译与组件模式
在config.gradle里控制app的模式
isAlone = true,是作为组件模式,这个时候mod可以单独打包成apk,注意设置组件模式的时候,要注释掉app下gradle中的对mod_news的引用,不然会报错,或者在引用mod的时候加个判断(我demo里没做处理)

if (!rootProject.ext.isAlone) {
        implementation project(":mod_news")
    }

在这里插入图片描述
isAlone = false,只能作为集成模式,这个时候mod不可以独立打包
在这里插入图片描述

六、注意事项

1.在lib里使用butterknife时直接用R文件会报错(原因可以百度查看),需要使用R2文件
需要在lib库的build里最上方增加以下代码

apply plugin: 'com.jakewharton.butterknife'//不加这个,model库没法用butterknife

另在项目build的dependencies下添加

 classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'

2.在公共lib库里使用api方式导入butterKnife后,在app壳工程使用butterKnife不会报错,但是注解无效,这个原因我还不是很清楚,目前我是在每个lib下单独使用implementation去导入butterKnife的,如果有人能解决这个问题,请告诉我,不胜感激。

3.关于butterKnife版本问题,如果使用最新的版本,butterKnife把support包修改为androidx的,我尝试了下,把项目里所有的support都该文androidx会有一些列问题,所以我还是使用以前的suppor包,另尝试了下8.8.1版本的butterKnife,第一条注意事项的插件会报错,所以降级为8.4.0版本的butterKnife。

4.找不到路由的几种情况
1)检查下AndroidManifest文件有没有配置

 <meta-data
            android:name="com.skyunicorn.demo.mod_news.app.NewsApplication"
            android:value="IModuleConfig" />

2)检查下CRouteUtils里的路由配置
每个mod的一级路径不能相同

 /* mod_news */
    public static final String News_Activity_Main = "/mod_news/main";
    // 获取news模块的fragment
    public static final String News_Fragment_Main = "/mod_news/main";

    /* mod_image */
    public static final String Imgs_Activity_Main = "/mod_image/main";

目前demo只是一个空框架,后期我会在GitHub中逐渐维护,将其他控件、功能模块demo集成进去,作为一个交流、记录的app,如果只需要空框架的话,请在csdn下载。

吐槽下:
还是喜欢以前不用积分就能下载的优快云,开源的东西就是要用来分享的,现在下载动不动就要那么多积分。

demo链接:
1.组件化demo链接:
https://download.youkuaiyun.com/download/skyunicorn/11340949
2.GitHub地址:该框架目前还在整理中,暂不公开地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值