很多场景下,都需要判断某个App处于前台还是后台。本文集网上编写的前台判断方案于一体。

目前,有6种方案:

方 法

判断原理 需要权限 可以判断其他应用位于前台 特点
RunningTask Andorid4.0系列可以,5.0以上机器不行 Android5.0此方法被废弃
RunningProcess 当App存在后台常驻的Service时失效
ActivityLifecycleCallbacks 简单有效,代码最少
UsageStatsManager 需要用户手动授权
AccessibilityService 需要用户手动授权
自解析/process 当/proc目录下的文件过多时,过多的IO操作会引起耗时

接下来,就对以上6种方法展开详细说明:

目录

1. RunningTask

1.1 原理

1.2 代码实现

1.3 方案缺点

2. RunningProcess

2.1原理

2.2  代码实现

2.3 方案缺点

3.ActivityLifecycleCallbacks

3.1 原理

3.2 代码实现

3.3 方案特点

4. UsageStatsManager

4.1 原理

4.2 代码实现

4.3 方案特点

5.  AccessibilityService

5.1 原理

5.2 代码实现

5.3 方案特点

6. 自解析/process

6.1 原理

6.2 优点

6.3 用法

6.4 方案特点

6.5 方案缺点

6.6 能耗问题解决

7.作为系统进程的获取方式

7.1 技术方案


1. RunningTask

1.1 原理

当一个App处于前台时,会处于RunningTask这个栈的栈顶,所以可以取出RunningTask栈顶的任务进程,与需要判断的App的包名进行比较,来达到目的。

1.2 代码实现

这种方法不仅能获取到前台进程的包名还能获取到activity名称。

public String getForegroundActivity() {  ActivityManager mActivityManager =  (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);  if (mActivityManager.getRunningTasks(1) == null) {  Log.e(TAG, "running task is null, ams is abnormal!!!");  return null;  }  ActivityManager.RunningTaskInfo mRunningTask =  mActivityManager.getRunningTasks(1).get(0);  if (mRunningTask == null) {  Log.e(TAG, "failed to get RunningTaskInfo");  return null;  }  String pkgName = mRunningTask.topActivity.getPackageName();  //String activityName =  mRunningTask.topActivity.getClassName();  return pkgName;
}  

1.3 方案缺点

getRunningTask方法在5.0以上已经被废弃,只会返回自己和系统的一些不敏感的task,不再返回其他应用的task,用CI方法来判断自身App是否处于后台仍然有效,但是无法判断其他应用是否位于前台,因为不能再获取信息。

2. RunningProcess

2.1原理

通过runningProcess获取到一个当前正在运行的进程的List,我们遍历这个List中的每一个进程,判断这个进程的一个importance 属性是否是前台进程,并且包名是否与我们判断的APP的包名一样,如果这两个条件都符合,那么这个App就处于前台。

2.2  代码实现

  以下code是判断当前应用是否在前台:

private static boolean isAppForeground(Context context) {ActivityManager activityManager =     (ActivityManager)context.getSystemService(Service.ACTIVITY_SERVICE);List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfoList =                                activityManager.getRunningAppProcesses();if (runningAppProcessInfoList == null) {Log.d(TAG,"runningAppProcessInfoList is null!");return false;}for(ActivityManager.RunningAppProcessInfo processInfo : runningAppProcessInfoList) {if (processInfo.processName.equals(context.getPackageName())&&(processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND)) { return true;}} return false;
}

以下code是判断在前台的是哪个应用:

