百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Android组件化框架搭建

bigegpt 2024-08-07 17:52 5 浏览

背景

当一个项目经过N手人开发,N个产品经理的蹂躏,N长时间的维护,此时一定存在大量代码冗余、业务耦合、项目臃肿,资源文件大把重复等等,不堪重负。当需要增加新功能或者修改之前某个功能的时候,我相信很多同仁都说只敢增加,不敢随意地去删除、修改原有的代码,因为不知道哪些有用,哪些没有用。不但增加了维护成本,也在无形中增加了APK的体积,浪费了资源。在此背景下,就衍生出了模块化、组件化的概念。目前也已经有很多优秀的案例,我就踩在巨人的肩膀上搭建了符合组件业务的组件化框架。

头条不允许内嵌GitHub的网址 所以无法配置超链接 需要源码的小伙伴可以留言,无偿奉献给小伙伴们!


一.浅谈模块

其基本理念就是,把常用的功能、控件、基础类、第三方库、权限等公共部分抽离封装,把业务拆分成N个模块进行独立(module)的管理,而所有的业务组件都依赖于封装的基础库,业务组件之间不做依赖,这样的目的是为了让每个业务模块能单独运行。而在APP层对整个项目的模块进行组装,拼凑成一个完整的APP。借助路由(Arouter)来对各个业务组件之间的跳转,通过消息(eventbus)来做各个业务模块之间的通信。

模块化的好处:

  1. 解耦 只要封装做得好,实际开发中会省去大量地重复代码的coding。
  2. 结构清晰、层次明显,对后面的维护也是极其容易的。
  3. 每个业务模块可独立运行,单独提测,节省开发时间。

二.基础搭建

先来一张整个项目构思图


根据项目构思图搭建的项目结构图

下面逐一介绍每个模块的功能:

  • app模块: app壳没有任何功能主要就是集成每个业务组件,最终打包成一个完整的APK。app壳的gradle做如下配置,根据配置文件中的 isModule 字段来依赖不同的业务组件
...
dependencise{
      //公用依赖包
    implementation project(':common_base')
    if (!Boolean.valueOf(rootProject.ext.isModule)) {
        //main模块
        implementation project(':module_main')
        implementation project(':module_market')
        implementation project(':module_wan_android')
    }
}
...
  • common_base模块:功能组件主要负责封装公共部分,如第三方库加载、网络请求、数据存储、自定义控件、各种工具类等。

为了防止重复依赖问题,所有的第三方库都放在该模块加载,业务模块不再做任何的第三方库依赖,只做common_base库的依赖即可。

common模块无论在什么情况下都是以 library 的形式存在,所有的业务组件都必须依赖于common

其结构如下:

在commong的 gradle 中引入项目中使用的所有第三方库,业务组件就不用再去逐一引入


apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'
...
dependencies {
    // 在项目中的libs中的所有的.jar结尾的文件,都是依赖
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //把implementation 用api代替,它是对外部公开的, 所有其他的module就不需要添加该依赖
    api rootProject.ext.dependencies["appcompat_v7"]
    api rootProject.ext.dependencies["constraint_layout"]
    api rootProject.ext.dependencies["cardview-v7"]
    api rootProject.ext.dependencies["recyclerview-v7"]
    api rootProject.ext.dependencies["support-v4"]
    api rootProject.ext.dependencies["design"]
    api rootProject.ext.dependencies["support_annotations"]
    //MultiDex分包方法
    api rootProject.ext.dependencies["multidex"]

    //黄油刀
    annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]
    api rootProject.ext.dependencies["butterknife"]

    //Arouter路由
    annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
    api rootProject.ext.dependencies["arouter_api"]
    api rootProject.ext.dependencies["arouter_annotation"]

    //eventbus 发布/订阅事件总线
    api rootProject.ext.dependencies["eventbus"]

    //网络
    api rootProject.ext.dependencies["novate"]

    //日志
    api rootProject.ext.dependencies["logger"]

    //fastJson
    api rootProject.ext.dependencies["fastjson"]

    //沉浸栏
    api rootProject.ext.dependencies["barlibrary"]

    //banner
    api rootProject.ext.dependencies["banner"]

    //图片加载
    api rootProject.ext.dependencies["picasso"]

    //lombok
    api rootProject.ext.dependencies["lombok"]
    api rootProject.ext.dependencies["lombokJavax"]
}
  • 业务组件,在集成模式下它以 library 的形式存在。在组件开发模式下它以 application 的形式存在,可以单独独立运行。

业务组件完整的 gradle 如下:

