Android识别预装的第三方App方法实例

前言

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

如果要详细划分,系统App还可根据其路径不同进一步划分(如/system/app、/system/priv-app、/vendor/app等)。但对于开发者来说,手机上安装的App只分为2类:系统App和用户App,可以根据系统API区分,这里就不详细说了,简单而言存在ApplicationInfo.FLAG_SYSTEM或ApplicationInfo.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的安装时间换算成十进制分别是1543770911816和1230739200000,对应的北京时间即2018-12-03 01:15:11和2009-01-01 00:00:00。 【嗯…想不到我12月3号1点多还没睡,还安装了个微信……】

系统启动时,PackageManagerService由SystemServer启动,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;
 }
}

其中,currentTime是scanPackageDirtyLI方法的一个参数。pkgSetting是从packages.xml中读取到的该App的信息(PackageSetting对象),如果packages.xml中不存在这个App的信息,会根据从Apk中解析到的信息创建一个PackageSetting。scanFileTime是Apk文件的最后修改时间。
可以看到存在这么几种情况:

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

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

  • 第一种情况:对应新安装App。currentTime为当前的时间戳,会将这个新安装的App的firstInstallTime和lastUpdateTime设置为当前时间戳。
  • 第二种情况:对应更新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,那么他是想干什么好事呢?

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android实现使用微信登录第三方APP的方法

    本文实例讲述了Android实现使用微信登录第三方APP的方法.分享给大家供大家参考,具体如下: 使用微信登录APP,免去注册过程,现在已经有很多的类似应用了.集成该功能过程不复杂,但还是有一些地方需要注意的. 开始之前,需要做下面的准备工作. 1.到微信开放平台注册你的APP,并申请开通微信登录的权限.参考这里: https://open.weixin.qq.com// 2.下载Android SDK和签名查看工具,请参考: https://open.weixin.qq.com/cgi-bin

  • Android app第三方支付宝支付接入教程

    支付宝的接入相对比较简单,看看支付宝官网的文档基本都能搞定,但是切记一点让你们的后台也要搞清楚支付宝的流程,重中之重. 1.注意事项 开发前一定要阅读支付宝官方文档 强烈建议签名等处理在后台处理,我这个是测试是在自己本地写的,不要吐槽 想获取支付宝合作商户ID,及支付宝公钥请点击支付宝链接,生成密钥及PKCS8转码工具在文档中 添加Android.permission.INTERNET权限和android.permission.ACCESS_NETWORK_STATE权限 要导入支付宝的包 2.

  • Android识别预装的第三方App方法实例

    前言 新买一台手机,里面会有很多App,有的属于系统App,不可卸载,有的属于第三方App,厂商会预装一些常用的或者给了他们广告费的App,这些是可以卸载的. 如果要详细划分,系统App还可根据其路径不同进一步划分(如/system/app./system/priv-app./vendor/app等).但对于开发者来说,手机上安装的App只分为2类:系统App和用户App,可以根据系统API区分,这里就不详细说了,简单而言存在ApplicationInfo.FLAG_SYSTEM或Applica

  • Android 中隐藏虚拟按键的方法实例代码

    下面通过一段代码给大家讲解android 隐藏虚拟按键的方法,废话不多说了,大家多多看看代码和注释吧,具体代码如下所示: /** * 隐藏虚拟按键,并且全屏 */ protected void hideBottomUIMenu() { //隐藏虚拟按键,并且全屏 if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api View v = this.getWindow().getDec

  • Android实现定时器的五种方法实例详解

    一.Timer Timer是Android直接启动定时器的类,TimerTask是一个子线程,方便处理一些比较复杂耗时的功能逻辑,经常与handler结合使用. 跟handler自身实现的定时器相比,Timer可以做一些复杂的处理,例如,需要对有大量对象的list进行排序,在TimerTask中执行不会阻塞子线程,常常与handler结合使用,在处理完复杂耗时的操作后,通过handler来更新UI界面. timer.schedule(task, delay,period); task: Time

  • Android设置项目为系统APP方法

    目录 何为系统APP何为三方APP? AS如何操作? 何为系统APP何为三方APP? 位于system分区内的是系统软件,位于data分区得的是第三方后安装的软件 系统软件是指控制和协调计算机及外部设备,支持应用软件开发和运行的系统,是无需用户干预的各种程序的集合,主要功能是调度,监控和维护计算机系统:负责管理计算机系统中各种独立的硬件,使得它们可以协调工作.系统软件使得计算机使用者和其他软件将计算机当作一个整体而不需要顾及到底层每个硬件是如何工作的. 所谓第三方软件指的是该非线性编辑系统生产商

  • Android编程设置全屏的方法实例详解

    本文实例讲述了Android编程设置全屏的方法.分享给大家供大家参考,具体如下: 在实际的应用程序开发中,我们有时需要把 Activity 设置成全屏显示,一般情况下,可以通过两种方式来设置全屏显示效果.其一,通过在代码中可以设置,其二,通过manifest配置文件来设置全屏. 其一:在代码中设置(如下) package xiaohang.zhimeng; import android.app.Activity; import android.content.pm.ActivityInfo; i

  • Android开发实现拍照功能的方法实例解析

    本文实例讲述了Android开发实现拍照功能的方法.分享给大家供大家参考,具体如下: 解析: 1)判断是否有摄像头checkCameraHardware(this) 2)获得相机camera = Camera.open(0); 3)把相机添加到mPreView = new SurfacePreView(this, mCamera); 4)实现拍照 mCamera.autoFocus 5)在拍照后使用mCamera.takePicture(null, null, mPicture);方法把图片保存

  • Android中常用的XML生成方法实例分析

    本文实例讲述了Android中常用的XML生成方法.分享给大家供大家参考.具体如下: 1. java代码: package com.android.antking.xml; import java.io.OutputStream; import java.util.List; import org.xmlpull.v1.XmlSerializer; import android.util.Xml; /**采用pull 生成xml文件 * * @author antkingwei * */ pub

  • Android编程之菜单的实现方法实例详解

    本文实例讲述了Android编程之菜单的实现方法.分享给大家供大家参考,具体如下: Options Menu 当用户按下menu button按钮时显示的菜单 Context Menu 当用户长久按住屏幕,被注册显示上下文菜单的视图时显示的菜单 Submenu    当用户按下一个菜单的某个选项时弹出的子菜单 以上三种菜单也就是我们经常所说的:选项菜单,上下文菜单和子菜单. 一.上下文菜单 1. 实现onCreateContextMenu即可创建该菜单 @Override public void

  • Android实现悬浮窗的简单方法实例

    目录 1. 前言 2.原理 3.具体实现 3.1浮窗布局 3.2 悬浮窗的实现 1. 使用服务Service 2. 获取WindowManager并设置LayoutParams 3. 创建View并添加到WindowManager 4. 实现悬浮窗的拖拽和关闭功能 5. 利用广播进行通信 6. 设置权限 3.3 完整代码 4. 总结 1. 前言 现在很多应用都有小悬浮窗的功能,比如看直播的时候,通过Home键返回桌面,直播的小窗口仍可以在屏幕上显示.下面将介绍下悬浮窗的的一种简单实现方式. 2.

  • Android识别NFC芯片制造商的方法

    如何识别? 通过NFC模块的Serial Number即UID,用户访问权限为R只读,每颗NFC芯片独有序列号(UID),在出厂时写入,用户不能改写. Android如何实现? 通过Intent获取UID /** * 读取nfcUID 返回String类型大写的UID * * 这个测试顺便加上的,下面用不到,做个笔记~~ * */ public static String readUID(Intent intent) throws UnsupportedEncodingException { T

随机推荐