public String getForegroundApp(Context context) {  ActivityManager am =  (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);  List<RunningAppProcesInfo> lr = am.getRunningAppProcesses();  if (lr == null) {  return null;  }  for (RunningAppProcessInfo ra : lr) {  if (ra.importance == RunningAppProcessInfo.IMPORTANCE_VISIBLE  || ra.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {  return ra.processName;  }  }  return null;
}  

getRunningAppProcess方法只能获取前台包名。

2.3 方案缺点

Android5.0之后已经被废弃。

例如,在聊天类型的App中,常常需要常驻后台来不间断地获取服务器的消息,就需要把Service设置成START_STICKY,kill后会被重启(等待5s左右)来保证Service常驻后台。如果Service设置了这个属性,这个App的进程就会被判断为前台。代码表现为

appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND

上述code永远成立,这样就永远无法判断到底那个是前台了。

3.ActivityLifecycleCallbacks

3.1 原理

       AndroidSDK14在Application类里增加了ActivityLifecycleCallbacks,我们可以通过这个Callback拿到App所有Activity的生命周期回调。

 public interface ActivityLifecycleCallbacks {void onActivityCreated(Activity activity, Bundle savedInstanceState);void onActivityStarted(Activity activity);void onActivityResumed(Activity activity);void onActivityPaused(Activity activity);void onActivityStopped(Activity activity);void onActivitySaveInstanceState(Activity activity, Bundle outState);void onActivityDestroyed(Activity activity);}

知道这些信息,我们就可以用更官方的办法来解决问题,只需要在Application的onCreate()里去注册上述接口,然后由Activity回调回来运行状态即可。

Android应用开发中一般认为back键是可以捕获的,而Home键是不能捕获的(除非修改framework),但是上述方法从Activity生命周期着手解决问题,虽然这两种方式的Activity生命周期并不相同,但是二者都会执行onStop();所以并不关心到底是触发了哪个键切入后台的。另外,Application是否被销毁,都不会影响判断的正确性

3.2 代码实现

(1)AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="mytest.example.com.broadcaststudy"><applicationandroid:name=".TestActivityLifecycleApplcation"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".SendBroadcastActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activity android:name=".SecondActivity"></activity></application></manifest>

(2) TestActivityLifecycleApplication.java

package mytest.example.com.broadcaststudy;import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;/*** Created by Maureen on 2018/1/18.*/
public class TestActivityLifecycleApplcation extends Application {private final String TAG = "TestActivityLifecycleApplcation";private static TestActivityLifecycleApplcation mTestActivityLifecycleApplcation;private int mActivityCount = 0;@Overridepublic void onCreate() {super.onCreate();mTestActivityLifecycleApplcation = new TestActivityLifecycleApplcation();registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {@Overridepublic void onActivityCreated(Activity activity, Bundle savedInstanceState) {Log.d(TAG,"onActivityCreated");}@Overridepublic void onActivityStarted(Activity activity) {Log.d(TAG,"onActivityStarted");mActivityCount++;}@Overridepublic void onActivityResumed(Activity activity) {Log.d(TAG,"onActivityResumed");}@Overridepublic void onActivityPaused(Activity activity) {Log.d(TAG,"onActivityPaused");}@Overridepublic void onActivityStopped(Activity activity) {Log.d(TAG,"onActivityStopped");mActivityCount--;}@Overridepublic void onActivitySaveInstanceState(Activity activity, Bundle outState) {Log.d(TAG,"onActivitySaveInstanceState");}@Overridepublic void onActivityDestroyed(Activity activity) {Log.d(TAG,"onActivityDestroyed");}});}public static TestActivityLifecycleApplcation getInstance( ) {if (null == mTestActivityLifecycleApplcation)mTestActivityLifecycleApplcation = new TestActivityLifecycleApplcation();return mTestActivityLifecycleApplcation;}public int getActivityCount( ) {return mActivityCount;}
}

(3) SendActivity.java

package mytest.example.com.broadcaststudy;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;/*** Created by ATC6111 on 2018/1/18.*/
public class SecondActivity extends Activity {private final String TAG = "SecondActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Log.d(TAG, "onCreate");}@Overrideprotected void onStart() {super.onStart();Log.d(TAG, "onStart");}@Overrideprotected void onRestart() {super.onRestart();Log.d(TAG, "onRestart");}@Overrideprotected void onResume() {super.onResume();Log.d(TAG, "onResume");}@Overrideprotected void onPause() {super.onPause();Log.d(TAG, "onPause");}@Overrideprotected void onStop() {super.onStop();Log.d(TAG, "onStop");}@Overrideprotected void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy");}
}

(4) SendBroadcastActivity.java

