Android获取前台进程的方法
-
概述
项目中很多场景交互非常依赖于客户端的前后景状态以及其他一些辅助信息上传,譬如当前新闻在前台(看到的是新闻界面)播放时,语音开启音乐应用,此时我们希望能看到音乐界面,并且音乐在播;而在导航应用在前台时,我们不希望跳转至音乐应用;此时,如果AIUI云平台不知道我们哪个应用在前台,交互就会混乱,由此可见,客户端获取前台进程还是非常有必要讨论一下的.
1.通过RunningTask
当一个App处于前台的时候,会处于RunningTask的这个栈的栈顶,所以我们可以取出RunningTask的栈顶的任务进程,看他与我们的想要判断的App的包名是否相同,来达到效果.
List tasks = am.getRunningTasks(1);
if (tasks != null && !tasks.isEmpty()) {
ComponentName componentName = tasks.get(0).topActivity;
if (componentName != null) {
return componentName.getClassName();
}
}
然而getRunningTask方法在Android5.0以上已经被废弃,只会返回自己和系统的一些不敏感的task,不再返回其他应用的task,用此方法来判断自身App是否处于后台,仍然是有效的,但是无法判断其他应用是否位于前台,因为不再能获取信息
2.通过RunningProcess
通过runningProcess获取到一个当前正在运行的进程的List,我们遍历这个List中的每一个进程,判断这个进程的一个importance 属性是否是前台进程,并且包名是否与我们判断的APP的包名一样,如果这两个条件都符合,那么这个App就处于前台。
遗憾的是从Andriod5.1版本后getRunningAppProcesses()只能拿到自己的进程信息,所以已经不再适用了.
3.通过ActivityLifecycleCallbacks
AndroidSDK14在Application类里增加了ActivityLifecycleCallbacks,我们可以通过这个Callback拿到App所有Activity的生命周期回调。
public interface ActivityLifecycleCallbacks {
voidonActivityCreated(Activity activity, Bundle savedInstanceState);
voidonActivityStarted(Activity activity);
voidonActivityResumed(Activity activity);
voidonActivityPaused(Activity activity);
voidonActivityStopped(Activity activity);
voidonActivitySaveInstanceState(Activity activity, Bundle outState);
voidonActivityDestroyed(Activity activity);
}
知道这些信息,我们就可以用更官方的办法来解决问题,当然还是利用方案二里的Activity生命周期的特性,我们只需要在Application的onCreate()里去注册上述接口,然后由Activity回调回来运行状态即可。
private ActivityLifecycleCallbacks mActivityLifecycleCallbacks = new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { Log.d(TAG, "onActivityStarted " + activity.toString()); if (CustomActivityManager.getInstance().getTopActivity() == null) { Status.INSTANCE.setSatus(Constant.FG_ACTIVITY, null, null); } } @Override public void onActivityResumed(Activity activity) { Log.d(TAG, "onActivityResumed " + activity.toString()); CustomActivityManager.getInstance().addTopActivity(activity); } @Override public void onActivityPaused(final Activity activity) { Log.d(TAG, "onActivityPaused " + activity.toString()); CustomActivityManager.getInstance().removeTopActivity(activity); } @Override public void onActivityStopped(final Activity activity) { if (CustomActivityManager.getInstance().getTopActivity() == null) { Status.INSTANCE.setSatus(Constant.BG_ACTIVITY, null, null); } } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { // Log.d(TAG, "onActivitySaveInstanceState " + activity.toString()); } @Override public void onActivityDestroyed(Activity activity) { } };
该方法需要在Application中进行注册相关Activity生命周期的回调registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
4.通过UsageStatsManager
UsageStatsManager就是使用情况统计管理者,通过它可以获取应用的使用情况。它是Android 5.0 才有的API。使用它之前需要在清单文件中配置 “android.permission.PACKAGE_USAGE_STATS”的权限
用户必须在 设置–安全–有权查看使用情况的应用 中勾选相应的应用
对应设备 Android 5.0 及其以上。
魅族和小米手机不能通过UsageStatsManager获取应用使用情况
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1) public static boolean queryUsageStats(Context context, String packageName) { class RecentUseComparator implements Comparator<UsageStats> { @Override public int compare(UsageStats lhs, UsageStats rhs) { return (lhs.getLastTimeUsed() > rhs.getLastTimeUsed()) ? -1 : (lhs.getLastTimeUsed() == rhs.getLastTimeUsed()) ? 0 : 1; } } RecentUseComparator mRecentComp = new RecentUseComparator(); long ts = System.currentTimeMillis(); UsageStatsManager mUsageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); List<UsageStats> usageStats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, ts - 1000 * 10, ts); if (usageStats == null || usageStats.size() == 0) { if (!havePermissionForTest(context)) { Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); Toast.makeText(context, "权限不够\n请打开手机设置,点击安全-高级-有权查看使用情况的应用,开启这个应用的权限",Toast.LENGTH_LONG).show(); } return false; } Collections.sort(usageStats, mRecentComp); String currentTopPackage = usageStats.get(0).getPackageName(); return currentTopPackage.equals(packageName); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static boolean havePermissionForTest(Context context) { try { PackageManager packageManager = context.getPackageManager(); ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0); AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STAGS, applicationInfo.uid, applicationInfo.packageName); return mode == AppOpsManager.MODE_ALLOWED; } catch (PackageManager.NameNotFoundException e) { return true; } }
5.通过AccessbilityService
通过 Android 自带无障碍功能,监控窗口焦点的变化,拿到焦点窗口对应包名
public static boolean getFromAccessibilityService(Context context, String packageName) { if (DetectService.isAccessibilitySettingsOn(context)) { DetectService detectService = DetectService.getInstance(); String foreground = detectService.getForegroundPackage(); Log.d(DEBUG, "当前窗口焦点对应包名为:" + foreground); return packageName.equals(foreground); } else { Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY+SETTINGS); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); Toask.makeTexxt(context, "请为App打开辅助功能开关", Toast.LENGTH_SHORT).show(); return false; } } public class DetectService extends AccessibilityService { private static String mForegroundPackageName; private static DetectService mInstatnce = null; private DetectService {} public static DetectService getInstance() { if (mInstance == null) { synchronized (DetectService.class) { if (mInstance == null) { mInstance = new DetectService(); } } } return mInstance; } @Override public void onAccessibilityEvent(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { mForegroundPackageName = event.getPackageName().toString(); } } @Override public void onInterrupt() { } public String getForegroundPackageName() { return mForegroundPackageName; } public static boolean isAccessibilitySettingsOn(Context context) { int accessibilityEnabled = 0; try { accessibilityEnabled = Settings.Secure.getInt(context.getContentResolver(), android.provider.Settings.Secure.ACCESSIBILITY_ENABLED); } catch (Settings.SettingNotFoundException e) { Log.d(DEBUG, e.getMessage()); } if (accessibilityEnabled == 1) { String services = Settings.Secure.getString(context.getContentResolver(), Setting.Secure.ENABLED_ACCESSIBILITY_SERICES); if (services != null) { return services.toLowerCase().contains(context.getPackageName().toLowerCase()); } } return false; } }
需要创建 ACCESSIBILITY SERVICE INFO 属性文件,注册 DETECTION SERVICE 到 AndroidManifest.xml
AccessibilityService有非常广泛的 ROM 覆盖, AccessibilityService不再需要轮询的判断当前的应用是不是在前台,系统会在窗口状态发生变化的时候主动回调,耗时和资源消耗都极小。可以用来判断任意应用甚至 Activity,PopupWindow, Dialog 对象是否处于前台.
-
总结
Android设备碎片化如此严重,不说各大厂商定制系统会修改API源码,单论Android的版本适配问题就很是头疼,这是站在用户角度考虑,但对开发者的技术要求也越高.唯有通过不断的积累,持续地沉淀,才能跟上时代的脚步.古人云,学如逆水行舟,不进则退,也是告诫世人持续学习的道理...