Google Play 控制台错误 - 无法升级到已安装的应用

Google Play Console error - Non-upgradable to installed app

我根据 google 教程和示例开发了一个即时应用程序 apk。当我尝试开始向生产环境推出时,我看到一个错误: 不可升级到已安装的应用程序

问题

此免安装应用 APK 的部分用户将没有资格使用您安装的应用中的任何 APK。

决议

确保您的免安装应用 APK 的目标与您的 APK 的目标相匹配。

项目结构: 有两种情况,第一种:

base - baseFeature,minSdk 18,targetSdk 26

application project(':apk')
feature project(':item-details')

项目详情功能 - minSdk 18,targetSdk 26

api project(':base')

ui - 即时模块中未包含的功能,minSdk 18,targetSdk 26,模块包含所有视图

implementation project(':base')
other modules like customcomponents, shared etc

instant - 即时应用程序模块 minSdk 18 或 23,targetSdk 26

implementation project(':base')
implementation project(':item-details')

apk - apk 模块,minSdk 18,targetSdk 26

implementation project(':ui')
implementation project(':shared')

第二种情况在 baseFeature 中有项目详细信息代码。

免安装应用 运行 来自 Android Studio 和 Google Play 开发和预发布。此外,当我尝试升级到已安装的应用程序时,一切正常。 在我看来,定位是正确的,但 Google Play 管理中心的想法不同。