package mytest.example.com.broadcaststudy;import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;import java.util.List;public class SendBroadcastActivity extends Activity {private static final String TAG = "SendBroadcastActivity";private static final String ACTION_MAUREEN_TEST = "com.maureen.test";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Log.d(TAG,"onCreate");setContentView(R.layout.activity_main);Intent intent = new Intent();intent.setAction(ACTION_MAUREEN_TEST);//intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);Log.d(TAG,"Begin to sendBroadcast:");sendBroadcast(intent);Log.d(TAG,"Send broadcast end!");}@Overrideprotected void onStart() {super.onStart();Log.d(TAG,"onStart");}@Overrideprotected void onRestart() {super.onRestart();Log.d(TAG,"onRestart");}@Overrideprotected void onResume() {super.onResume();Log.d(TAG,"onResume");startActivity(new Intent(this, SecondActivity.class));}@Overrideprotected void onPause() {super.onPause();Log.d(TAG,"onPause");}@Overrideprotected void onStop() {super.onStop();Log.d(TAG,"onStop");}@Overrideprotected void onDestroy() {super.onDestroy();Log.d(TAG,"onDestroy");}
}

在讲解为什么不在onActivityResumed( )与onActivityPaused( )中对activity进行计数,而是在onActivityStarted()和onActivityStopped( )中对activity进行计数之前。

先看以下几种情况的activity生命周期:

A、启动App,进入SendBroadcastActivity:

01-01 05:33:56.401 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityCreated
01-01 05:33:56.401 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onCreate
01-01 05:33:56.427 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: Begin to sendBroadcast:
01-01 05:33:56.430 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: Send broadcast end!
01-01 05:33:56.431 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityStarted
01-01 05:33:56.431 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStart
01-01 05:33:56.432 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityResumed
01-01 05:33:56.432 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onResume

B、点击back键退出SendBroadcastActivity:

点击back键退出App:
01-01 05:35:37.983 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityPaused
01-01 05:35:37.983 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onPause
01-01 05:35:38.035 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityStopped
01-01 05:35:38.036 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStop
01-01 05:35:38.036 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityDestroyed
01-01 05:35:38.036 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onDestroy

C、在A情况下点击home键:

01-01 05:37:34.690 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityPaused
01-01 05:37:34.690 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onPause
01-01 05:37:34.708 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivitySaveInstanceState
01-01 05:37:34.708 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityStopped
01-01 05:37:34.708 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStop

D、在A情况下点击recent键kill进程:

从recent中kill进程:
01-01 05:38:17.867 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityPaused
01-01 05:38:17.867 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onPause
01-01 05:38:17.914 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivitySaveInstanceState
01-01 05:38:17.914 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityStopped
01-01 05:38:17.914 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStop

E、新增code在SendBroadcastActivity的onResume( )中启动SecondActivity:

在SendBroadcastActivity的onResume函数中启动SecondActivity:
01-01 05:57:05.262 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onCreate
01-01 05:57:05.286 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStart
01-01 05:57:05.287 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onResume
01-01 05:57:05.324 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onPause
01-01 05:57:05.367 16836-16836/mytest.example.com.broadcaststudy D/SecondActivity: onCreate
01-01 05:57:05.370 16836-16836/mytest.example.com.broadcaststudy D/SecondActivity: onStart
01-01 05:57:05.370 16836-16836/mytest.example.com.broadcaststudy D/SecondActivity: onResume
01-01 05:57:05.605 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStop

其中尤其是E,可以看到activity切换时的生命周期。这里暂且称SendBroadcastActivity为A,SecondActivity为B。

如果在A.onResume( )时mActivityCount = 1; A.onPause()时mActivityCount--,

在B.onResume( )时mActivityCount++,此处就会有个短暂的延时,在跳转过程中就会出现mActivityCount = 0,

即判断App在后台,就不正确了。

所以要在onActivityStart( )和onActivityStopped( )中 对activity进行计数。

即在A.onStart()时mActivity = 1; B.onStart( )时mActivity ++; A.onStop()时,mActivity--。

所以,当activity的计数为0时表示应用在后台,否则就在前台。

3.3 方案特点

(1)Android应用开发中,一般认为back键是可以捕获的,而Home键不能捕获(除非修改Framework),虽然这两种方式的Activity生命周期并不相同,但是二者都会执行onStop( );所以并不关心到底是哪个键切入后台的。另外,Application是否销毁,都不会影响判断的正确性;

