项目中同时拥有GMS和HMS

Have both GMS and HMS in the project

如何在应用程序中同时拥有 Google 移动服务和 华为 移动服务?

由于华为失去了GMS的授权,看来我们需要更换所有GMS 华为提供的应用程序中使用的服务。为此,“最佳实践”是什么?使用 flavors 并以某种方式单独处理每个 class,或者复制粘贴项目并开始替换?或者……更好的是,有没有一种方法可以同时拥有这两者……以某种方式让应用程序根据其所在的设备决定使用哪种服务?显然,最后一个会假定 A​​PK 文件大小增加。

有什么想法吗?

虽然这确实取决于您应用的架构,但目前有 2 种合理的选择;

  1. 使用风味和变体,这将为您提供更大的灵活性。建立架构和实施会相对耗时更多,但它是一种提供良好代码隔离的干净方法。由于这些生态系统有不同的市场(华为的 AppGallery),具有风格和变体,因此建立单独的构建管道非常方便。它使您能够为不同的生态系统维护不同的 apk
  2. 使用 wrapper/bridge 方法。简单地说,实现包装器 类 来决定并将请求转发到相应的端点。通过这种方法,可以为两个市场保持单一。 HMS实际上为此提供了一个强大的工具。它分析依赖GMS的代码,然后自动生成wrapper 类 并将原始代码转换为使用wrapper 类。它被称为 "HMS Converter",甚至有一个 Android Studio 插件。 https://developer.huawei.com/consumer/en/huawei-toolkit/

在我回答你的问题之前,先简单解释一下什么是 HMS 和 GMS:

  • HMS代表华为移动服务
  • GMS 代表 Google 移动服务

您可以在华为的应用商店(名为 AppGallery)中发布您的应用(使用 Google 的库),但此应用将仅在包含 HMS+GMS 的华为设备上可见并可供下载(到 2020 年的所有设备都有 HMS 和 GMS)。

但是较新的手机,即 Mate 30 系列、P40 - 将只安装 HMS。因此,如果你想让你的应用程序对所有华为设备(HMS+GMS 和 HMS)可见,那么你必须在你的应用程序中实现检测用户设备上有什么服务的功能。它将决定调用什么适当的函数(即华为地图或 Google 地图的初始化实例)。

检测HMS和GMS的代码如下:

对于华为移动服务,我们使用:

HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(context);

https://developer.huawei.com/consumer/en/doc/development/HMS-References/huaweiapiavailability

对于 Google 我们使用的移动服务:

GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);

https://developers.google.com/android/reference/com/google/android/gms/common/GoogleApiAvailability

下面是正确处理 HMS 和 GMS 检测的代码:

public static boolean isHmsAvailable(Context context) {
    boolean isAvailable = false;
    if (null != context) {
        int result = HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(context);
        isAvailable = (com.huawei.hms.api.ConnectionResult.SUCCESS == result);
    }
    Log.i(TAG, "isHmsAvailable: " + isAvailable);
    return isAvailable;
}

public static boolean isGmsAvailable(Context context) {
    boolean isAvailable = false;
    if (null != context) {
        int result = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
        isAvailable = (com.google.android.gms.common.ConnectionResult.SUCCESS == result);
    }
    Log.i(TAG, "isGmsAvailable: " + isAvailable);
    return isAvailable;
}

AFAIK 这些 类 (HuaweiApiAvailability/GoogleApiAvailability) 如果您实施任何华为 kit/Google 的库,则可用。

所以,我设法做到了:

定义了两种口味

    gms {
        dimension "services"
        buildConfigField "String", "SERVICE_USED", '"g"'

    }
    hms {
        dimension "services"
        buildConfigField "String", "SERVICE_USED", '"h"'
    }

我在代码中使用 "g" 和 "h" 每当我需要决定做这样的事情时: API 需要 deviceType of "android" 或 "iOS" 并包含华为版本,我们定义了另一个常量 "huawei"。我使用 SERVICE_USED 来知道要发送什么常量。

