这段时间做的一个项目,需要获取当前正在运行的app,android5.0之前可以使用getRunningTask获取,5.0这个方法不可用了,但是提供了getRunningAppProcess也可以获得。但是自从android5.1以后,Google从安全和隐私方面考虑,也废弃了这个方法,现在只能返回自己的应用。这段时间一直在研究,在网上也搜寻了很久,诸如,通过反射ActivityManager.RunningAppProcessInfo下的“processState”,或者反射android.app.ActivityThread,或者获取正在运行的top activity也都是失败。还有使用UsageState,AccessableService,这些需要用户手动开启,不符合项目需要。也找过源代码研究比如ActivityManagerNative的,系统设置里的应用管理代码,也都无功而返。
终于在stackOverFlow找个一个大神的回答http://stackoverflow.com/a/32366476。读取android下proc的文件夹获取进程的相关信息。虽然之前看到过这个大神的获取正在运行的进程列表https://github.com/jaredrummler/AndroidProcesses,但是获取的是列表,不能判断哪一个进程是当然显示的应用,用于判断的foreground参数能返回多个true的情况。这个大神又在回答这个问题放出获取当前应用的代码。贴一下代码:
/** first app user */
public
static
final
int
AID_APP =
10000
;
/** offset for uid ranges for each user */
public
static
final
int
AID_USER =
100000
;
public
static
String getForegroundApp() {
File[] files =
new
File(
"/proc"
).listFiles();
int
lowestOomScore = Integer.MAX_VALUE;
String foregroundProcess =
null
;
for
(File file : files) {
if
(!file.isDirectory()) {
continue
;
}
int
pid;
try
{
pid = Integer.parseInt(file.getName());
}
catch
(NumberFormatException e) {
continue
;
}
try
{
String cgroup = read(String.format(
"/proc/%d/cgroup"
, pid));
String[] lines = cgroup.split(
"\n"
);
String cpuSubsystem;
String cpuaccctSubsystem;
if
(lines.length ==
2
) {
//有的手机里cgroup包含2行或者3行,我们取cpu和cpuacct两行数据
cpuSubsystem = lines[
0
];
cpuaccctSubsystem = lines[
1
];
}
else
if
(lines.length==
3
){
cpuSubsystem = lines[
0
];
cpuaccctSubsystem = lines[
2
];
}
else
{
continue
;
}
if
(!cpuaccctSubsystem.endsWith(Integer.toString(pid))) {
// not an application process
continue
;
}
if
(cpuSubsystem.endsWith(
"bg_non_interactive"
)) {
// background policy
continue
;
}
String cmdline = read(String.format(
"/proc/%d/cmdline"
, pid));
if
(cmdline.contains(
"com.android.systemui"
)) {
continue
;
}
int
uid = Integer.parseInt(
cpuaccctSubsystem.split(
":"
)[
2
].split(
"/"
)[
1
].replace(
"uid_"
,
""
));
if
(uid >=
1000
&& uid <=
1038
) {
// system process
continue
;
}
int
appId = uid - AID_APP;
int
userId =
0
;
// loop until we get the correct user id.
// 100000 is the offset for each user.
while
(appId > AID_USER) {
appId -= AID_USER;
userId++;
}
if
(appId <
0
) {
continue
;
}
// u{user_id}_a{app_id} is used on API 17+ for multiple user account support.
// String uidName = String.format("u%d_a%d", userId, appId);
File oomScoreAdj =
new
File(String.format(
"/proc/%d/oom_score_adj"
, pid));
if
(oomScoreAdj.canRead()) {
int
oomAdj = Integer.parseInt(read(oomScoreAdj.getAbsolutePath()));
if
(oomAdj !=
0
) {
continue
;
}
}
int
oomscore = Integer.parseInt(read(String.format(
"/proc/%d/oom_score"
, pid)));
if
(oomscore < lowestOomScore) {
lowestOomScore = oomscore;
foregroundProcess = cmdline;
}
}
catch
(IOException e) {
e.printStackTrace();
}
}
return
foregroundProcess;
}
private
static
String read(String path)
throws
IOException {
StringBuilder output =
new
StringBuilder();
BufferedReader reader =
new
BufferedReader(
new
FileReader(path));
output.append(reader.readLine());
for
(String line = reader.readLine(); line !=
null
; line = reader.readLine()) {
output.append(
'\n'
).append(line);
}
reader.close();
return
output.toString().trim();
//不调用trim(),包名后面会带有乱码
}
依照大神的代码,在实际测试中有的手机能返回当然的包名,有的还是返回null,比照系统文件和代码分析,发现有的手机里cgroup包含两行cpu 和cpuacct,有的则是三行,多了一行memory。所以对代码稍加改动,上面是改动过的。下面对调用的文件和文件内容解释一下:
1.proc下以数字命名的文件夹,文件夹名即是一个进程的pid,该文件夹下的文件包含这个进程的信息;
2.cgroup,控制组群(control groups)的简写,是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。cpu:设置cpu的使用率;cpuacct:记录cpu的统计信息。
3.bg_non_interactive,运行cpu的一个分组,另一分组是apps,当一个应用(进程)即可从apps分组切换到bg_non_interactive,也可以切换回来。apps分组可以利用95%的cpu,而bg_non_interactive只能使用大约5%。
4.cmdline,显示内核启动的命令行。
5.oom_score_adj,这个文件的数值用来标记在内存不足的情况下,启发式的(不知道怎么翻译好==)选择哪个进程被杀掉,值从0(从不被杀掉)到1000(总是被杀掉)。