(2)该方案除了用于判断当前应用内的哪个activity位于前台外,还可用于作为实现“进程完全退出”的一种很好的计数方案;

(3)该方案需要在Application中进行注册相关Activity生命周期的回调,上述code所示。只需要对mActivityCount计数进行判断即可知道是否在前台。

4. UsageStatsManager

4.1 原理

通过使用UsageStatsManager获取,此方法是Android5.0之后提供的新API,可以获取一个时间段内的应用统计信息,但是

必须满足以下要求。

使用前提

  1. 此方法只在android5.0以上有效
  2. AndroidManifest中加入此权限
    <uses-permission  android:name="android.permission.PACKAGE_USAGE_STATS" />
  1. 打开手机设置,点击安全-高级,在有权查看使用情况的应用中,为这个App打上勾

4.2 代码实现

UsageStatsManager mUsageStatsManager = (UsageStatsManager)context.getApplicationContext().getSystemService(Context.USAGE_STATS_SERVICE);
long time = System.currentTimeMillis();
List<UsageStats> stats ;
if (isFirst){stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - TWENTYSECOND, time);
}else {stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - THIRTYSECOND, time);
}
// Sort the stats by the last time used
if(stats != null) {TreeMap<Long,UsageStats> mySortedMap = new TreeMap<Long,UsageStats>();start=System.currentTimeMillis();for (UsageStats usageStats : stats) {mySortedMap.put(usageStats.getLastTimeUsed(),usageStats);}LogUtil.e(TAG,"isFirst="+isFirst+",mySortedMap cost:"+ (System.currentTimeMillis()-start));if(mySortedMap != null && !mySortedMap.isEmpty()) {                    topPackageName =  mySortedMap.get(mySortedMap.lastKey()).getPackageName();        runningTopActivity=new ComponentName(topPackageName,"");if (LogUtil.isDebug())LogUtil.d(TAG,topPackageName);}
}

跳转到“查看应用使用权限”界面的跳转代码如下:

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);

还要声明权限:

<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />

4.3 方案特点

1、该方案最大的缺点是需要用户手动授权,因此在使用时要结合场景做适当引导;

2、该方案为Android5.0以后Google官方比较推荐的获取进程信息的方式,是最符合Google意图的方式,不过在使用时会有一些延时需要小心处理。

3、使用时可能会遇到通知栏也被计入,具体可参考:4 种获取前台应用的方法(肯定有你不知道的) - 掘金

5.  AccessibilityService

5.1 原理

Android 辅助功能(AccessibilityService) 为我们提供了一系列的事件回调,帮助我们指示一些用户界面的状态变化。

我们可以派生辅助功能类,进而对不同的 AccessibilityEvent 进行处理。同样的,这个服务就可以用来判断当前的前台应用

优势

  • AccessibilityService 有非常广泛的 ROM 覆盖,特别是非国产手机,从 Android API Level 18(Android 2.2) 到 Android Api Level 23(Android 6.0)
  • AccessibilityService 不再需要轮询的判断当前的应用是不是在前台,系统会在窗口状态发生变化的时候主动回调,耗时和资源消耗都极小
  • 不需要权限请求
  • 它是一个稳定的方法,与 “方法5”读取 /proc 目录不同,它并非利用 Android 一些设计上的漏洞,可以长期使用的可能很大
  • 可以用来判断任意应用甚至 Activity, PopupWindow, Dialog 对象是否处于前台

劣势

  • 需要要用户开启辅助功能
  • 辅助功能会伴随应用被“强行停止”而剥夺

5.2 代码实现

参考博客:http://effmx.com/articles/tong-guo-android-fu-zhu-gong-neng-accessibility-service-jian-ce-ren-yi-qian-tai-jie-mian/

步骤:

(1)派生AccessibilityService,创建窗口状态探测服务

创建DetectionService.java

