三种方式概述及对比
- getRunningTasks方法
在Android5.0以前,系统允许我们通过ActivityManager的getRunningTasks()函数,直接获取当前运行的App。
这种方法唯一的问题就是过时了,在5.0以上不能使用。 - USAGE_STATS_SERVICE方法
从Android5.0以后,系统为了安全起见,开始重重设限,要求我们通过用户手动授权的方式,获得USAGE_STATS_SERVICE的访问权限,才能读到数据。
但是,这种方式有两个问题:
1.要求用户手动授权,不能自动运行。
2.在碎片化系统中表现不一,有的系统无法给出权限,例如Android电视就无法真正获取权限。 - 系统proc文件夹方法
原理:Android系统在管理进程时,通过low memory killer机制来定时找出oom_score评分高出阈值的进程,进行回收,那么反过来考虑,oom_score值最低的,且oom_adj值为0的进程(0为前台进程的adj值),就是很可能是当前的前台进程了。
这种方式可以静默运行,但是也有两个问题:
1.获取的其实是进程名,默认进程名为App的包名,但是开发者可以自定义进程名,所以你可能拿不到App的包名。
2.获取的结果并不精准,因为评分最低的不一定是当前显示的App,还有可能是某个进程优先级很高的后台服务,我们可能需要维护一个黑名单,在代码中屏蔽掉这个名单上的所有后台服务。
getRunningTasks
在Android4.X的时代,获取当前运行的App非常简单:
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ComponentName cn = activityManager.getRunningTasks(1).get(0).topActivity;
String pkg = cn.getPackageName();
USAGE_STATS_SERVICE
从Android 5.0LOLLIPOP起,为了提升安全性,系统禁用了getRunningTasks,改为通过USAGE_STATS_SERVICE来获取当前运行的App:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
UsageStatsManager m = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
if (m != null) {
long now = System.currentTimeMillis();
//获取60秒之内的应用数据
List<UsageStats> stats = m.queryUsageStats(UsageStatsManager.INTERVAL_BEST, now - 60 * 1000, now);
Log.i(TAG, "Running app number in last 60 seconds : " + stats.size());
String topActivity = "";
//取得最近运行的一个app,即当前运行的app
if ((stats != null) && (!stats.isEmpty())) {
int j = 0;
for (int i = 0; i < stats.size(); i++) {
if (stats.get(i).getLastTimeUsed() > stats.get(j).getLastTimeUsed()) {
j = i;
}
topActivity = stats.get(j).getPackageName();
Log.i(TAG, "top running app is : "+topActivity);
}
}
return stats.size();
}
}
这需要用户手动授权,调起系统授权页面“有权查看使用情况的应用”
系统页面授权
代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (!hasPermission()) {
startActivityForResult(
new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS),
MY_PERMISSIONS_REQUEST_PACKAGE_USAGE_STATS);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_PERMISSIONS_REQUEST_PACKAGE_USAGE_STATS) {
if (!hasPermission()) {
Toast.makeText(this,"未能开启部分App权限!",Toast.LENGTH_SHORT).show();
}
}
}
//检测用户是否对本app开启了“Apps with usage access”权限
private boolean hasPermission() {
AppOpsManager appOps = (AppOpsManager)
getSystemService(Context.APP_OPS_SERVICE);
int mode = 0;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
android.os.Process.myUid(), getPackageName());
}
return mode == AppOpsManager.MODE_ALLOWED;
}
private static final int MY_PERMISSIONS_REQUEST_PACKAGE_USAGE_STATS = 1101;
通过系统proc文件夹读取
通过读取系统proc文件夹,找到所有进程列表,然后利用oom_adj文件,判断出当前正在使用的进程。
代码如下:
//5.0以上,没有usage state权限
public static final int AID_APP = 10000;
public static final int AID_USER = 100000;
/**
* 5.0以上,没有usage state权限
* @return
*/
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.XXX.xxx")){
continue;
}
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(),包名后会带有乱码
}
网友评论