您知道关于如何推出免安装应用程序的任何想法吗?请帮忙 :( 我已经为此版本工作了 3 天,但我无法推出应用程序。

2017 年 9 月 10 日更新 APK 详细信息:

Supported Android devices 8448 devices 
API levels 18+ 
Target SDK 26 
Screen layouts 4 screen layouts 
Localizations default + 113 languages 
Features 2 features 
Required permissions 12 permissions 
OpenGL ES versions 1.0+ 
OpenGL textures all textures 
Uploaded Sep 9, 2017, 7:57:11 AM PDT 

根据 Android documentation:

You can use the aapt tool, included in the Android SDK, to determine how Google Play will filter your application, based on its declared features and permissions. To do so, run aapt with the dump badging command. This causes aapt to parse your application's manifest and apply the same rules as used by Google Play to determine the features that your application requires.

通过 运行 在您的可安装和即时应用程序 apk 上执行该命令,打印以下信息。

可安装的应用程序(版本 551):

package: name='skyesoftware.blogspace' versionCode='551' versionName='0.3.1.551' platformBuildVersionName=''
sdkVersion:'18'
targetSdkVersion:'26'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.READ_EXTERNAL_STORAGE'
uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE'
uses-permission: name='android.permission.ACCESS_WIFI_STATE'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED'
uses-permission: name='android.permission.ACCESS_COARSE_LOCATION'
uses-permission: name='android.permission.ACCESS_FINE_LOCATION'
uses-permission: name='android.permission.WAKE_LOCK'
uses-permission: name='com.google.android.providers.gsf.permission.READ_GSERVICES'
uses-permission: name='com.google.android.c2dm.permission.RECEIVE'
uses-permission: name='skyesoftware.blogspace.permission.C2D_MESSAGE'
…
feature-group: label=''
  uses-feature: name='android.hardware.faketouch'
  uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
  uses-feature: name='android.hardware.location'
  uses-implied-feature: name='android.hardware.location' reason='requested android.permission.ACCESS_COARSE_LOCATION permission, and requested android.permission.ACCESS_FINE_LOCATION permission'
  uses-feature: name='android.hardware.screen.portrait'
  uses-implied-feature: name='android.hardware.screen.portrait' reason='one or more activities have specified a portrait orientation'
  uses-feature: name='android.hardware.wifi'
  uses-implied-feature: name='android.hardware.wifi' reason='requested android.permission.ACCESS_WIFI_STATE permission'
…
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--' 'af' 'am' 'ar' 'az' 'az-AZ' 'be' 'be-BY' 'bg' 'bn' 'bn-BD' 'bs' 'bs-BA' 'ca' 'cs' 'da' 'de' 'el' 'en-AU' 'en-GB' 'en-IN' 'es' 'es-ES' 'es-US' 'et' 'et-EE' 'eu' 'eu-ES' 'fa' 'fi' 'fr' 'fr-CA' 'gl' 'gl-ES' 'gu' 'gu-IN' 'hi' 'hr' 'hu' 'hy' 'hy-AM' 'id' 'in' 'is' 'is-IS' 'it' 'iw' 'ja' 'ka' 'ka-GE' 'kk' 'kk-KZ' 'km' 'km-KH' 'kn' 'kn-IN' 'ko' 'ky' 'ky-KG' 'lo' 'lo-LA' 'lt' 'lv' 'mk' 'mk-MK' 'ml' 'ml-IN' 'mn' 'mn-MN' 'mr' 'mr-IN' 'ms' 'ms-MY' 'my' 'my-MM' 'nb' 'ne' 'ne-NP' 'nl' 'pa' 'pa-IN' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'si' 'si-LK' 'sk' 'sl' 'sq' 'sq-AL' 'sr' 'sr-Latn' 'sv' 'sw' 'ta' 'ta-IN' 'te' 'te-IN' 'th' 'tl' 'tr' 'uk' 'ur' 'ur-PK' 'uz' 'uz-UZ' 'vi' 'zh-CN' 'zh-HK' 'zh-TW' 'zu'
densities: '120' '160' '240' '320' '480' '640' '65534'

免安装应用程序基本功能:

package: name='skyesoftware.blogspace' versionCode='1' versionName='1.0.0' platformBuildVersionName=''
sdkVersion:'18'
targetSdkVersion:'26'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.WAKE_LOCK'
uses-permission: name='com.google.android.c2dm.permission.RECEIVE'
uses-permission: name='skyesoftware.blogspace.permission.C2D_MESSAGE'
application: label='' icon=''
feature-group: label=''
  uses-feature: name='android.hardware.faketouch'
  uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
other-activities
other-receivers
other-services
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--' 'af' 'am' 'ar' 'az' 'az-AZ' 'be' 'be-BY' 'bg' 'bn' 'bn-BD' 'bs' 'bs-BA' 'ca' 'cs' 'da' 'de' 'el' 'en-AU' 'en-GB' 'en-IN' 'es' 'es-US' 'et' 'et-EE' 'eu' 'eu-ES' 'fa' 'fi' 'fr' 'fr-CA' 'gl' 'gl-ES' 'gu' 'gu-IN' 'hi' 'hr' 'hu' 'hy' 'hy-AM' 'in' 'is' 'is-IS' 'it' 'iw' 'ja' 'ka' 'ka-GE' 'kk' 'kk-KZ' 'km' 'km-KH' 'kn' 'kn-IN' 'ko' 'ky' 'ky-KG' 'lo' 'lo-LA' 'lt' 'lv' 'mk' 'mk-MK' 'ml' 'ml-IN' 'mn' 'mn-MN' 'mr' 'mr-IN' 'ms' 'ms-MY' 'my' 'my-MM' 'nb' 'ne' 'ne-NP' 'nl' 'pa' 'pa-IN' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'si' 'si-LK' 'sk' 'sl' 'sq' 'sq-AL' 'sr' 'sr-Latn' 'sv' 'sw' 'ta' 'ta-IN' 'te' 'te-IN' 'th' 'tl' 'tr' 'uk' 'ur' 'ur-PK' 'uz' 'uz-UZ' 'vi' 'zh-CN' 'zh-HK' 'zh-TW' 'zu'
densities: '120' '160' '240' '320' '480' '640' '65534'

免安装应用功能 APK:

package: name='skyesoftware.blogspace' versionCode='1' versionName='1.0.0' split='blogspace_item_details' platformBuildVersionName=''
sdkVersion:'18'
targetSdkVersion:'26'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
application: label='' icon=''
feature-group: label=''
  uses-feature: name='android.hardware.faketouch'
  uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
other-activities
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--'
densities: '160'

如您所见,您的可安装应用请求 ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION 权限,即 android.hardware.location 功能的 implicitly add a requirement。同样,ACCESS_WIFI_STATE 权限暗示了 android.hardware.wifi 特性。设备上既没有 GPS 也没有 WiFi 的用户(这听起来很奇怪,但此类设备普遍存在)将无法将您的即时应用升级到可安装应用。

另一个限制可安装应用程序可用性的因素是 android.hardware.screen.portrait 功能,这是隐含的,因为:

one or more activities have specified a portrait orientation

要解决所有这些问题并使您的可安装应用可供即时应用的所有用户使用,请将以下块添加到可安装应用的清单中(在 <manifest> 标签下方的级别):

<uses-feature
    android:name="android.hardware.location"
    android:required="false" />

<uses-feature
    android:name="android.hardware.location.network"
    android:required="false" />

<uses-feature
    android:name="android.hardware.location.gps"
    android:required="false" />

<uses-feature
    android:name="android.hardware.wifi"
    android:required="false" />

<uses-feature
    android:name="android.hardware.screen.portrait"
    android:required="false" />

android.hardware.location.networkandroid.hardware.location.gps 特性符合以下 requirement:

If your app targets Android 5.0 (API level 21) or higher and uses the ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission in order to receive location updates from the network or a GPS, respectively, you must also explicitly declare that your app uses the android.hardware.location.network or android.hardware.location.gps hardware features.


顺便说一句,另一种确定可安装应用程序需要哪些功能的方法是 Google Play 控制台的应用程序发布部分的 APK details 信息屏幕。

这条消息非常有道理。只需查看您安装的应用程序所需的功能(您可以为此使用 classyshark)。如果它需要 phone,因为你要求拨号权限或你只是手动要求它,或者你要求 GSL 版本 2 作为你安装版本的最低要求,那么部分不满足的用户是有道理的这些标准,但有 android 6+ 将无法使用您安装的版本,而只能使用即时版本。

通过上面评论中的要点查看已安装应用程序的清单和即时应用程序,您的 minSdk 确实存在问题。您安装的应用程序具有 minSdk 18,而您的免安装应用程序未设置 minSdk。在免安装应用程序基础清单上设置 minSdk 18,看看是否能解决问题。

直觉上,此验证器正在防止一种不可能发生的情况:JB 之前的用户获取您的免安装应用程序,但无法升级到已安装的应用程序。因为即时应用程序运行时本身不会追溯到那么远。验证器确实是愚蠢的。它只是查看那些 minSdk 值。但这可能就是我们希望的样子,因为运行时兼容性正在稳步回到旧设备,而且无需重新发布即可实现。 (我们不太可能回到 sdk 17。只是先发制人地证明为什么我们不想制作这个验证器 "smarter"。)

简单回答: 如果您没有正确增加版本号,就会发生这种情况。

只有"lower"版本号可以升级,更高版本号不能升级。

我听从了 Idolon 的建议(运行 aapt dump badging MyApp.apk 两个 apk 和比较)并添加了几个我缺少的权限。

但是,在我添加 OpenGLES 功能以匹配我安装的应用程序之前,我仍然无法让它完全工作。就我而言:

<uses-feature android:glEsVersion="0x00020000" android:required="true" />