public class DetectionService extends AccessibilityService {final static String TAG = "DetectionService";static String foregroundPackageName;@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {return 0; // 根据需要返回不同的语义值}/*** 重载辅助功能事件回调函数,对窗口状态变化事件进行处理* @param event*/@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {/** 如果 与 DetectionService 相同进程,直接比较 foregroundPackageName 的值即可* 如果在不同进程,可以利用 Intent 或 bind service 进行通信*/foregroundPackageName = event.getPackageName().toString();/** 基于以下还可以做很多事情,比如判断当前界面是否是 Activity,是否系统应用等,* 与主题无关就不再展开。*/ComponentName cName = new ComponentName(event.getPackageName().toString(),event.getClassName().toString());}}@Overridepublic void onInterrupt() {}@Overrideprotected  void onServiceConnected() {super.onServiceConnected();}
}

(2)创建Accessibility Service Info属性文件

创建res/xml/detection_service_config.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- 根据的 Service 的不同功能需要,你可能需要不同的配置 -->
<accessibility-servicexmlns:android="http://schemas.android.com/apk/res/android"android:accessibilityEventTypes="typeWindowStateChanged"android:accessibilityFeedbackType="feedbackGeneric"android:accessibilityFlags="flagIncludeNotImportantViews" />

(3)注册Detection service到AndroidManifest.xml

在AndroidManifest.xml中添加

<serviceandroid:name="your_package.DetectionService"android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"><intent-filter><action android:name="android.accessibilityservice.AccessibilityService"/></intent-filter><meta-dataandroid:name="android.accessibilityservice"android:resource="@xml/detection_service_config"/></service>

(4)使用detection service判断应用是否在前台

创建isForegroundPkgViaDetectionService( )函数

  /*** 方法6:使用 Android AccessibilityService 探测窗口变化,跟据系统回传的参数获取 前台对象 的包名与类名** @param packageName 需要检查是否位于栈顶的App的包名*/public static boolean isForegroundPkgViaDetectionService(String packageName) {return packageName.equals(DetectingService.foregroundPackageName);}

去设置里开启辅助功能,就可以通过isForegroundPkgDetectService( )判断应用是否在前台了,只需要传入相应应用的包为参数即可。

当然,也可以参照以下方式引导用户开启辅助功能:

(1)引导用户开启辅助功能

 final static String TAG = "AccessibilityUtil";// 此方法用来判断当前应用的辅助功能服务是否开启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.i(TAG, e.getMessage());}if (accessibilityEnabled == 1) {String services = Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);if (services != null) {return services.toLowerCase().contains(context.getPackageName().toLowerCase());}}return false;}private void anyMethod() {// 判断辅助功能是否开启if (!isAccessibilitySettingsOn(getContext())) {// 引导至辅助功能设置页面startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));} else {// 执行辅助功能服务相关操作}}

效果如下:

5.3 方案特点

1. AccessibilityService有非常广泛的ROM覆盖,特别是非国产手机,从API Level 8 (Android2.2)到API Level 23(Android6.0)

2. AccessibilityService不再需要轮询地判断当前的应用是不是在前台,系统会在窗口状态发生变化的时候主动回调,耗时和资源消耗都极小;

3.不需要权限请求;

4.它是一个稳定的方法,并非利用Android一些设计上的 漏洞,可以长期使用的可能很大;

5.可以用来判断任意应用甚至Activity,PopupWindow,Dialog对象是否处于前台。

5.4 方案缺点

1、需要用户手动开启辅助功能;

2、辅助功能会伴随应用被“强行停止”或第三方管理工具通过Root而剥夺,而且进程重启需要对用户进行重新引导开启;

3、部分厂商可能对辅助功能进行限制,如已知的vivo部分机型。

6. 自解析/process

6.1 原理

无意中看到乌云上有人提的一个漏洞,Linux系统内核会把process进程信息保存在/proc目录下,Shell命令去获取的他,再根据进程的属性判断是否为前台

6.2 优点