if (Boolean.valueOf(rootProject.ext.isModule)) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
apply plugin: 'com.jakewharton.butterknife'
 ...
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //公用依赖包
    implementation project(':common_base')

    //Arouter路由
    annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
    //黄油刀
    annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]

}
  • 配置文件,对项目中的第三库、app的版本等配置
//全局统一配置文件
ext {
    //true 每个业务Module可以单独开发
    //false 每个业务Module以lib的方式运行
    //修改之后需要Sync方可生效
    isModule = false
    //版本号
    versions = [
            applicationId           : "com.wss.amd",        //应用ID
            versionCode             : 1,                    //版本号
            versionName             : "1.0.0",              //版本名称

            compileSdkVersion       : 27,
            buildToolsVersion       : "27.0.3",
            minSdkVersion           : 17,
            targetSdkVersion        : 23,

            androidSupportSdkVersion: "27.1.1",
            constraintLayoutVersion : "1.1.1",
            runnerVersion           : "1.0.1",
            espressoVersion         : "3.0.1",
            junitVersion            : "4.12",
            annotationsVersion      : "24.0.0",

            multidexVersion         : "1.0.2",
            butterknifeVersion      : "8.4.0",
            arouterApiVersion       : "1.4.0",
            arouterCompilerVersion  : "1.2.1",
            arouterannotationVersion: "1.0.4",
            eventbusVersion         : "3.0.0",
            novateVersion           : "1.5.5",
            loggerVersion           : "2.2.0",
            fastjsonVersion         : "1.1.54",
            barlibraryVersion       : "2.3.0",
            picassoVersion          : "2.71828",
            bannerVersion           : "1.4.10",
            javaxVersion            : "1.2",
            lombokVersion           : "1.16.6",
            greendaoVersion         : "3.2.2",

    ]
    dependencies = ["appcompat_v7"        : "com.android.support:appcompat-v7:${versions["androidSupportSdkVersion"]}",
                    "constraint_layout"   : "com.android.support.constraint:constraint-layout:${versions["constraintLayoutVersion"]}",
                    "runner"              : "com.android.support.test:runner:${versions["runnerVersion"]}",
                    "espresso_core"       : "com.android.support.test.espresso:espresso-core:${versions["espressoVersion"]}",
                    "junit"               : "junit:junit:${versions["junitVersion"]}",
                    "support_annotations" : "com.android.support:support-annotations:${versions["annotationsVersion"]}",
                    "design"              : "com.android.support:design:${versions["androidSupportSdkVersion"]}",
                    "support-v4"          : "com.android.support:support-v4:${versions["androidSupportSdkVersion"]}",
                    "cardview-v7"         : "com.android.support:cardview-v7:${versions["androidSupportSdkVersion"]}",
                    "recyclerview-v7"     : "com.android.support:recyclerview-v7:${versions["androidSupportSdkVersion"]}",

                    //方法数超过65535解决方法64K MultiDex分包方法
                    "multidex"            : "com.android.support:multidex:${versions["multidexVersion"]}",

                    //路由
                    "arouter_api"         : "com.alibaba:arouter-api:${versions["arouterApiVersion"]}",
                    "arouter_compiler"    : "com.alibaba:arouter-compiler:${versions["arouterCompilerVersion"]}",
                    "arouter_annotation"  : "com.alibaba:arouter-annotation:${versions["arouterannotationVersion"]}",

                    //黄油刀
                    "butterknife_compiler": "com.jakewharton:butterknife-compiler:${versions["butterknifeVersion"]}",
                    "butterknife"         : "com.jakewharton:butterknife:${versions["butterknifeVersion"]}",

                    //事件订阅
                    "eventbus"            : "org.greenrobot:eventbus:${versions["eventbusVersion"]}",

                    //网络
                    "novate"              : "com.tamic.novate:novate:${versions["novateVersion"]}",

                    //日志
                    "logger"              : "com.orhanobut:logger:${versions["loggerVersion"]}",

                    //fastJson
                    "fastjson"            : "com.alibaba:fastjson:${versions["fastjsonVersion"]}.android",

                    //沉浸式状态栏
                    "barlibrary"          : "com.gyf.barlibrary:barlibrary:${versions["barlibraryVersion"]}",

                    //banner
                    "banner"              : "com.youth.banner:banner:${versions["bannerVersion"]}",

                    //图片加载
                    "picasso"             : "com.squareup.picasso:picasso:${versions["picassoVersion"]}",

                    //lombok
                    "lombokJavax"         : "javax.annotation:javax.annotation-api:${versions["javaxVersion"]}",
                    "lombok"              : "org.projectlombok:lombok:${versions["lombokVersion"]}",

                    //数据库
                    "greenDao"            : "org.greenrobot:greendao:${versions["greendaoVersion"]}",
    ]
}


