Android 多客户模块化架构实现

637 阅读3分钟

在 Android 应用开发中,面对多个定制客户版本的需求时,频繁修改包名、资源、Application 类等内容往往十分低效且易出错。为了解决这一痛点,本文介绍一种清晰、灵活的多客户模块化架构设计,能够有效应对不同客户的定制需求,同时保持代码结构清晰、可维护。


思路:

将主项目作为一个壳,配置包名以及需要Application需要配置的属性,具体实现全部放在业务module中,这样就做到类包名和具体实现的解耦,项目需要频繁改包名的时候就不需要动业务层逻辑了.

1:项目结构


project-root/
├── app/                  # 主壳工程 只配置属性无业务逻辑
├── base/                 # 公共工具模块
├── business/             # 通用业务模块(Activity、ViewModel 等)
├── clientA/              # 客户 A 定制模块
├── clientB/              # 客户 B 定制模块

2: 主项目配置(app)

主项目作为App启动的模块需要配置签名/启动类/网络/provider的适配,只需要处理bulid.gradleAndroidManifest.xml

2.1 bulid.gradle配置

plugins {
	//application
    alias(libs.plugins.android.application)
    alias(libs.plugins.jetbrains.kotlin.android)
	//kapt  我这里用的kapt  apt 以及 ksp 自己适配
    alias(libs.plugins.jetbrains.kotlin.kapt)
}

android {
    namespace 'com.xxx.xx'
    compileSdk libs.versions.compileSdk.get().toInteger()

    defaultConfig {
        applicationId 'com.xxx.xx'
        minSdk libs.versions.minSdk.get().toInteger()
        targetSdk libs.versions.targetSdk.get().toInteger()
        versionCode  libs.versions.versionCode.get().toInteger()
        versionName libs.versions.versionName.get().toString()

        multiDexEnabled true
        vectorDrawables {
            useSupportLibrary true
        }
        renderscriptSupportModeEnabled true
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a'
        }

    }

    signingConfigs {
        jks {
            storeFile file(StoreFile)
            storePassword StorePassword
            keyAlias KeyAlias
            keyPassword KeyPassword
        }
    }

	//配置签名
    buildTypes {
        debug {
            // 混淆开关
            minifyEnabled false
            debuggable true
            signingConfig signingConfigs.jks
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        }
        release {
            // 混淆开关
            minifyEnabled false
            // 移除无用的resource文件
            shrinkResources false
            debuggable false

            signingConfig signingConfigs.jks
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        }
    }
	//配置 打包名字
    android.applicationVariants.configureEach {
        variant ->
            variant.outputs.configureEach {
                outputFileName = "Voice_version-"+libs.versions.versionName.get()+".apk"
            }
    }


}


dependencies {
	// 配置 App 的实现module
    implementation project(':app_a')
    implementation project(':app_b')

}

2.2 AndroidManifest.xml配置


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools">


    <application
        android:name="xxx.xxx"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:extractNativeLibs="true"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:largeHeap="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:preserveLegacyExternalStorage="true"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:hardwareAccelerated="true"
        android:supportsRtl="true"
        android:theme="@style/Theme.Voice"
        android:usesCleartextTraffic="true"
        tools:replace="android:name,android:allowBackup"
        tools:targetApi="31">

		
		//配置 provider 适配文件读取
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>


		//配置 启动页面
        <activity
            android:name="xxx.WelcomeActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:screenOrientation="portrait"
            android:theme="@style/SplashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

    </application>

</manifest>

3. module配置

3.1 bulid.gradle配置

plugins {
    alias(libs.plugins.android.library)
    alias(libs.plugins.jetbrains.kotlin.android)
    alias(libs.plugins.jetbrains.kotlin.kapt)
}


android {
    namespace 'com.qianrun.voice'
    compileSdk libs.versions.compileSdk.get().toInteger()

    defaultConfig {
        minSdk libs.versions.minSdk.get().toInteger()
    }

    buildTypes {
        debug {
            debuggable true
        }
        release {
            debuggable false
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = '11'
    }

    buildFeatures {
        viewBinding true
        buildConfig = true
    }

}

dependencies {

    testImplementation libs.junit
    androidTestImplementation libs.androidx.junit
    androidTestImplementation libs.androidx.espresso.core


    implementation libs.androidx.core.ktx
    implementation libs.androidx.lifecycle.runtime.ktx

    implementation libs.androidx.appcompat
    implementation libs.com.google.android.material
    implementation libs.androidx.recyclerview
    implementation libs.androidx.constraintlayout

    implementation project(':lib_net')
    implementation project(':lib_common')
    implementation project(':lib_util')
    implementation project(':lib_live')
}

2.2 AndroidManifest.xml配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

   // 权限
   ...
   
    <application
        tools:replace="android:name"
       >

		//页面
        <activity
            android:name="com.qianrun.voice.MainActivity"
            android:exported="true"
            android:configChanges="uiMode|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
            android:launchMode="singleTop" />
        <activity android:name="com.qianrun.voice.ui.activity.RegisterInformationActivity" />
        <activity android:name="com.qianrun.voice.ui.activity.PasswordAndRegisterActivity" />
        <activity android:name="com.qianrun.voice.ui.activity.VerificationCodeActivity" />


        <activity android:name="com.qianrun.voice.ui.activity.LoginActivity" />
        <activity android:name="com.qianrun.voice.test2.TestBasicActivity" />
        <activity android:name="com.qianrun.voice.test2.TestBasicTitleActivity" />
        <activity android:name="com.qianrun.voice.test2.TestBasicVbActivity" />
        <activity android:name="com.qianrun.voice.test2.TestBasicVmActivity" />
        <activity android:name="com.qianrun.voice.test2.TestBasicVbVmActivity" />
        <activity android:name="com.qianrun.voice.test2.TestBasicTitleVbActivity" />
        <activity android:name="com.qianrun.voice.test2.TestBasicTitleVmActivity" />
        <activity android:name="com.qianrun.voice.test2.TestBasicTitleVbVmActivity" />
        <activity android:name="com.qianrun.voice.test2.TestBasicFragmentActivity" />
        <activity android:name="com.qianrun.voice.ui.activity.SearchActivity" />
        <activity android:name="com.qianrun.voice.ui.activity.UserInfoActivity"

            />
        <activity android:name="com.qianrun.voice.ui.activity.FFFActivity" />
        <activity android:name="com.qianrun.voice.ui.activity.NoblePrivilegeActivity" />
        <activity android:name="com.qianrun.voice.ui.youth.YouthModeActivity" />
        <activity android:name="com.qianrun.voice.ui.ranking.RankingActivity" />
        <activity android:name="com.qianrun.voice.ui.activity.PublishDynamicsActivity" />
    </application>

</manifest>

总结

本文提出了一种适用于多客户场景的 Android 模块化架构设计方案。通过将主项目设计为“空壳”,业务逻辑与客户定制拆分为独立模块,并结合 Gradle 的 flavornamespacemanifestPlaceholders 等机制,实现了如下优势:

  • 客户版本切换无需手动改代码,构建高效可靠
  • 客户资源、包名、Application 类等完全隔离,避免冲突
  • 支持自动打包、CI/CD 集成,适合规模化运营
  • 架构清晰,便于团队协作与后期维护

这种架构非常适用于有多个定制客户的 Android 应用项目,可显著提升开发效率与系统稳定性。