  1. 不需要任何权限
  2. 可以判断任意一个应用是否在前台,而不局限在自身应用

6.3 用法

(1)获取一系列正在运行的App的进程

List<AndroidAppProcess> processes = ProcessManager.getRunningAppProcesses();

(2)获取任一正在运行的App进程的详细信息

AndroidAppProcess process = processes.get(location);
String processName = process.name;Stat stat = process.stat();
int pid = stat.getPid();
int parentProcessId = stat.ppid();
long startTime = stat.stime();
int policy = stat.policy();
char state = stat.state();Statm statm = process.statm();
long totalSizeOfProcess = statm.getSize();
long residentSetSize = statm.getResidentSetSize();PackageInfo packageInfo = process.getPackageInfo(context, 0);
String appName = packageInfo.applicationInfo.loadLabel(pm).toString();

(3)判断是否在前台

if (ProcessManager.isMyProcessInTheForeground()) {// do stuff
}

(4)获取一系列正在运行的App进程的详细信息

List<ActivityManager.RunningAppProcessInfo> processes = ProcessManager.getRunningAppProcessInfo(ctx);

6.4 方案特点

1、不需要任何权限;

2、可以判断任意一个应用是否在前台,而不局限在自身应用;

3、当/proc下文件夹过多时,此方法是耗时操作;

4、该方案存在能耗问题;

5、在Android6.0.1以上版本或部分厂商版本受限于SEAndroid,只能获取到第3方进程的信息。

6.5 方案缺点

1. 当/proc下文件夹过多时,此方法是耗时操作

2. 该方案再6.0手机适配运行ok,但在最新的小米、华为6.0.1手机中发现受限于SELinux,无法读取系统应用的设备节点进行解析,只能解析第三方应用设备节点。

6.6 能耗问题解决

1、Java层对象缓存:对调用比较频繁的Java层对象在JNI中建立全局缓存,这就避免了每次调用时都需要通过JNI接口获取;

对一些判断是需要的场景在初始化时由Java层传入Jni层,并建立全局缓存。

2、过来的为Android进程:将pid小于1000的Native进程过滤掉;

3、只解析发生变化的进程:在每次轮询解析/proc节点时先判断进程的pid在缓存中是否存在,如果存在只需要更新进程的优先

级信息,其他信息不会发生变化;如果进程之前不存在则需要全新解析:

(1)命中缓存时的解析代码如下

//Code,待补充

(2)未命中缓存时,则进行全新解析

//Code,待补充

4、在解析进程时,过来父进程为zygote的进程:Android中所有应用进程的父进程都是Zygote;

5、在Java层对调用做缓存处理:对于调用比较频繁的情况,如果当次Native调用没有完成,则返回之前的值,不需要阻塞等待;

6、对于只关心前台进程的场景进行特殊处理:

//code,待补充

通过优化,适配方案的能耗与系统接口基本保持一致。

7.作为系统进程的获取方式

7.1 技术方案

虽然getRunningTask从Android5.0开始被系统废弃,但是作为系统应用时,该接口依然是可用的。在用户取得Root权限,或者应用跟厂商合作时,应用本身可能会被内置在系统目录,即:/system/app/或system/private-app/等目录,因此对于这种情况,使用getRunningTask获取依然是一种方便的实现。

1、需要判断应用是否为系统应用:

//Code,待补充。

2、在AndroidManifest.xml中需要声明如下权限:

//Code,待补充。

参考链接:

1.GitHub - wenmingvs/AndroidProcess: 判断App位于前台或者后台的6种方法

2.http://effmx.com/articles/tong-guo-android-fu-zhu-gong-neng-accessibility-service-jian-ce-ren-yi-qian-tai-jie-mian/

3.4 种获取前台应用的方法(肯定有你不知道的) - 掘金

4.http://www.voidcn.com/article/p-aaftfysq-bny.html

Android | 判断App处于前台还是后台的方案相关推荐

  1. 如何判断app在前台还是后台

    项目遇到一个轮训,有通知就toast.但是toast是可以不受actvity控制的,当我按home键让app常驻后台后,我要他不吐司,这时候我们需要判断app在前台还是后台. 发挥谷歌搜索的威力 Ch ...

  2. android 判断app处于前后台

    判断 onActivityResumed() 方法表示进入了前台 onActivityStopped() 方法表示进入了后台 实现方式: 在application中利用接口实现 registerAct ...

  3. Android判断App前台运行还是后台运行(运行状态)

    原文:http://p.codekk.com/detail/Android/wenmingvs/AndroidProcess AndroidProcess 项目地址:https://github.co ...

