首页 > 解决方案 > 为 Wear OS 和普通应用程序构建的 Android Studio 项目,但共享源文件

问题描述

我有一个非常小的 Android 应用程序,已移植到 Wear OS。它工作正常。但现在我有两个独立的项目,它们的源文件 99.5% 相同。如何将两个版本放在一个项目中,因此每个公共源文件只需要一份副本?

uses-feature android.hardware.type.watch(例如, Manifest文件需要定制——至少对于屏幕尺寸。其他一切都相同。)

我尝试在一个项目中制作两个模块,一个“应用程序”另一个“磨损”。但是由于模块似乎与目录相对应,这并不能直接解决共享源文件的问题。

我玩过“构建配置”——但我看不到那里的路径。我花了一些时间在处理“依赖项”的“构建类型”上,但我无法弄清楚如何让一个模块查看另一个模块的目录树,比如 res/ 目录。

解决此问题的正确方法是什么?

标签: android-studiogradlemodulewear-osandroid-tv

解决方案


命名空间

保持不同模块的源和资源的名称空间不同是很重要的。究竟如何做到这一点是任意的。

在一个共享代码和资源的 Android 应用和 Wear OS 应用的简单案例中,我采用了以下结构:

org.domain.theproject
    <shared code and resources under this>
    org.domain.theproject.app
        <Android app code and resources>
    org.domain.theproject.wear
        <Wear OS app code and resources>
    

每个模块的命名空间在 Gradle 构建文件中定义。

如果一切设置正确,则依赖模块的资源将与库模块的资源合并,以便引用如

android:icon="@mipmap/ic_launcher"

可以引用在库模块中定义的同名资源,或者在

(不知道发生资源名称冲突时会发生什么。)

目录结构

我一直在 Android 应用程序和 Wear OS 应用程序共享一个公共库的项目中使用这种结构:

TheProject/
    AppModule/
        src/main/
            java/org/domain/theproject/
                app/
                    <Android app source files>
            res/
                layout/
                < etc. >
    SharedModule/
        src/main/
            java/org/domain/theproject/
                <shared library source files>
            res/
                layout/
                <etc. >
    WearModule/
        src/main/
            java/org/domain/theproject/
                wear/
                    <Wear OS app source files>
            res/
                layout/
                < etc. >

显现

  • 每个模块必须有自己的清单文件AndroidManifest.xml,在

    源/主/

不过,只有那些应用程序会被复制到最终的 APK 文件中。

  • 库模块有一个非常简单的清单:两行就足够了,例如:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest package="org.domain.theproject"/>

这用于指示可以访问该库的已编译资源的包。

  • Wear OS 或 Android 应用程序的模块清单将更加精细——通常的元素结构

    <application><activity>

  • 清单中以点“.”为前缀的软件包名称 是相对于标签的package属性。<manifest>其他包,尤其是共享库模块中的包,可以通过完整的显式包路径访问。

上面的包结构可以通过如下结构的清单巧妙地实现:

<manifest 
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.domain.theproject.app">
    <!-- ... -->
    <application >
        <activity android:name=".MainActivity">
            <!-- ... -->

然后活动名称将被理解为org.domain.theproject.app.MainActivity

此外,该package属性为 Java 代码中的资源提供了一个包R:在上述情况下,默认R值为 real org.domain.theproject.R

  • 清单中引用的资源......在一种平面命名空间中,由库资源和依赖模块资源的资源名称合并而成。很容易发生冲突。

Gradle 构建文件

build.gradle文件必须以

apply plugin: 'com.android.library'

应用build.gradle文件必须以

apply plugin: 'com.android.application'

除了通常的模块依赖之外,build.gradle应用程序模块必须列出它们使用的库模块的依赖关系。例如,如果一个应用程序模块依赖于“共享”库模块,那么它的build.gradle文件必须在其依赖项部分指出该依赖项,例如:

dependencies {
    implementation project(':shared')
    //...
}

