为Wear OS和普通应用程序构建的Android Studio项目,但共享源文件 [英] Android Studio project that builds for both Wear OS and normal app, but shares source files

查看:162
本文介绍了为Wear OS和普通应用程序构建的Android Studio项目,但共享源文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个非常小的Android应用程序,已移植到Wear OS.可以. 但是现在我有两个单独的项目,它们的源文件是99.5%相同. 如何将两个版本都放在一个项目中,所以每个公共源文件只需要一个副本?

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

我尝试在一个项目中制作两个模块,一个是"app"应用程序,另一个是其他服装".但是,由于模块似乎与目录相对应,因此不能直接解决共享源文件的问题.

我玩过构建配置", -但是我看不到那里的路径. 我花了一些时间处理依赖关系"的构建类型",但是我无法弄清楚如何使一个模块进入另一个模块的目录树,例如res/目录.

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

解决方案

命名空间

重要的是要使不同模块的源和资源的命名空间保持不同.确切地讲,这是任意的.

在共享代码和资源的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文件中.

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

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

这用于指示可用于访问该库的编译资源的程序包.

  • 用于Wear OS或Android应用程序的模块的清单将更加详尽-带有元素的通常结构

    <application><activity>

  • 清单中以."为前缀的软件包名称.相对于<manifest>标记的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'

应用程序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类结构(尽管这是一个很好的约定). 而是OS用来标识应用程序的应用程序(或程序包?)的标识符.

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版本 低于从属应用程序模块的数量,但反之亦然.

因此,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指令中链接的内容.

I have a very small Android app that I have ported to Wear OS. It works OK. But now I have two separate projects, whose source files are 99.5% identical. How can I put both versions in one project, so only one copy of each common source file is needed?

(For instance, the Manifest file needs to be tailored -- at least for the uses-feature android.hardware.type.watch, and one source file needs to be different -- menus in the Android app have to be handled differently on the Wear app. One resource was tailored for the small screen size. Everything else is identical.)

I tried making two modules in the one project, one "app" the other "wear". But since modules seem to correspond to directories, this doesn't directly address the problem of shared source files.

I played with "Build Configurations" -- but I see nothing about paths there. I spent some time with "Build Types", which deal with "dependencies", but I couldn't sort out how to make one module look into the other module's directory tree for, say a res/ directory.

What is the right way to resolve this?

解决方案

namespaces

It is important to keep the namespaces of source and resources of different modules distinct. Exactly how this is done is arbitrary.

In a simple case of an Android app and a Wear OS app that share code and resources, I adopted the following structure:

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>
    

The namespace of each module is defined in the Gradle build file.

If everything is set up properly, resources for dependent modules are merged with those of the library module, so that a reference such as

android:icon="@mipmap/ic_launcher"

could refer to a resource of that name defined in the library module, or one defined in the

