1. 问题描述
需要实现从我们app跳转到第三方app,并统计第三方app在前台所待时长的功能。
2. 问题分析
大概过程如下:
1)首先得有权限,我们这个需要实现允许查看应用使用情况、悬浮窗两个权限。
2)跳转到第三方app;
3)启动一个服务监听;
4)弹出一个悬浮窗
5)监听第三方app是否在前台运行。实现思路是启动一个定时器,每隔1s去查看前台应用信息。
6)预期时间完成,销毁悬浮窗,解绑Service。
3. 实现过程
3.1 悬浮窗
- 悬浮窗权限
当我们自己app在后台运行时,无法弹出一个Toast或者Dialog,所以得做一个类似于360安全卫士一样的悬浮窗。所以得要申请悬浮窗权限。6.0之前只需要在AndroidManifest文件中申请:1
2<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"/>
6.0之后,代码中动态申请,需要跳转到系统设置中去获取:
1 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ |
- 显示一个悬浮窗
显示一个悬浮窗,首先写一个布局文件,然后添加到WindowManager中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// LayoutInflater.from中Context用getApplicationContext()
View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.layout_window,null);
WindowManager mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
//设置type.系统提示型窗口,一般都在应用程序窗口之上.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}else{
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
//设置效果为背景透明.
params.format = PixelFormat.RGBA_8888;
//设置flags.不可聚焦及不可使用按钮对悬浮窗进行操控.
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//设置窗口初始停靠位置.
params.gravity = Gravity.LEFT | Gravity.TOP;
params.x = 0;
params.y = 0;
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowManager.addView(view,params); - 注意: 在Android 8.0后 params.type变化。具体参考: Android 8.0 悬浮窗变动与用法
3.2 监听app在前台运行
Android检测app运行在前台,5.0以前是通过获取手机当前活跃进程列表,5.0后这种办法用不了了,5.0以后通过UsageStatsManager(统计服务类)来获取。
1)5.0之前
通过ActivityManager获取运行app进程来判断app是否处于前台。
1 | public boolean isRunningForeground(Context context,String packageName){ |
2)5.0之后
Android5.0之后通过UsageStatsManager(统计服务类)
- 申请“允许查看应用使用情况”。
5.0以后查看应用使用情况
1 | <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" |
在代码中,我们也做一层判断,是否已经获取该权限,如果没有,则跳转到设置权限界面:
1 | Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); |
- 获取最近运行app
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
38public String getRunningPackageNameOver21(Context context){
String topPackageName = "";
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP){
return topPackageName;
}
// 1.获取统计服务类
UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
long currTime = System.currentTimeMillis();
//2.获取从今天0点到现在的使用情况
List<UsageStats> usageStatsList = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, getStartTime(),currTime);
//3.根据最后使用时间降序排列
Collections.sort(usageStatsList,new UsageComparator());
//获取前台应用,排除其他应用因通知栏而产生的统计信息
Field mLastEventField = null;
try {
mLastEventField = UsageStats.class.getField("mLastEvent");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
for (UsageStats usageStats:usageStatsList){
if (mLastEventField != null){
int lastEvent = 0;
try {
lastEvent = mLastEventField.getInt(usageStats);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (lastEvent == 1){
topPackageName = usageStats.getPackageName();
// Log.i(TAG,"包名:" + usageStats.getPackageName() + ",:" + dateFormat.format(new Date(date)));
return topPackageName;
}
}
}
return topPackageName;
}4. 最后效果