当前位置: 移动技术网 > IT编程>移动开发>Android > Android识别预装的第三方App方法实例

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

2019年07月30日  | 移动技术网IT编程  | 我要评论

新著龙虎门在线漫画,妖精的飞羽,炎黄子孙在异界

前言

新买一台手机,里面会有很多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,那么他是想干什么好事呢?

总结

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网