谷歌Play控制台错误 - 无法升级已安装的应用程序

12

我基于Google的教程和示例开发了一个即时应用程序apk。 当我尝试开始推出生产时,我看到一个错误:Non-upgradable to installed app。

问题:

某些即时应用程序APK的用户将无法获得您已安装应用程序中的任何APK。

解决方法:

确保Instant App APK的定位与APK的定位相匹配。

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

base - baseFeature,minSdk 18,targetSdk 26

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

项目详情特性 - 最低SDK版本为18,目标SDK版本为26

api project(':base')

在即时模块中未包含UI功能,最小SDK版本为18,目标SDK版本为26,该模块包含所有视图。

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

即时应用模块最低支持版本为18或23,目标版本为26。

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

apk - apk模块,最低支持SDK版本18,目标SDK版本26

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

第二种情况是baseFeature中有item-details代码。

即时应用可以从Android Studio和Google Play开发和预发布中运行。当我尝试升级已安装的应用程序时,一切正常。在我看来,定位是正确的,但Google Play控制台持不同意见。

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

更新10.09.2017 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 

@keyboardsurfer 在每个模块中,targetSdk 都是 26(我正在使用全局参数)。 - Skye
在您的“apk” Gradle中缺少实现项目(“:base”),实现项目(“:item-details”)依赖项。 - Julia K
你的“apk”模块依赖包含实现项目(“:shared”),你是否还有一个名为“shared”的功能模块? - Julia K
@JuliaK 模块“shared”只是一个库(包含Kotlin扩展、模型、工具、配置文件等),它被包含在每个功能中。 - Skye
@JuliaK 那么,即使“ui”模块包含“:base”,我是否应该将“:base”模块放入“apk”模块中? - Skye
显示剩余9条评论
5个回答

8
根据Android 文档,您可以使用包含在Android SDK中的工具来确定Google Play如何基于应用程序声明的功能和权限过滤您的应用程序。要这样做,请使用dump badging命令运行aapt。这会导致aapt解析您的应用程序清单并应用与Google Play使用的相同规则来确定您的应用程序所需的功能。

通过在可安装和即时应用程序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功能的要求。同样,ACCESS_WIFI_STATE权限意味着需要android.hardware.wifi功能。没有GPS或WiFi的设备上(这听起来很奇怪,但是在市场上确实存在这种设备),用户将无法将您的即时应用程序升级为可安装应用程序。
另一个限制您可安装应用程序可用性的因素是android.hardware.screen.portrait功能,这是由于以下原因而被暗示:

一个或多个活动已指定为纵向方向

为了解决所有这些问题并使您的可安装应用程序适用于即时应用程序的所有用户,请将以下块添加到可安装应用程序的清单中(在<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功能是为了遵循以下要求而存在的:

如果您的应用程序面向Android 5.0(API级别21)或更高版本,并使用ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION权限以接收来自网络或GPS的位置更新,则必须明确声明您的应用程序使用android.hardware.location.networkandroid.hardware.location.gps硬件功能。

此外,确定可安装应用程序需要哪些功能的另一种方法是在Google Play控制台的应用发行版块中的APK详细信息信息屏幕。

  1. 这有关系吗?两者的版本代码和版本名称都相同。
  2. 我每次都这样做。
  3. minSdkVersion 和 targetSdkVersion 是相同的。 我将尝试上传可安装的 apk,其版本代码高于即时 apk。
- Skye
1
@Skye,我在谈论“默认入口点URL”,它在meta标签中用android:name="default-url"指定为“即时”应用程序。请查看此处的详细信息:https://developer.android.com/topic/instant-apps/prepare.html#default-url。同样的URL也必须是“已安装”应用程序的启动器活动意图过滤器的一部分。 - Idolon
@Skye,你能否在APK信息中扩展功能部分并检查那两个功能是什么?我猜其中一个是android.hardware.touchscreen,第二个是android.hardware.screen.portrait。如果是,请在您可安装的应用程序清单中添加<uses-feature android:name="android.hardware.screen.portrait" android:required="false" />,然后再次检查上传。 - Idolon
Idolon,非常感谢!!! :) 我从未想过它可能因为这样的原因而无法工作。我已经成功发布了一个即时应用程序,但是我在谷歌搜索中找不到它 :/ 这正常吗?(移动保留为0.00,因此所有流量都是即时应用程序)。我将您的答案标记为正确 :) - Skye
1
很高兴你解除了阻止!有一个小问题:与其禁用可安装应用程序的隐含功能,最好要求即时应用程序中的功能。也就是说,由于您的应用程序使用例如ACCESS_FINE_LOCATION,因此确实需要GPS。这已经隐含在可安装的应用程序中,但即时应用程序应明确要求该功能。关于即时应用程序未显示在Google搜索结果中:您的意思是有搜索结果,但它没有标记为“即时”吗?这可能需要一些时间来索引。如果几天后仍无法正常工作,请告诉我们。 - philo
显示剩余18条评论

2

我遵循了Idolon的建议(在两个apk上运行aapt dump badging MyApp.apk并进行比较),并添加了几个我缺少的权限。

然而,在我添加一个OpenGLES功能来匹配我的已安装应用之前,我仍然无法完全使其工作。在我的情况下:

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


0

这条消息非常有意义。只需查看您安装的应用程序所需的功能(您可以使用classyshark进行查看)。如果它需要电话,因为您请求拨号器权限或手动要求它,或者您要求GSL版本2作为已安装版本的最低要求,则不符合这些标准但具有Android 6+的部分用户将无法使用您已安装的版本,而只能使用即时版本。


0
简单回答: 如果您未正确递增版本号,则会发生此情况。
只有具有“较低”版本号的人才能升级-具有较高版本号的人无法升级。

0

查看您安装的应用程序清单以及您在上面的评论中提供的要点,您的minSdk存在问题。您的安装应用程序具有minSdk 18,而您的即时应用程序没有设置minSdk。在即时应用程序基本清单上设置minSdk 18,看看是否解决了问题。

直觉上,此验证器正在防止一种不可能发生的情况:预JB用户获取您的即时应用程序,并且无法升级到已安装的应用程序。因为即时应用程序运行时本身并不会回溯那么久。验证器确实很愚蠢。它只是查看这些minSdk值。但这可能是我们想要的方式,因为运行时兼容性正在向旧设备稳步移动,而这是在您重新发布之前发生的。(我们几乎不可能回到sdk 17。仅预先证明为什么我们不希望使此验证器“更聪明”。)


我在每个gradle文件中声明了minSdk 18,因此manifest中的声明被忽略了。我还在即时应用程序gradle文件中进行了声明(使用插件声明:com.android.instantapp)。 - Skye
你能分享生成的构件吗,即zip文件吗? - philo
2个即时应用程序清单和Gradle文件:https://gist.github.com/banaszeknorbert/9a66de51e204fbb6cece6fc74e80936f Zip文件链接:https://skyesoftware-my.sharepoint.com/personal/apps_skyesoftware_pl/_layouts/15/guestaccess.aspx?docid=1cf0a15a4d4c246aeb46c1ebb807b0cb6&authkey=ASSaV2SXqf0XLJZQCnl1wkk - Skye

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接