为 Wear OS 和普通应用程序构建的 Android Studio 项目,但共享源文件

Posted

技术标签:

【中文标题】为 Wear OS 和普通应用程序构建的 Android Studio 项目,但共享源文件【英文标题】:Android Studio project that builds for both Wear OS and normal app, but shares source files 【发布时间】:2020-10-22 22:14:18 【问题描述】:

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

(例如,Manifest 文件需要定制——至少对于uses-feature android.hardware.type.watch,并且一个源文件需要不同——Android 应用程序中的菜单必须在 Wear 应用程序上以不同方式处理。一个资源是为小屏幕尺寸量身定制的。其他一切都是一样的。)

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

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

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

【问题讨论】:

我在 Linux 中使用软链接实现了一种变通方法。但这使得该项目不可移植。 这能回答你的问题吗? Sharing files between Android mobile and wear modules 嗯,这是一个方向。我认为这个想法是有一个“通用”模块来构建一个库文件,然后将其包含在其他模块中。对于这个非常小的项目,构建一个库似乎有点过分——我只想安排更改 java 文件的包含路径。我玩了一下:首先,在 AS 4.0 中,compile 指令必须更改为implementation。共享模块的 build.gradle 文件必须指定apply plugin: 'com.android.library'。接下来...共享模块需要一个清单。我被困在那里了。 共享库模块的清单实际上可以是单行的:<manifest package="com.example.common" />。它不能与您的(非库)应用程序模块的包名称相同。 Android Studio 在查找资源方面可能有点不靠谱,但如果遇到问题,请查看您的 include 中的 .R 路径。应该匹配源文件所在模块的包名。FWIW,这与Wear无关,它是标准库模块的东西;你读过developer.android.com/studio/projects/… 的文档吗? 【参考方案1】:

命名空间

保持不同模块的源和资源的命名空间不同是很重要的。具体如何完成是任意的。

在一个共享代码和资源的 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,在

src/main/

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

库模块有一个非常简单的清单:两行就足够了,类似于:

&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;manifest package="org.domain.theproject"/&gt;

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

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

&lt;application&gt;&lt;activity&gt;

清单中以点“.”为前缀的软件包名称与&lt;manifest&gt; 标记的package 属性相关。其他包,尤其是共享库模块中的包,可以通过完整的显式包路径访问。

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

<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 实际上是org.domain.theproject.R

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

Gradle 构建文件

build.gradle 文件必须以

开头
apply plugin: 'com.android.library'

App build.gradle 文件必须以

开头
apply plugin: 'com.android.application'

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

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

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

注意applicationId 属性,如

android 
 defaultConfig 
        applicationId "org.domain.theproject"
        

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

Android Studio 的不足

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

事情失去意义的事情经常发生,而且

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的包是 由模块清单文件中的&lt;manifest&gt; 标签的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 版本是可能的 低于依赖的应用程序模块,但不是相反。

因此,Wear OS 模块的最低 SDK 版本可能是 Wear 应用的最低版本 (23), 而同一项目中的 Android 应用程序模块对于 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,则错误说是一个包 会出现“不存在”。)

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

【讨论】:

以上是关于为 Wear OS 和普通应用程序构建的 Android Studio 项目,但共享源文件的主要内容,如果未能解决你的问题,请参考以下文章

全新 Google Pixel Watch 重磅上线 | 着手为 Wear OS 构建应用!

使用 Compose 构建 Wear OS 应用

Compose for Wear OS 1.1 推出稳定版: 了解新功能!

Compose for Wear OS 1.1 推出稳定版: 了解新功能!

一起看 I/O | Compose for Wear OS Beta 版发布

一起看 I/O | Compose for Wear OS Beta 版发布