Android | 判断App处于前台还是后台的方案

2024-06-06 19:18

本文主要是介绍Android | 判断App处于前台还是后台的方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

目前,有6种方案:

 法

判断原理需要权限可以判断其他应用位于前台特点
RunningTaskAndorid4.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 原理

       AndroidSDK14Application类里增加了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);}

       知道这些信息,我们就可以用更官方的办法来解决问题,只需要在ApplicationonCreate()里去注册上述接口,然后由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处于前台还是后台的方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1036990

相关文章

Linux使用nohup命令在后台运行脚本

《Linux使用nohup命令在后台运行脚本》在Linux或类Unix系统中,后台运行脚本是一项非常实用的技能,尤其适用于需要长时间运行的任务或服务,本文我们来看看如何使用nohup命令在后台... 目录nohup 命令简介基本用法输出重定向& 符号的作用后台进程的特点注意事项实际应用场景长时间运行的任务服

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11

Java解析JSON的六种方案

《Java解析JSON的六种方案》这篇文章介绍了6种JSON解析方案,包括Jackson、Gson、FastJSON、JsonPath、、手动解析,分别阐述了它们的功能特点、代码示例、高级功能、优缺点... 目录前言1. 使用 Jackson:业界标配功能特点代码示例高级功能优缺点2. 使用 Gson:轻量

Redis KEYS查询大批量数据替代方案

《RedisKEYS查询大批量数据替代方案》在使用Redis时,KEYS命令虽然简单直接,但其全表扫描的特性在处理大规模数据时会导致性能问题,甚至可能阻塞Redis服务,本文将介绍SCAN命令、有序... 目录前言KEYS命令问题背景替代方案1.使用 SCAN 命令2. 使用有序集合(Sorted Set)

MyBatis延迟加载的处理方案

《MyBatis延迟加载的处理方案》MyBatis支持延迟加载(LazyLoading),允许在需要数据时才从数据库加载,而不是在查询结果第一次返回时就立即加载所有数据,延迟加载的核心思想是,将关联对... 目录MyBATis如何处理延迟加载?延迟加载的原理1. 开启延迟加载2. 延迟加载的配置2.1 使用

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了