【转载请注明出处】
作者:DrkCore
原文:http://blog.youkuaiyun.com/drkcore/article/details/76290656
在应用开发中我们通常会在应用的打包脚本中标注多个渠道,用以统计应用在不同的应用市场中的效果。这当然是一个不错的做法,不过有的时候我们还想要知道用户是通过什么方式安装应用的,此时就需要使用 InstallerPackageName
了:
package android.content.pm;
public abstract class PackageManager {
/**
* Retrieve the package name of the application that installed a package. This identifies
* which market the package came from.
*
* @param packageName The name of the package to query
*/
public abstract String getInstallerPackageName(String packageName);
}
我们可以通过 PackageManager.getInstallerPackageName(String pack)
来获取某个应用安装时使用的安装器的名称。看起来非常的方便但是该方法并不能代替渠道号的作用。
常见的返回值
大部分系统应用和通过 ADB 安装的应用(包括使用 AS 运行安装)会返回 null
,而直接打开 APK 安装的则会返回 com.google.android.packageinstaller
。以下是一些例子:
Package | Installer | Desc |
---|---|---|
com.android.mms | null | 系统短信服务 |
com.android.mms.service | null | 系统短信服务 |
core.demo | null | 通过 Adb 直接安装的应用 |
net.openvpn.openvpn | com.google.android.packageinstaller | 设备默认安装器 |
com.baidu.tieba_mini | com.google.android.packageinstaller | 设备默认安装器 |
com.netease.cloudmusic | com.google.android.packageinstaller | 设备默认安装器 |
产商应用市场
笔者测试的设备是 SAMSUNG S7E 9350,部分系统级应用使用的安装器是三星应用市场,如下:
Package | Installer | Desc |
---|---|---|
com.sec.android.app.samsungapps | com.sec.android.app.samsungapps | 三星应用市场 |
com.samsung.android.themestore | com.samsung.android.themestore | 三星主题商店 |
com.enhance.gameservice | com.enhance.gameservice | 三星的游戏服务 |
由此可以合理推测小米、华为等产商也应该会用同样的做法。不过笔者手头并没有这些设备,如果知道的同学欢迎留言告知。
Google Play 和第三方应用市场
Google Play 的安装器名称大部分是 com.android.vending
。如果你的统计量比较大的话你可能还会遇到一个名称是 com.google.android.feedback
,它代表的是旧版的谷歌市场,不过其占有量现在应该很低了。
Package | Installer | Desc |
---|---|---|
com.google.android.gm | com.android.vending | GooglePlay |
com.android.chrome | com.android.vending | GooglePlay |
笔者并没有旧版的谷歌市场 | com.google.android.feedback | 旧版的谷歌市场 |
Google Play 毕竟是官家的市场,installer 的返回值是有保证的。但是第三方的市场有可能并不遵守这样的规则。
笔者测试中使用了百度应用市场发现其安装器是 com.google.android.packageinstaller
也就是系统默认的安装器。普通应用想要发起安装 APK 的请求都是通过以下的 Intent 来安装的,此时打开的界面就是默认安装器:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + apk), "application/vnd.android.package-archive");
context.startActivity(intent);
猜测原因是因为第三方应用市场在未 Root 的设备中本身也只是普通的应用所以并无权限,而产商应用市场和 Google Play 持有系统签名可以设置自己的安装器名称。
但由于条件原因笔者并未测试过 Root 后开启静默安装的百度市场的行为,如有同学知道的还请告知。
更新版本对 Installer 的影响
先说结论:更新应用会根据安装方法刷新 installerName
笔者设备上的百度地图原本是通过三星应用市场安装的:
在使用了百度应用市场更新后其 installer 就变成了默认安装器:
笔者也试过用 Google Play 来更新应用,结果和上述结论一致:
InstallerName 某种程度上算是实时变化的,这一点和部分数据统计 SDK 比如友盟就不一样。按照友盟的策略应用在一台设备上的渠道号只认准首次安装时的值,就算后期用户手动换了渠道后也是一样。在实际开发中这一点需要注意。
如何修改渠道号
Adb 命令修改
pm install -i installerName file.apk 可以在安装时修改 installerName
该方法可以使用任意的值,比如笔者在测试中就是用了中文作为 installerName:
hero2qltechn:/sdcard $ pm install -i 这个是使用adb写入的包名 app-debug.apk
:
在测试的时候可以使用该方法来验证结果。
代码修改
PackageManager.setInstallerPackage()
可以修改 installerName 但是限制比较多
/**
* Change the installer associated with a given package. There are limitations
* on how the installer package can be changed; in particular:
* <ul>
* <li> A SecurityException will be thrown if <var>installerPackageName</var>
* is not signed with the same certificate as the calling application.
* <li> A SecurityException will be thrown if <var>targetPackage</var> already
* has an installer package, and that installer package is not signed with
* the same certificate as the calling application.
* </ul>
*
* @param targetPackage The installed package whose installer will be changed.
* @param installerPackageName The package name of the new installer. May be
* null to clear the association.
*/
public abstract void setInstallerPackageName(String targetPackage, String installerPackageName);
该方法并不要求任何的特殊权限,但是要求调用者要和要修改成的安装器对应的应用要有相同的签名。
实测中 installerPackageName 对应的应用必须已安装,由于签名的原因笔者只能将之设置成自身的名字:
其他
关于海外市场的开发者可能知道某些广告 SDK 提供商最近修改了相关政策不再向非 Google Play 下载的应用提供广告。出于好奇笔者找了下其 SDK 发现了如下代码:
其实现就是获取包名然后再 PackageManager.getInstallerPackageName
的,嗯,看来大家的想法都是差不多的。
参阅资料:
StackOverFlow: Can PackageManager.getInstallerPackageName() tell me that my app was installed from Amazon app store?
https://stackoverflow.com/questions/13289748/can-packagemanager-getinstallerpackagename-tell-me-that-my-app-was-installed-fStackOverFlow: getInstallerPackageName returns null
https://stackoverflow.com/questions/12593621/getinstallerpackagename-returns-nullCoderWall: Check if an APK is from Google Play
https://coderwall.com/p/fipesa/check-if-an-apk-is-from-google-play