-
概述
項目中很多場景交互非常依賴於客戶端的前后景狀態以及其他一些輔助信息上傳,譬如當前新聞在前台(看到的是新聞界面)播放時,語音開啟音樂應用,此時我們希望能看到音樂界面,並且音樂在播;而在導航應用在前台時,我們不希望跳轉至音樂應用;此時,如果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的版本適配問題就很是頭疼,這是站在用戶角度考慮,但對開發者的技術要求也越高.唯有通過不斷的積累,持續地沉淀,才能跟上時代的腳步.古人雲,學如逆水行舟,不進則退,也是告誡世人持續學習的道理...