Android如何识别预装的第三方App

栏目: IOS · Android · 发布时间: 5年前

内容简介:新买一台手机,里面会有很多App,有的属于系统App,不可卸载,有的属于第三方App,厂商会预装一些常用的或者给了他们广告费的App,这些是可以卸载的。如果要详细划分,系统App还可根据其路径不同进一步划分(如但是,利用这个方法那些预装的App也会归到用户App中,那么有没有办法知道用户App中哪些是预装的哪些是用户手动安装的呢?

新买一台手机,里面会有很多App,有的属于系统App,不可卸载,有的属于第三方App,厂商会预装一些常用的或者给了他们广告费的App,这些是可以卸载的。

如果要详细划分,系统App还可根据其路径不同进一步划分(如 /system/app/system/priv-app/vendor/app 等)。但对于开发者来说,手机上安装的App只分为2类:系统App和用户App,可以根据系统API区分,这里就不详细说了,简单而言存在 ApplicationInfo.FLAG_SYSTEMApplicationInfo.FLAG_UPDATED_SYSTEM_APP flag的即为系统App,否则为用户App。

但是,利用这个方法那些预装的App也会归到用户App中,那么有没有办法知道用户App中哪些是预装的哪些是用户手动安装的呢?

在这里分享一种方法: App的安装时间是整秒的为预装的第三方App

如果不关心为什么能用这个奇怪方法来区分预装App的话,就可以关闭这篇文章了。

之前我也一直不清楚为什么可以用这种方法,当时我猜是因为手机第一次启动的时候时间是不准确的,会是某某年1月1日,然后因为启动时会扫描各个App目录然后安装App,因此被打上这样的安装时间。但这种解释是说不通的,即便真是这样,那安装时间也不应该是整秒的。因此我决定好好找一下原因,以下是我求证的历程。

背景知识

首先介绍一些背景知识。

  • App的安装时间可以通过获取 PackageInfo 得到,其 firstInstallTime 属性即安装时间。

  • /data/system/packages.xml 保存了手机上安装的App的信息,其中App的安装时间就保存在这里。我在下面截取了2个示例。

<package name="com.tencent.mm" 
codePath="/data/app/com.tencent.mm-TSn6yG4fF7A_EaxE5OtrHQ==" 
nativeLibraryPath="/data/app/com.tencent.mm-TSn6yG4fF7A_EaxE5OtrHQ==/lib" 
primaryCpuAbi="armeabi" publicFlags="945307204" 
privateFlags="0" ft="167702c7508" it="1676feab448" 
ut="167702c8a57" version="1360" userId="10118">
...
</package>

<package name="com.android.providers.downloads" 
codePath="/system/priv-app/DownloadProvider" 
nativeLibraryPath="/system/priv-app/DownloadProvider/lib" publicFlags="944258629" 
privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" 
ut="11e8dc5d800" version="28" sharedUserId="10006" isOrphaned="true">
...
</package>

其中 it 的值便是安装时间,这里是用十六进制保存的,上面这2个App的安装时间换算成十进制分别是 15437709118161230739200000 ,对应的北京时间即 2018-12-03 01:15:112009-01-01 00:00:00 。 【嗯…想不到我12月3号1点多还没睡,还安装了个微信……】

  • 系统启动时, PackageManagerServiceSystemServer 启动, PackageManagerService 会扫描 /data/app/system/app/system/priv-app/vendor/app 等等目录,可以理解为会把这些目录中的Apk安装一遍, PackageManagerService 会结合上面提到的 packages.xml 把各个App解析成 PackageParser.Package 对象。

思路

根据上面的知识,我们可以知道,如果 packages.xml 已经有了某个App的信息,那么这个App的安装时间肯定就是 packages.xml 中记录的时间。第一次启动手机时 packages.xml 文件还不存在,或者新安装一个App时, packages.xml 中还没有这个App的记录,也就是说,确认这个 packages.xml 中的 firstInstallTime (即 it )是如果生成的便是问题的关键。

以下基于 7.0.0_r1 版本代码。

通过搜索 PackageManagerService ,在 scanPackageDirtyLI 方法中有这么一段代码:

// Take care of first install / last update times.
if (currentTime != 0) {
   if (pkgSetting.firstInstallTime == 0) {
       pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;
   } else if ((scanFlags&SCAN_UPDATE_TIME) != 0) {
       pkgSetting.lastUpdateTime = currentTime;
   }
} else if (pkgSetting.firstInstallTime == 0) {
   // We need *something*.  Take time time stamp of the file.
   pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
} else if ((policyFlags&PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
   if (scanFileTime != pkgSetting.timeStamp) {
       // A package on the system image has changed; consider this
       // to be an update.
       pkgSetting.lastUpdateTime = scanFileTime;
   }
}

其中, currentTimescanPackageDirtyLI 方法的一个参数。 pkgSetting 是从 packages.xml 中读取到的该App的信息( PackageSetting 对象),如果 packages.xml 中不存在这个App的信息,会根据从Apk中解析到的信息创建一个 PackageSettingscanFileTime 是Apk文件的最后修改时间。

可以看到存在这么几种情况:

  • 传入的 currentTime 不为0,从 packages.xml 中读取到的 firstInstallTime 为0。这种情况会将 firstInstallTimelastUpdateTime 均设置为传入的 currentTime 的值。
  • 传入的 currentTime 不为0,传入的 scanFlags 设置了 SCAN_UPDATE_TIME 。这种情况会将 lastUpdateTime 设置为传入的 currentTime 的值。
  • 传入的 currentTime 为0,从 packages.xml 中读取到的 firstInstallTime 为0。这种情况会将 firstInstallTimelastUpdateTime 均设置为Apk的最后修改时间。
  • 传入的 currentTime 为0,从 packages.xml 中读取到的 firstInstallTime 不为0,传入的 policyFlags 设置了 PackageParser.PARSE_IS_SYSTEM_DIRscanFileTimepackages.xml 中读取到的 timeStamppackages.xmlpackage 标签的 ft )不相同。这种情况会将 lastUpdateTime 设置为Apk的最后修改时间。

对应到我们真实使用手机的场景,上面4种情况分别对应以下几种场景:

  • 第一种情况:对应新安装App。 currentTime 为当前的时间戳,会将这个新安装的App的 firstInstallTimelastUpdateTime 设置为当前时间戳。
  • 第二种情况:对应更新App。 currentTime 为当前的时间戳,会将 lastUpdateTime 设置为当前时间戳, firstInstallTime 保持不变。
  • 第三种情况:手机启动时 PackageManagerService 扫描各个目录时发现了 packages.xml 中不存在的App(第一次启动时所有App都不在 packages.xml 中)。
  • 第四种情况:系统更新等操作更新了系统分区的App,导致其文件的最后修改时间和记录的不一致了,会被认为是更新。

我们可以大胆猜测,第一次启动手机时会走第三种情况,因此系统App和预装App的安装时间是文件的最后修改时间,而这些文件的最后修改时间都是整秒的。

如何验证?

我们先看看上面那个 com.android.providers.downloads 的Apk文件的最后修改时间。

# stat DownloadProvider.apk
  File: `DownloadProvider.apk'
  Size: 504712	 Blocks: 992	 IO Blocks: 512	regular file
Device: 10305h/66309d	 Inode: 1308	 Links: 1
Access: (644/-rw-r--r--)	Uid: (    0/    root)	Gid: (    0/    root)
Access: 2009-01-01 00:00:00.000000000
Modify: 2009-01-01 00:00:00.000000000
Change: 2009-01-01 00:00:00.000000000

时间与 packages.xml 中保存的时间一致,确实是把文件的最后修改时间作为了安装时间。那么还有一个问题需要确认,传入的 currentTime 是0吗?

我们追溯调用链,会在 PackageManagerService 的构造函数中看到扫描各个目录的方法。调用 scanDirTracedLI 方法传入的最后一个参数0即 scanPackageDirtyLI 方法中的 currentTime 。感兴趣的还可以仔细看看 PackageManagerService 到底扫描了哪些目录。

File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
scanDirTracedLI(vendorOverlayDir, mDefParseFlags
       | PackageParser.PARSE_IS_SYSTEM
       | PackageParser.PARSE_IS_SYSTEM_DIR
       | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

// Find base frameworks (resource packages without code).
scanDirTracedLI(frameworkDir, mDefParseFlags
       | PackageParser.PARSE_IS_SYSTEM
       | PackageParser.PARSE_IS_SYSTEM_DIR
       | PackageParser.PARSE_IS_PRIVILEGED,
       scanFlags | SCAN_NO_DEX, 0);

// Collected privileged system packages.
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
scanDirTracedLI(privilegedAppDir, mDefParseFlags
       | PackageParser.PARSE_IS_SYSTEM
       | PackageParser.PARSE_IS_SYSTEM_DIR
       | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

// Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirTracedLI(systemAppDir, mDefParseFlags
       | PackageParser.PARSE_IS_SYSTEM
       | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

// Collect all vendor packages.
File vendorAppDir = new File("/vendor/app");
try {
   vendorAppDir = vendorAppDir.getCanonicalFile();
} catch (IOException e) {
   // failed to look up canonical path, continue with original one
}
scanDirTracedLI(vendorAppDir, mDefParseFlags
       | PackageParser.PARSE_IS_SYSTEM
       | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

// Collect all OEM packages.
final File oemAppDir = new File(Environment.getOemDirectory(), "app");
scanDirTracedLI(oemAppDir, mDefParseFlags
       | PackageParser.PARSE_IS_SYSTEM
       | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

...

scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

scanDirTracedLI(mDrmAppPrivateInstallDir, mDefParseFlags
       | PackageParser.PARSE_FORWARD_LOCK,
       scanFlags | SCAN_REQUIRE_KNOWN, 0);

如果感兴趣,你可以去跟一下安装App和更新App的代码,看传入的 currentTime 是不是当前的时间戳。

到此,我们已经证明了第一次启动手机时,系统会把文件的最后修改时间当成系统App和预装App的安装时间,而这个时间一般是类似于上面那样 2009-01-01 00:00:00.000000000 的整秒的时间(至于为什么是这样,那就是另一个问题了),而我们自己安装App时几乎不可能在一个整秒的时间安装,所有 我们可以用安装时间是否为整秒来区分手机预装的App和用户手动安装的App

至于区分预装App和用户手动安装的App有什么用?请发挥你的想象,比如说,一个用户的手机上只有你家一个手动安装的App或者少数几个App,那么他是想干什么好事呢?


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Linux内核完全注释

Linux内核完全注释

赵炯 / 机械工业出版社 / 2005-8 / 42.00元

Linux内核完全注释,ISBN:9787111149682,作者:赵炯编著一起来看看 《Linux内核完全注释》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具