最后别忘记在工程的中 build.gradle引入该配置文件

apply from: "config.gradle"

修改isModule字段之后 需要Sysn才会生效

三.搭建过程中遇到的问题

  • Application 、全局Context、 Activity管理问题

在功能组件即Demo中的common_base封装BaseApplication,在BaseApplication对第三方库初始化、全局Context的获取等操作。在BaseActivity中对Activity进行添加和移除的管理

//BaseApplicion
public class BaseApplication extends Application {
    ...
    //全局唯一的context
    private static BaseApplication application;

    //Activity管理器
    private ActivityManage activityManage;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        application = this;
        //MultiDex分包方法 必须最先初始化
        MultiDex.install(this);
    }
    public void onCreate() {
        super.onCreate();
        activityManage = new ActivityManage();
        initARouter();
        initLogger();
    }
  /**
     * 获取全局唯一上下文
     *
     * @return BaseApplication
     */
    public static BaseApplication getApplication() {
        return application;
    }
}

//BaseActivity
public abstract class BaseActivity extends Activity {  
    ...
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //加入Activity管理器
        BaseApplication.getApplication().getActivityManage().addActivity(this);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //将Activity从管理器移除
        BaseApplication.getApplication().getActivityManage().removeActivityty(this);
    }
}
  • AndroidManifest的管理

我们知道APP在打包的时候最后会把所有的AndroidManifest进行合并,所以每个业务组件的Activity只需要在各自模块的AndroidManifest中注册即可。如果业务组件需要独立运行,则需要单独配置一份AndroidManifest,在gradle的sourceSets根据不同的模式加载不同的AndroidManifest文件。

gradle配置

...
android {
   ...
    sourceSets {
        main {
            if (Boolean.valueOf(rootProject.ext.isModule)) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {
                    //排除java/debug文件夹下的所有文件
                    exclude '*module'
                }
            }
        }
    }
}
...

注意:在配置Gradle的时候 manifest.srcFile... manifest 是小写的

其中集成模式加载的Manifest中不能设置Application和程序入口:

//集成模式下Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wss.module.wan">
    <application>
        <activity android:name=".main.WanMainActivity" />
    </application>
</manifest>

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

        <activity android:name=".main.WanMainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

    </application>
</manifest>

需要注意的是如果在组件开发模式下,组件的Applicaion必须继承自BaseApplicaion

  • 不同组件之间的跳转

业务组件之间没有依赖,不能通过常规的Intent显示的进行跳转,这个时候就需要引入路由的概念


利用阿里的[ARouter]对需要跳转的页面做配置

gradle配置

android {
   ...
       defaultConfig {
         ...
        //Arouter路由配置
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
                includeCompileClasspath = true
            }
        }
    }
}
dependencies{
     ...
    //Arouter路由
    annotationProcessor rootProject.ext.dependencies["arouter_compiler"]
}


目标页面配置

@Route(path = "/wan/WanMainActivity")
public class WanMainActivity extends ActionBarActivity<WanMainPresenter> implements IWanMainView,
 OnRcyItemClickListener {
      ...
}

跳转

...
   ARouter.getInstance()
          .build("/wan/WanMainActivity")
          .navigation();
...


  • 不同组件之间通信

可以利用第三方 如[EventBus]对消息进行管理。在common_base组件中的Base类做了对消息的简单封装,子类只需要重写regEvent()返回true即可对事件的注册,重写onEventBus(Object)即可对事件的接收。

public abstract class BaseActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        if (regEvent()) {
            EventBus.getDefault().register(this);
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (regEvent()) {
            EventBus.getDefault().unregister(this);
        }
    }
    /**
     * 子类接收事件 重写该方法
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventBus(Object event) {
    }

    /**
     * 需要接收事件 重写该方法 并返回true
     */
    protected boolean regEvent() {
        return false;
    }


  • Butterknife的问题

在library中使用butterknife会存在找不到的问题。

推荐使用8.4.0版本,用R2代替R,onClick中使用if else不要使用switch case即可解决问题 。

public class HomeFragment extends BaseMvpFragment<HomePresenter> implements IHomeView, OnRcyItemClickListener {
   
    @BindView(R2.id.banner)
    Banner banner;

    @BindView(R2.id.recycle_view)
    RecyclerView recyclerView;  
    ...

    @OnClick({R2.id.tv_title, R2.id.btn_open})
    public void onClick(View v) {
        if (v.getId() == R.id.tv_title) {
            //do something

        } else if (v.getId() == R.id.btn_open) {
            //do something
        }
    }
}
  • 资源文件冲突问题