然后我在 build.gradle:

的顶部做了这个
apply plugin: 'com.android.application'
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Hms")) {
    //*meh*
} else {
    apply plugin: 'io.fabric'
}

因为我使用的是 fabric(和 fabric / firebase ... 不能真正与 HMS 一起使用)而且我也在 build.gradle

的最底部做了这个
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Hms")) {
    apply plugin: 'com.huawei.agconnect'
} else {
    apply plugin: 'com.google.gms.google-services'
}

只包含合适的插件。

然后我开始处理使用 gms 的每个东西(地图、位置、推送通知、分析),方法是制作一个包装器并将代码分离到每种风格中。即对于推送通知,我创建了一个 HPushNotif,它有一个 getToken 方法。我在两种风格中定义了相同的 class 和方法,但我根据服务类型(gms 或 hms)实现它们。

我在项目中包含依赖项时使用了这种类型的表示法:

//GMS stuff
gmsImplementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
gmsImplementation 'com.google.firebase:firebase-core:16.0.9'
gmsImplementation 'com.google.firebase:firebase-messaging:18.0.0'
gmsImplementation 'com.google.firebase:firebase-crash:16.2.1'
gmsImplementation 'com.google.android.gms:play-services-maps:16.1.0'
gmsImplementation 'com.google.android.gms:play-services-location:16.0.0'
gmsImplementation 'com.google.android.gms:play-services-tagmanager:16.0.8'

//HMS stuff
hmsImplementation 'com.huawei.agconnect:agconnect-core:1.0.0.300'
hmsImplementation 'com.huawei.hms:push:4.0.3.301'
hmsImplementation 'com.huawei.hms:maps:4.0.1.301'
hmsImplementation 'com.huawei.hms:location:4.0.3.303'

Implementation前的gmshms指的是口味名称。只有在选择了适当的 BuildVariant 时才会加载这些依赖项(即正在构建适当的风格)。

基本上,我为这两种情况包装了地图、分析、位置和推送通知的逻辑。这就是结构的外观。没什么特别的。

就是这样。当他们创建 HMS 时,他们基本上通过 class 复制 GMS class 并通过方法复制方法。您会看到确切的方法名称与调用参数甚至返回值完全匹配。它们 99.99% 相同。这让事情变得更容易。基本上你只需要复制两个 classes 中的代码并导入正确的东西(在 class 的顶部)。您很少需要更改已经为 GMS 编写的代码。

希望对大家有所帮助。

@ and @的回答都是正确的。我想补充一点:

首先,您需要根据应用场景和development/test成本,select选择合适的解决方案(G+H或G2H)

  1. 如果选择G+H方案,需要检查GMS是否可用。如果无法正常使用GMS接口,则需要HMS。详情参考@deadfish的回答。建议您使用此解决方案,可以
  • 降低应用程序打包的复杂性。一个包可以同时发布到 Google Play 和 AppGallery。
  • 降低代码维护成本。在原有逻辑代码上增加了HMS+GMS适配层代码。这样就可以根据手机自动调用合适的代码phone。即只需要在现有逻辑代码上调用检查GMS是否可用的方法即可,无需维护两套代码
  1. 如果选择G2H方案,兼容性测试的工作量较小。您只需要在华为 phones 上测试新的 APK。在华为应用市场和 Google Play 上发布您的应用,使用不同的软件包。您在应用市场发布的应用只包含华为的逻辑代码。可以参考@AndreiBogdan的回答,或者看docs Supporting Multiple Channels.

  2. 如@ say, you can use HMS Toolkit Convertor。它支持 G+H 和 G2H 转换。目前,HMS Toolkit 支持 Java 和 Kotlin。支持Android Studio版本:3.3.2~4​​.1.

综合之前给出的所有好答案:https://github.com/abusuioc/from-gms-to-hms#step-5-integrate-hms-sdks-in-your-app

