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_APPflag的即为系统App,否则为用户App。
但是,利用这个方法那些预装的App也会归到用户App中,那么有没有办法知道用户App中哪些是预装的哪些是用户手动安装的呢?
在这里分享一种方法:App的安装时间是整秒的为预装的第三方App。
如果不关心为什么能用这个奇怪方法来区分预装App的话,就可以关闭这篇文章了。
之前我也一直不清楚为什么可以用这种方法,当时我猜是因为手机第一次启动的时候时间是不准确的,会是某某年1月1日,然后因为启动时会扫描各个App目录然后安装App,因此被打上这样的安装时间。但这种解释是说不通的,即便真是这样,那安装时间也不应该是整秒的。因此我决定好好找一下原因,以下是我求证的历程。
背景知识
首先介绍一些背景知识。
App的安装时间可以通过获取PackageInfo得到,其firstInstallTime属性即安装时间。
/data/system/packages.xml保存了手机上安装的App的信息,其中App的安装时间就保存在这里。我在下面截取了2个示例。
... ...
其中it的值便是安装时间,这里是用十六进制保存的,上面这2个App的安装时间换算成十进制分别是1543770911816和1230739200000,对应的北京时间即2018-12-0301:15:11和2009-01-0100: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方法中有这么一段代码:
//Takecareoffirstinstall/lastupdatetimes. if(currentTime!=0){ if(pkgSetting.firstInstallTime==0){ pkgSetting.firstInstallTime=pkgSetting.lastUpdateTime=currentTime; }elseif((scanFlags&SCAN_UPDATE_TIME)!=0){ pkgSetting.lastUpdateTime=currentTime; } }elseif(pkgSetting.firstInstallTime==0){ //Weneed*something*.Taketimetimestampofthefile. pkgSetting.firstInstallTime=pkgSetting.lastUpdateTime=scanFileTime; }elseif((policyFlags&PackageParser.PARSE_IS_SYSTEM_DIR)!=0){ if(scanFileTime!=pkgSetting.timeStamp){ //Apackageonthesystemimagehaschanged;considerthis //tobeanupdate. 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文件的最后修改时间。
#statDownloadProvider.apk File:`DownloadProvider.apk' Size:504712 Blocks:992 IOBlocks:512 regularfile Device:10305h/66309d Inode:1308 Links:1 Access:(644/-rw-r--r--) Uid:(0/root) Gid:(0/root) Access:2009-01-0100:00:00.000000000 Modify:2009-01-0100:00:00.000000000 Change:2009-01-0100:00:00.000000000
时间与packages.xml中保存的时间一致,确实是把文件的最后修改时间作为了安装时间。那么还有一个问题需要确认,传入的currentTime是0吗?
我们追溯调用链,会在PackageManagerService的构造函数中看到扫描各个目录的方法。调用scanDirTracedLI方法传入的最后一个参数0即scanPackageDirtyLI方法中的currentTime。感兴趣的还可以仔细看看PackageManagerService到底扫描了哪些目录。
FilevendorOverlayDir=newFile(VENDOR_OVERLAY_DIR); scanDirTracedLI(vendorOverlayDir,mDefParseFlags |PackageParser.PARSE_IS_SYSTEM |PackageParser.PARSE_IS_SYSTEM_DIR |PackageParser.PARSE_TRUSTED_OVERLAY,scanFlags|SCAN_TRUSTED_OVERLAY,0); //Findbaseframeworks(resourcepackageswithoutcode). scanDirTracedLI(frameworkDir,mDefParseFlags |PackageParser.PARSE_IS_SYSTEM |PackageParser.PARSE_IS_SYSTEM_DIR |PackageParser.PARSE_IS_PRIVILEGED, scanFlags|SCAN_NO_DEX,0); //Collectedprivilegedsystempackages. finalFileprivilegedAppDir=newFile(Environment.getRootDirectory(),"priv-app"); scanDirTracedLI(privilegedAppDir,mDefParseFlags |PackageParser.PARSE_IS_SYSTEM |PackageParser.PARSE_IS_SYSTEM_DIR |PackageParser.PARSE_IS_PRIVILEGED,scanFlags,0); //Collectordinarysystempackages. finalFilesystemAppDir=newFile(Environment.getRootDirectory(),"app"); scanDirTracedLI(systemAppDir,mDefParseFlags |PackageParser.PARSE_IS_SYSTEM |PackageParser.PARSE_IS_SYSTEM_DIR,scanFlags,0); //Collectallvendorpackages. FilevendorAppDir=newFile("/vendor/app"); try{ vendorAppDir=vendorAppDir.getCanonicalFile(); }catch(IOExceptione){ //failedtolookupcanonicalpath,continuewithoriginalone } scanDirTracedLI(vendorAppDir,mDefParseFlags |PackageParser.PARSE_IS_SYSTEM |PackageParser.PARSE_IS_SYSTEM_DIR,scanFlags,0); //CollectallOEMpackages. finalFileoemAppDir=newFile(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-0100:00:00.000000000的整秒的时间(至于为什么是这样,那就是另一个问题了),而我们自己安装App时几乎不可能在一个整秒的时间安装,所有我们可以用安装时间是否为整秒来区分手机预装的App和用户手动安装的App。
至于区分预装App和用户手动安装的App有什么用?请发挥你的想象,比如说,一个用户的手机上只有你家一个手动安装的App或者少数几个App,那么他是想干什么好事呢?
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。