  4. Android 判断app是否在前台还是在后台运行

    Android 判断app是否在前台还是在后台运行,直接看代码,可直接使用. [java]  view plain copy public static boolean isBackground(Co ...

  5. android5.0以后获取应用运行状态,Android判断App前台运行还是后台运行(运行状态)...

    本文通过图文并茂的方式给大家介绍android判断app状态的相关内容,具体详情如下所示: 要了解这块,首先需要明白一些概念,app,process,task 1.process就是进程,是linux ...

  6. iOS应用处于前台、后台、应用被杀掉场景-收到远程推送内容进行收款语音播报;

    iOS应用处于前台.后台.应用被杀掉场景-收到远程推送内容进行收款语音播报: 介绍: 收银应用两大技术点:远程推送.收款成功语音播报收款金额及其他附带语音内容: 顺便点下android语音播报有一个至 ...

  7. Android 判断App运行在模拟器还是真机上的最终解决方案

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明. 本文链接: https://blog.csdn.net/qq_39731011/article ...

  8. 判断App整体处于前台还是后台

    转载请注明转自:[noyet12的博客](http://blog.csdn.net/u012975705) 博客原址:http://blog.csdn.net/u012975705/article/d ...

  9. android判断app前后台状态

    项目中需要在应用从后台切换到前台时做操作,自己实现了功能,但对这块的机制不太了解,So.找了相关的资料来学习总结下. !!! 部分资料来源https://github.com/wenmingvs/An ...

  10. Android判断app是不是第一次启动

    判断APP是不是第一次启动 基本思路就是,在第一次启动APP的时候,在本地保存一个布尔数据进行记录.如果是第一次启动保存为true,然后进行判断,如果是true,则提示是第一次启动并且修改为false ...

最新文章

  1. pl sql如何调试oracle存储过程,PL/SQL Developer中调试oracle的存储过程
  2. java rect 旋转_处理(Java可视化语言):使用rectMode(CENTER)而不是rectMode(CORNER)旋转矩形,留下奇数衰落轨迹效果...
  3. 带有Spring,Hibernate,Akka,Twitter Bootstrap,Apache Tiles和jQuery的Maven Web项目Kickstarter代码库...
  4. 虚拟磁盘没有可用的合格服务器,VMware提示:没有更多空间可供虚拟磁盘***.vmdk使用 所引发的故障及处理...
  5. SDN第四次上机作业
  6. 阿里云mysql数据库日志_阿里云mysql数据库操作日志
  7. 免费模板 | 高颜值超实用,这套财务分析模版自带BUFF加成
  8. LaserJet 5000 及 5100 系列打印机出现 pcl xl error 怎么办
  9. apollo学习之:如何测试canbus模块
  10. openlayers 地图上加图标_Openlayers绘制地图标注
  11. 好兄弟们,做了份上海落户政策总结给大家看看
  12. Dharma家族变体,.adobe后缀勒索病毒解密
  13. 家庭版取消灰色勾选加密内容以便保护数据的方法
  14. xml和json转换的在线工具
  15. Python制作植物大战僵尸,赶快来试试吧
  16. Windows下动态内存分配方式http://whx.tzgt.gov.cn/newOperate/html/7/71/711/3938.html
  17. android 5.0小米1刷机包,小米1刷机包 V5稳定版V1.2 流畅纯净 精简省电 默认开启未知来源 Android4.1.2...
  18. 借“核高基”东风 打造国产数据库第一品牌
  19. 基于MUSIC算法的DOA估计Matlab仿真
  20. spawn-fcgi与fcgi

热门文章

  1. IDM6.39序列号,亲测可用
  2. 以太坊执行层P2P网络架构与设计:Discv5
  3. c语言岩石1ms,2017年注册岩土工程师基础考试真题下午和答案解析
  4. 2022年(上半年)信息系统项目管理师考试-综合知识真题及解析(一)
  5. 《诗经·陈风·月出》presentation
  6. 名悦集团:家庭用车买什么国产还是进口好
  7. 怎么把自己也拍成白雪公主?
  8. matlab 图像处理之边缘提取
  9. Ins图片爬取(基于python,selenium)
  10. FPGA Nios II学习笔记一