对于大多数应用,推荐使用依赖于 GMS 和 HMS SDK 的单一构建 + 在运行时决定(基于设备的可用性)使用哪一个。

必须将 googlehuawei 设置为 productFlavors,然后设置为 sourceSets


根项目build.gradle:

buildscript {
    repositories {
        google()
        mavenCentral()
        maven { url "https://developer.huawei.com/repo/" }
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.2.1"
        classpath "com.google.gms:google-services:4.3.10"
        classpath "com.huawei.agconnect:agcp:1.6.5.300"
    }
}

模块 build.gradle:

plugins {
    id "com.android.application"
    id "androidx.navigation.safeargs"
}

def json_huawei_release = "src/huaweiRelease/agconnect-services.json"
def json_huawei_debug = "src/huaweiDebug/agconnect-services.json"
def json_google = "src/google/google-services.json"

if (getGradle().getStartParameter().getTaskRequests().toString().contains('Huawei')) {
    if (project.file(json_huawei_debug).exists() || project.file(json_huawei_release).exists()) {
        apply plugin: "com.huawei.agconnect"
    }
}

if (getGradle().getStartParameter().getTaskRequests().toString().contains('Google')) {
    if (project.file(json_google).exists()) {
        println "found: ${project.file(json_google)}"
        apply plugin: "com.google.gms.google-services"
        apply plugin: "com.google.firebase.crashlytics"
    } else {
        println "missing: ${project.file(json_google)}"
    }
}

android {

    ...
    flavorDimensions "vendor"
    productFlavors {
        google {
            dimension "vendor"
            versionNameSuffix "-google"
        }
        huawei {
            dimension "vendor"
            versionNameSuffix "-huawei"
        }
    }

    sourceSets {
        google {
            java.srcDir "src/google/java"
        }
        huawei {
            java.srcDir "src/huawei/java"
        }
    }
}

dependencies {

    /** Google Play Services */
    googleImplementation "com.google.android.gms:play-services-base:18.0.1"
    googleImplementation "com.google.android.gms:play-services-basement:18.0.0"
    googleImplementation "com.google.android.gms:play-services-auth:20.0.0"
    googleImplementation "com.google.android.gms:play-services-identity:18.0.0"
    googleImplementation "com.google.android.gms:play-services-oss-licenses:17.0.0"

    /** Google Firebase */
    googleImplementation "com.google.firebase:firebase-auth:21.0.1"
    googleImplementation "com.google.firebase:firebase-database:20.0.3"
    googleImplementation "com.google.firebase:firebase-messaging:23.0.0"
    googleImplementation "com.google.firebase:firebase-functions:20.0.1"
    googleImplementation "com.google.firebase:firebase-crashlytics:18.2.6"
    googleImplementation "com.google.firebase:firebase-analytics:20.0.2"
    googleImplementation "com.google.firebase:firebase-perf:20.0.4"
    // googleImplementation "com.firebaseui:firebase-ui-auth:8.0.0"

    /** Huawei Mobile Services */
    huaweiImplementation "com.huawei.hms:base:6.1.0.302"
    huaweiImplementation "com.huawei.hms:push:6.1.0.300"
    huaweiImplementation "com.huawei.hms:hianalytics:6.3.0.300"

    /** Huawei AppGallery Connect */
    huaweiImplementation "com.huawei.agconnect:agconnect-core:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-auth:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-remoteconfig:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-function:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-cloud-database:1.5.2.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-applinking:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-crash:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-apms:1.5.2.309"
    huaweiImplementation "com.huawei.agconnect:agconnect-storage:1.5.0.100"
    huaweiImplementation "com.huawei.agconnect:agconnect-appmessaging:1.6.5.300"
}

这允许为所有内容提供自定义实现;它将构建两个不同的工件。

测试时必须考虑切换构建变体和测试设备 - 但可以在 IDE 运行 配置中传递任务名称和设备序列号(以便 运行 正确测试设备上的正确构建变体)。