(I don't know what happens when there is a resource name conflict.)

directory structure

I have been using this structure in projects where an Android app and a Wear OS app share a common library:

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. >

manifest

  • Each module must have its own manifest file, AndroidManifest.xml, under

    src/main/

Only those for apps are copied into the final APK file, though.

  • Library modules have a very simple manifest: two lines suffice, something like:

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

This serves to indicate the package under which compiled resources of that library can be accessed.

  • Manifests for modules for Wear OS or Android apps will be more elaborate -- the usual structure with elements

    <application> and <activity>

  • Names of packages within the manifest prefixed with a dot '.' are relative to the package attribute of the <manifest> tag. Other packages, especially those within a shared library module, may be accessed by a full explicit package path.

The above package structure can be neatly realized by a manifest structured like this:

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

The activity name will then be understood to be org.domain.theproject.app.MainActivity.

Also, the package attribute provides a package for the R resource in Java code: in the above case, the default R is really org.domain.theproject.R.

  • Resources referred to within the manifest ... are in a kind of flat namespace merged from the resource names of the library resources and the dependent module resources. It is very easy for a conflict to arise.

Gradle build file

Library build.gradle files must begin with

apply plugin: 'com.android.library'

App build.gradle files must begin with

apply plugin: 'com.android.application'

Besides the usual module dependencies, the build.gradle of app modules must list dependencies on the library modules they use. For instance, if an app module is dependent on library module "shared", then its build.gradle file must indicate that dependency in its dependencies section, like:

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

Of course, build.gradle files for Wear OS apps will also have dependencies for Wear OS, and Android apps will have dependencies for, say, AndroidX. But if a library they depend on has the same external dependencies, linking conflicts may occur. See "external library dependencies" below.

Note that the applicationId property, as in

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

does not necessarily reflect the Java class structure (although that is a good convention). It is rather the identifier for the app (or package?) that the OS uses to identify the app.

shortcomings of Android Studio

Android Studio squirrels information away willy-nilly, and regularly gets confused as to what it put where. All sorts of perplexing behavior results and much time is wasted. (This being just one general area of bug infestations in the program.)

It happens horribly often that things stop making sense, and

Build -> "Clean Project" 

does not suffice, and nor does that, plus

File -> "Sync Project with Gradle Files".  

Sometimes one must

File -> "Invalidate Caches/Restart"

And the fact is, sometimes one must turn AS off, go in with a console, and manually delete the hidden directories etc. I will not go into that here.

In doing the sorts of things described here, you will surely run into such issues, especially where it comes to the R interface between resources and Java code. I don't have any clear way to identify when these problems are happening, or which measure to take. You just have to develop a feeling for it.

Java source

classes

Obvious to those experienced with Java: the package names in Java files must reflect the directory structure; within AS, that means relative to the module's directory

src/main/java/

Library module classes are merged with those of the dependent modules at that level -- class names can be imported across modules in the usual Java way. So to effect the structure above, a Java file

If the Gradle config and manifest are configured properly, classes from the library module can be accessed from dependent modules as though the directories beneath the modules' src/main/java directory had been merged.

resources

The package that holds the automatically-generated resource package R is defined by the <manifest> tag's package attribute in the module's manifest file. But it is possible to refer explicitly to resources defined within other packages.

For instance, within the code of the dependent app package

org.domain.theproject.app,

the unqualified symbol R refers to the generated resource package

org.domain.theproject.app.R

The resources of the library package

org.domain.theproject

may be referenced explicitly in the same code via

org.domain.theproject.R

the R package in shared libraries

An annoyance of converting conventional app code to a shared library: the elements in R are not static -- so they can't be used in switch statements. It's not a big deal to convert them to if...else, but if you have a lot of them...

resource XML

TODO -- how to access resources of a library module from within resources of a dependent module?

minimum SDK versions

It is possible for the minimum SDK version of the library modules to be lower than that of the dependent app modules, but not the other way around.

Thus, the minimum SDK version for a Wear OS module could be the lowest possible for Wear apps (23), while that of the Android app module in the same project could be the lowest possible for AndroidX (14), provided the shared library module has its minimum SDK version set to 14.

The target SDK versions of all modules should match. (I guess -- haven't checked.)

external library dependencies

I have managed in simple examples to avoid using Jetifier, by manually resolving dependency conflicts between dependent modules and library modules. This way, APK file can turn out much smaller. It requires some thought, and experimentation.

This is all accomplished int the Gradle build file's dependencies block. I think the principle is: starting with the independent library modules, load required dependencies with the property

implementation

but in dependent modules that require the same dependencies, load them with the property

compileOnly

So if the library module and the app module need the external library

'androidx.recyclerview:recyclerview:1.1.0'

the library will have listed in its dependencies

implementation 'androidx.recyclerview:recyclerview:1.1.0'

while the app module will have

compileOnly 'androidx.recyclerview:recyclerview:1.1.0'

(If both are listed as implementation, then "Duplicate class" errors occur; if the independent class is missing the compileOnly, errors saying a package that "does not exist" will occur.)

Further, it is also possible to tailor just what is linked in the implementation directive by using exclude properties.

这篇关于为Wear OS和普通应用程序构建的Android Studio项目,但共享源文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