keep move

Android应用安装时机简易判断

字数统计: 817阅读时长: 4 min
2019/03/10 Share

定义

此处将应用的安装时机分为:

  • 预装
  • 新机或刷机后第一时间安装(1天内)
  • 其他情况

原理

可获取的信息

  • uid

Android中每个app会被作为一个用户处理,拥有一个 uid 。正常情况下app的 uid 是根据安装先后递增的, uid 的范围在 android.os.Process 中定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Defines the start of a range of UIDs (and GIDs), going from this
* number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
* to applications.
*/
public static final int FIRST_APPLICATION_UID = 10000;

/**
* Last of application-specific UIDs starting at
* {@link #FIRST_APPLICATION_UID}.
*/
public static final int LAST_APPLICATION_UID = 19999;
  • flags

android.content.pm.ApplicationInfoflags 字段,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* Flags associated with the application. Any combination of
* {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
* {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
* {@link #FLAG_ALLOW_TASK_REPARENTING}
* {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP},
* {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS},
* {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
* {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_SUPPORTS_XLARGE_SCREENS},
* {@link #FLAG_RESIZEABLE_FOR_SCREENS},
* {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE},
* {@link #FLAG_ALLOW_BACKUP}, {@link #FLAG_KILL_AFTER_RESTORE},
* {@link #FLAG_RESTORE_ANY_VERSION}, {@link #FLAG_EXTERNAL_STORAGE},
* {@link #FLAG_LARGE_HEAP}, {@link #FLAG_STOPPED},
* {@link #FLAG_SUPPORTS_RTL}, {@link #FLAG_INSTALLED},
* {@link #FLAG_IS_DATA_ONLY}, {@link #FLAG_IS_GAME},
* {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_USES_CLEARTEXT_TRAFFIC},
* {@link #FLAG_MULTIARCH}.
*/
public int flags = 0;

/**
* Value for {@link #flags}: if set, this application is installed in the
* device's system image.
*/
public static final int FLAG_SYSTEM = 1<<0;
  • firstInstallTime

android.content.pm.PackageInfo 中有 firstInstallTime 字段,如下:

1
2
3
4
5
/**
* The time at which the app was first installed. Units are as
* per {@link System#currentTimeMillis()}.
*/
public long firstInstallTime;

判断方式

实测 uidfirstInstallTime 的关系如下:

uid 10000 10001 10060 10061 10100 10101
firstInstallTime t2 t1 t5 t6 t9 t10
system true false true false false false

uid 连续变化,从 10000 开始

firstInstallTime 在预装应用中可能出现跳变,不随 uid 连续变化,在用户自己安装的应用中则会随 uid 连续变化

flags 为 system 的 uid 没有覆盖全部的预装应用,如 10000 ~ 10061 为预装应用,但 10001 可能为非system

根据以上规律,设计以下判断方法:

1、从高 uid 往低 uid 查询,找到第一个 flags 为 system的应用 uid ,假设为 10060,同时记录其前一个(遍历的方向来说)应用的 uid ,假设为 10061

2、找到要判断应用的 uid ,假设为 x

3、如果 x <= 10060,则认为是预装应用,否则如果 x 的安装时间在 10061 之后一天之内,则认为是第一时间安装

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static final int TIME_BUILT_IN = 1;
public static final int TIME_FIRST_TIME = 2;
public static final int TIME_OTHER = 3;

public static int getInstallTimeOfPkg(String pkg, Context context) {
PackageManager pm = context.getPackageManager();
try {
PackageInfo toCheckPkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_CONFIGURATIONS);
long toCheckPkgInstallTime = toCheckPkgInfo.firstInstallTime;

long firstBuiltInInstallTime = 0;
int toCheckPkgUid = 0;

for (int uid = Process.LAST_APPLICATION_UID; uid >= Process.FIRST_APPLICATION_UID; uid--) {
String[] pkgList = pm.getPackagesForUid(uid);
if (pkgList == null) {
continue;
}
String pkgName = pkgList[0];
if (pkg.equals(pkgName)) {
toCheckPkgUid = uid;
}
PackageInfo pkgInfo = pm.getPackageInfo(pkgName, PackageManager.GET_CONFIGURATIONS);
boolean isSystem = (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
if (isSystem) {
if (toCheckPkgUid <= uid) {
return TIME_BUILT_IN;
} else if (toCheckPkgInstallTime - firstBuiltInInstallTime < DateUtils.DAY_IN_MILLIS) {
return TIME_FIRST_TIME;
}
break;
} else {
firstBuiltInInstallTime = pkgInfo.firstInstallTime;
}
}
} catch (Exception ignored) {
}

return TIME_OTHER;
}

PS:

  • Process.LAST_APPLICATION_UID 值较大,为提升效率遍历 uid 的起始值可设置小一些,如从 Process.FIRST_APPLICATION_UID + 2000 开始

  • 通过 uid 遍历安装应用的方式在多数机型上不会被认为使用了读取安装应用列表权限

  • 部分预装应用为非system应用,如果第一个预装app flags 不为 system,则判断出错

CATALOG
  1. 1. 定义
  2. 2. 原理
    1. 2.1. 可获取的信息
    2. 2.2. 判断方式