当然,build.gradleWear OS 应用程序的文件也将依赖于 Wear OS,而 Android 应用程序将依赖于例如 AndroidX。但是如果它们所依赖的库具有相同的外部依赖关系,则可能会发生链接冲突。请参阅下面的“外部库依赖项”。

请注意,该applicationId属性,如

android {
 defaultConfig {
        applicationId "org.domain.theproject"
        }}

不一定反映 Java 类结构(尽管这是一个很好的约定)。它是操作系统用来识别应用程序的应用程序(或包?)的标识符。

Android Studio 的缺点

Android Studio 会不顾一切地将信息隐藏起来,并且经常对它放在哪里感到困惑。各种令人费解的行为结果和大量时间都被浪费了。(这只是程序中错误感染的一个一般领域。)

事情不再有意义的事情经常发生,而且

Build -> "Clean Project" 

是不够的,也不是,加上

File -> "Sync Project with Gradle Files".  

有时必须

File -> "Invalidate Caches/Restart"

事实是,有时必须关闭 AS,使用控制台进入,并手动删除隐藏目录等。我不会在这里讨论。

在做这里描述的事情时,你肯定会遇到这样的问题,尤其是在资源和 Java 代码之间的 R 接口方面。我没有任何明确的方法来确定这些问题何时发生,或者采取何种措施。你只需要对它产生一种感觉。

Java 源代码

班级

熟悉 Java 的人很清楚:Java 文件中的包名必须反映目录结构;在 AS 中,这意味着相对于模块的目录

src/main/java/

库模块类与该级别的依赖模块的类合并——类名可以以通常的 Java 方式跨模块导入。因此,要实现上述结构,需要一个 Java 文件

如果 Gradle 配置和清单配置正确,则可以从依赖模块访问库模块中的类,就好像模块目录下的src/main/java目录已合并一样。

资源

包含自动生成的资源包的包由模块清单文件中的标记属性R定义。但是可以显式引用其他包中定义的资源。<manifest>package

例如,在依赖应用程序包的代码中

org.domain.theproject.app,

不合格符号R指的是生成的资源包

org.domain.theproject.app.R

库包的资源

org.domain.theproject

可以通过在同一代码中显式引用

org.domain.theproject.R

共享库中的 R 包

将传统应用程序代码转换为共享库的烦恼:其中的元素R不是静态的——因此它们不能在switch语句中使用。将它们转换为 没什么大不了的if...else,但是如果您有很多...

资源 XML

TODO——如何从依赖模块的资源中访问库模块的资源?

最低 SDK 版本

库模块的最低 SDK 版本可能低于依赖应用程序模块的最低 SDK 版本,但反之则不然。

因此,Wear OS 模块的最低 SDK 版本可能是 Wear 应用程序的最低版本 (23),而同一项目中的 Android 应用程序模块的最低 SDK 版本可能是 AndroidX 的最低版本 (14),前提是共享库模块将其最低 SDK 版本设置为 14。

所有模块的目标 SDK 版本应匹配。(我猜 - 还没有检查。)

外部库依赖

通过手动解决依赖模块和库模块之间的依赖冲突,我在简单的示例中进行了管理,以避免使用 Jetifier。这样,APK 文件可以变得更小。它需要一些思考和实验。

这一切都在 Gradle 构建文件的dependencies块中完成。我觉得原理是:从独立的库模块开始,用属性加载需要的依赖

implementation

但是在需要相同依赖项的依赖模块中,使用属性加载它们

compileOnly

所以如果库模块和应用模块需要外部库

'androidx.recyclerview:recyclerview:1.1.0'

图书馆将在其dependencies

implementation 'androidx.recyclerview:recyclerview:1.1.0'

而应用程序模块将具有

compileOnly 'androidx.recyclerview:recyclerview:1.1.0'

(如果两者都被列为implementation,则发生“重复类”错误;如果独立类缺少compileOnly,则会发生错误说“不存在”的包。)

此外,还可以implementation通过使用exclude属性来定制指令中链接的内容。


推荐阅读