目前没有比较好的约束方式,只能通过设置资源的前缀来防止资源文件冲突,然后在提交代码的时候对代码进行检查是否规范来控制。


该文章于2018年首次发表,历经3年多的变革,其中使用的一些技术以及开发技巧可能有更新,请各位读者按照实际开发过程中的情况酌情调优~

头条不允许内嵌GitHub的网址 所以无法配置超链接 需要源码的小伙伴可以留言,无偿奉献给小伙伴们!

相关推荐

php-fpm的配置和优化

目录概述php-fpm配置php-fpm进程优化配置慢日志查询配置php7进阶到架构师相关阅读概述这是关于php进阶到架构之php7核心技术与实战学习的系列课程:php-fpm的配置和优化学习目标:理...

成功安装 Magento2.4.3最新版教程「技术干货」

外贸独立站设计公司xingbell.com经过多次的反复实验,最新版的magento2.4.3在oneinstack的环境下的详细安装教程如下:一.vps系统:LinuxCentOS7.7.19...

十分钟让你学会LNMP架构负载均衡

业务架构、应用架构、数据架构和技术架构一、几个基本概念1、pv值pv值(pageviews):页面的浏览量概念:一个网站的所有页面,在一天内,被浏览的总次数。(大型网站通常是上千万的级别)2、u...

php从远程URL获取(mp4 mp3)音视频的流媒体数据

/***从远程URL中获取媒体(如mp4mp3)的内容*@parammixed$file_url*@parammixed$media_type...

Zabbix5.0安装部署

全盘展示运行状态,减轻运维人员的重复性工作量,提高系统排错速度,加速运维知识学习积累。1.png1、环境安装关闭SELinux并重启系统2.png安装httpd、mariadb、php运行yum-...

php 常见配置详解

以下是PHP常见的配置项及其含义:error_reporting:设置错误报告级别,可以控制PHP显示哪些错误。例如,设置为E_ALL将显示所有错误,而设置为0将禁止显示任何错误。displa...

实践分享|基于基石智算 DeepSeek API + WordPress 插件自动生成访客回复

基石智算举办的DeepSeek案例大赛汇集了不少基于CoresHubDeepSeekAPI服务或模型部署服务的精彩实践。本次我们将分享个人实践:通过DeepSeekAPI+Word...

如何在Eclipse中搭建Zabbix源码的调试和开发环境

Zabbix是一款非常优秀的企业级软件,被设计用于对数万台服务器、虚拟机和网络设备的数百万个监控项进行实时监控。Zabbix是开放源码和免费的,这就意味着当出现bug时,我们可以很方便地通过调试源码来...

MySQL自我保护参数

#头条创作挑战赛#之前(MySQL自我保护工具--pt-kill)提到用pt-kill工具来kill相关的会话,来达到保护数据库的目的,本文再通过修改数据库参数的方式达到阻断长时间运行的SQL的目...

Python闭包深度解析:掌握数据封装的高级技巧

闭包作为Python高级编程特性之一,为开发者提供了一种优雅的方式来实现数据封装和状态保持。这一概念源于函数式编程理论,在现代Python开发中发挥着重要作用。理解和掌握闭包的使用不仅能够提升代码的表...

Java服务网格故障注入与熔断实战

在分布式系统的高可用性挑战中,服务网格的故障注入与熔断机制是检验系统韧性的终极试金石。以下是10道逐步升级的"地狱关卡",每个关卡都对应真实生产环境中可能遇到的致命场景,并附具体场景示...

MySQL数据库性能优化全攻略:程序员必知的七大核心策略

作为程序员,我们每天都要与数据库打交道。当系统用户量突破百万级时,数据库往往成为性能瓶颈的首要怀疑对象。本文将深入探讨MySQL优化的七大核心策略,并提供可直接落地的优化方案,助您构建高效稳定的数据库...

如何在 Windows 11 上使用单个命令安装 XAMPP

XAMPP是一种广泛使用的软件,用于在Windows操作系统上快速运行LAMP服务器包,包括Windows11。尽管LAMP通常用于Linux系统,但XAMPP并不使用Li...

uTorrent怎样将bt种子转换为磁力

如何用uTorrent把BT种子转为磁力链接?以下方法希望能帮到你。1、在uTorrent窗口里,点击工具栏的按钮,所示。2、在打开窗口里,选取要转为磁力的种子文件,然后点击打开按钮,参照图示操作...

支持向量机SVM 分类和回归的实例

支持向量机(SupportVectorMachine)是Cortes和Vapnik于1995年首先提出的,它在解决小样本、非线性及高维模式识别中表现出许多特有的优势,并能够推广应用到函数拟合等其他...