diff --git a/blogs/埋点.md b/blogs/埋点.md index a8f266d..64e4a9c 100644 --- a/blogs/埋点.md +++ b/blogs/埋点.md @@ -146,5 +146,19 @@ public class MyApplication extends Application { 对于 App 的启动和退出事件,其实就是判断当前 App 是出于前台还是处于后台。而 Android 系统本身没有给 App 提供相关的接口来判断这些状态,所以只能借助其他方式来间接判断。 +| 方案 | 含义 | 是否需要权限 | 特点 | +| ------ | -------------------------- | ------------ | ---------------- | +| 方案一 | RunningTask | 否 | 5.0 此方法已废弃 | +| 方案二 | RunningProcess | 否 | 无 | +| 方案三 | ActivityLifecycleCallbacks | 否 | 简单、代码量少 | +| 方案四 | UsageStatsManager | 是 | 需要用户手动授权 | +| 方案五 | 无障碍服务 | 否 | 需要用户手动授权 | +| 方案六 | 读取 /proc 目录下的信息 | 是 | 效率 | + + + +详见:[https://github.com/wenmingvs/AndroidProcess](https://github.com/wenmingvs/AndroidProcess) + #### 参考 +[https://github.com/wenmingvs/AndroidProcess](https://github.com/wenmingvs/AndroidProcess) \ No newline at end of file diff --git a/blogs/埋点之应用前后台判断.md b/blogs/埋点之应用前后台判断.md new file mode 100644 index 0000000..7b50a4b --- /dev/null +++ b/blogs/埋点之应用前后台判断.md @@ -0,0 +1,221 @@ +--- +埋点之应用前后台判断 +--- + +#### 目录 + +1. 思维导图 +2. 六种实现方式 + - RunningTask + - RunningProcess + - ActivityLifecucleCallbacks + - UsageStatsManager + - 无障碍服务 + - 读取 /proc 目录下的信息 + +#### 思维导图 + +#### ---- RnnningTask 方式 + +```java + public static boolean getRunningTask(Context context, String packageName) { + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + ComponentName cn = am.getRecentTasks(1, ActivityManager.RECENT_WITH_EXCLUDED).get(0).topActivity; + return !TextUtils.isEmpty(packageName) && packageName.equals(cn.getPackageName()); + } +``` + +主要功能在于 ActivityManager#getRecentTasks 方法。 + +原理: + +是当一个 APP 处于前台的时候,会处于 RunningTask 的栈顶,所以我们可以取出栈顶的任务进程,和我们传入的包名对比来确定 APP 是否位于前台。 + +缺点: + +该方法在 API 21 已经被废弃,只会返回自己和系统的一些不敏感的 Task,不再返回其他应用的从该方法的注释信息可以了解到,它返回用户最近启动的任务列表,此方法仅用于调试和显示任务管理用户界面,**绝对不能**用在应用程序中,因为未来可能会崩溃。 + +#### ---- RunningProcess 方式 + +```java + public static boolean getRunningAppProcess(Context context, String packageName) { + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List appProcessInfos = activityManager.getRunningAppProcesses(); + if (appProcessInfos == null) { + return false; + } + for (ActivityManager.RunningAppProcessInfo info : appProcessInfos) { + if (info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + && info.processName.equals(packageName)) { + return true; + } + } + return false; + } +``` + +原理: + +通过 runningProcess 获取到一个当前正在运行的进程的 List,遍历这个 List 的每一个进程,判断这个进程的 importance 属性是否是前台进程,并且包名一致,从而判断是否是前台应用。 + +缺点: + +在社交类型的 APP 中,常常需要常驻后台来不间断的获取服务器消息,这就需要我们把 Service 设置为 START_STICKY,保证 Service 常驻后台。如果设置了这个属性,这个 APP 的进程就会判断为前台,appProcess.importance 的值永远是 ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,这样就永远无法判断到底哪个是前台应用了。 + +#### ---- ActivityLifecycleCallbacks 方式 + +```java +public class MyApplication extends Application { + private int appCount = 0; + + @Override + public void onCreate() { + super.onCreate(); + registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + } + + @Override + public void onActivityStarted(Activity activity) { + appCount++; + } + + @Override + public void onActivityResumed(Activity activity) { + } + + @Override + public void onActivityPaused(Activity activity) { + } + + @Override + public void onActivityStopped(Activity activity) { + appCount--; + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + + } + + @Override + public void onActivityDestroyed(Activity activity) { + + } + }); + } + + public int getAppCount() { + return appCount; + } + + public void setAppCount(int appCount) { + this.appCount = appCount; + } +} +``` + +即使当 Application 因为内存不足被 kill 掉时,这个方法仍然可以使用,虽然全局变量的值会丢失,但是再次进入 App 时会重新统计。 + +需要注意这些方法的执行是在 Activity 的生命周期方法之前执行。 + +#### ---- UsageStatsManager 方式 + +原理: + +通过使用 UsageStatsManager 获取,此方法是 Android 5.0 之后提供的 API,可以获取一个时间段内的应用统计信息,但是必须需用户授权: + +```java + +``` + +手机设置 -> 查看应用使用情况 + +#### ---- 无障碍服务 + +```java +public class DetectService extends AccessibilityService { + + + private static String mForegroundPackageName; + private static volatile DetectService mInstance = null; + + public static DetectService getInstance() { + if (mInstance == null) { + synchronized (DetectService.class) { + if (mInstance == null) { + mInstance = new DetectService(); + } + } + } + return mInstance; + } + + public static String getmForegroundPackageName() { + return mForegroundPackageName; + } + + /** + * 判断当前应用的辅助功能是否开启 + */ + public static boolean isAccessibilitySettingOn(Context context) { + int accessibilityEnable = 0; + try { + accessibilityEnable = Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED); + } catch (Settings.SettingNotFoundException e) { + e.printStackTrace(); + } + if (accessibilityEnable == 1) { + String services = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + if (services != null) { + return services.toLowerCase().contains(context.getPackageName()); + } + } + return false; + } + + /** + * 监听窗口焦点,并且获取焦点窗口的包名 + * + * @param event + */ + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + mForegroundPackageName = event.getPackageName().toString(); + } + } + + @Override + public void onInterrupt() { + + } +``` + +使用: + +```java +public static boolean getFromAccessibilityService(Context context,String packageName){ + if (DetectService.isAccessibilitySettingOn(context)){ + DetectService detectService=DetectService.getInstance(); + String foreground=detectService.getmForegroundPackageName(); + return foreground.equals(packageName); + }else { + Intent intent=new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + return false; + } +} +``` + +原理: + +Android 辅助功能( AccessibilityService )为我们提供了一系例的事件回调,帮助我们指示一些用户界面的状态变化。我们可以派生辅助功能类,进而对不同的 AccessibilityEvent 进行处理,同样的,这个服务就可以判断当前的前台应用。可以用来判断任意应用甚至 Activity、PopopWindow、Dialog 对象是否处于前台。 + +#### —— 读取 /proc 文件 + +已失效。 \ No newline at end of file