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

很多场景下,都需要判断某个App处于前台还是后台。

目前,有6种方案:

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

1. RunningTask

1.1 原理

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

1.2 代码实现

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

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();
        //int taskid = mRunningTask.id; //获取到taskid就可以使用activityManager.moveTaskToFront(taskInfo.id, 0);回到前台了
        return pkgName;  
}  

1.3 方案缺点

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

1.4 其他

getRunningServices()获取系统运行中的后台service。

getRecentTasks() 获取最近打开的task,手机查看最近打开的应用可以用这个实现。

...

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);
    //获取app运行中的process集合
    List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfoList =                              
                                            activityManager.getRunningAppProcesses();
​
    if (runningAppProcessInfoList == null) {
        Log.d(TAG,"runningAppProcessInfoList is null!");
        return false;
    }
    //processInfo.processName是进程名   
   for(ActivityManager.RunningAppProcessInfo processInfo : runningAppProcessInfoList) {
       //判断进程的重要级别和进程名与包名的比较
       //ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND=100
       //importance属性==100 表示这个进程在前台,其他数字表示在后台,所以通过importance和processName判断应用是否在前台
        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方法只能获取前台包名。通过包名,使用intent的方式也能回到前台

Intent intent = context.getPackageManager().getLaunchIntentForPackage(appProcessInfo.processName);
context.startActivity(intent);

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">
​
    <application
        android: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;
​
public class TestActivityLifecycleApplcation extends Application {
​
    private final String TAG = "TestActivityLifecycleApplcation";
    private static TestActivityLifecycleApplcation mTestActivityLifecycleApplcation;
    private int mActivityCount = 0;
​
    @Override
    public void onCreate() {
        super.onCreate();
        mTestActivityLifecycleApplcation = new TestActivityLifecycleApplcation();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                Log.d(TAG,"onActivityCreated");
            }
​
            @Override
            public void onActivityStarted(Activity activity) {
                Log.d(TAG,"onActivityStarted");
                mActivityCount++;
            }
​
            @Override
            public void onActivityResumed(Activity activity) {
                Log.d(TAG,"onActivityResumed");
            }
​
            @Override
            public void onActivityPaused(Activity activity) {
                Log.d(TAG,"onActivityPaused");
            }
​
            @Override
            public void onActivityStopped(Activity activity) {
                Log.d(TAG,"onActivityStopped");
                mActivityCount--;
            }
​
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
                Log.d(TAG,"onActivitySaveInstanceState");
            }
​
            @Override
            public 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;
​
public class SecondActivity extends Activity {
​
    private final String TAG = "SecondActivity";
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
    }
​
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }
​
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }
​
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
    }
​
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
    }
​
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop");
    }
​
    @Override
    protected 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";
​
    @Override
    protected 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!");
    }
​
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"onStart");
    }
​
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"onRestart");
    }
​
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"onResume");
        startActivity(new Intent(this, SecondActivity.class));
    }
​
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"onPause");
    }
​
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"onStop");
    }
​
    @Override
    protected 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计数进行判断即可知道是否在前台。

3.4 推荐使用*

可以在onStart中判断应用是否需要更新。或者实现其他需求

可以在onStop中直接调用getRunningAppProcess或者getRunningTask判断应用是不是跑后台去了,这个时候可以做一些事情。然后从后台回到前台调用onCreate,又可以做一些事情。

代码

1.在应用进入后台的时候开启一个前台服务

2.统计activity的数量

3.记录当前activity的实例

4.用变量记录是否处于后台状态

public class MyApp extends Application {
​
    public static Context INSTANCE;
    private Application.ActivityLifecycleCallbacks mCallback;
    private int mActivityCount = 0;
    //是否进入后台
    private boolean isBackground = false;
    private Activity curActivity;//当前activity
    private Intent mService;
​
    @Override
    public void onCreate() {
        super.onCreate();
​
        INSTANCE = this;
​
        mCallback = new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
                Log.i("zxd", "onActivityCreated: ");
            }
​
            @Override
            public void onActivityStarted(@NonNull Activity activity) {
                mActivityCount++;
                curActivity = activity;
                if (mService != null)
                    stopService(mService);
                if (mIsOnBackground) { //从后台回到前台
                    //设置接口 处理
                    //mOnBackgroundCallback.handleOnForeground(activity);
                    //重置后台标识
                    mIsOnBackground = false;
                }
            }
​
            @Override
            public void onActivityResumed(@NonNull Activity activity) {
                Log.i("zxd", "onActivityResumed: ");
            }
​
            @Override
            public void onActivityPaused(@NonNull Activity activity) {
                Log.i("zxd", "onActivityPaused: ");
            }
​
            @Override
            public void onActivityStopped(@NonNull Activity activity) {
                Log.i("zxd", "onActivityStopped: ");
                mActivityCount--;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    mService = new Intent(getApplicationContext(), ForeService.class);
                    startForegroundService(mService);
                }
            
                if (liveActivityCount == 0) {//处于后台
                    mOnBackgroundCallback.handleOnBackground(activity);
                    mIsOnBackground = true;
                }
            }
​
            @Override
            public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
                Log.i("zxd", "onActivitySaveInstanceState: ");
            }
​
            @Override
            public void onActivityDestroyed(@NonNull Activity activity) {
                Log.i("zxd", "onActivityDestroyed: ");
                if (mService != null)
                    stopService(mService);
            }
        };
        registerActivityLifecycleCallbacks(mCallback);
    }
    
    //压后台的回调
    private OnBackgroundCallback mOnBackgroundCallback = null;
​
    /**
     * 压后台的回调处理
     */
    interface OnBackgroundCallback {
        //压后台时触发
        void handleOnBackground(Activity activity);
​
        //从后台回到前台时触发
        void handleOnForeground(Activity activity);
    }
​
    public void setOnBackgroundCallback(OnBackgroundCallback onBackgroundCallback) {
        mOnBackgroundCallback = onBackgroundCallback;
    }
}

4. UsageStatsManager

4.1 原理

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

必须满足以下要求。

使用前提

  1. 此方法只在android5.0以上有效

  2. AndroidManifest中加入此权限

    <uses-permission  android:name="android.permission.PACKAGE_USAGE_STATS" />
  1. 打开手机设置,点击安全-高级,在有权查看使用情况的应用中,为这个App打上勾img

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;
​
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return 0; // 根据需要返回不同的语义值
    }
​
    /**
     * 重载辅助功能事件回调函数,对窗口状态变化事件进行处理
     * @param event
     */
    @Override
    public 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());
        }
    }
​
    @Override
    public void onInterrupt() {
    }
​
    @Override
    protected  void onServiceConnected() {
        super.onServiceConnected();
    }
​
}

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

创建res/xml/detection_service_config.xml

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

(3)注册Detection service到AndroidManifest.xml

在AndroidManifest.xml中添加

<service
    android:name="your_package.DetectionService"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
​
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService"/>
    </intent-filter>
​
    <meta-data
        android: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 {
            // 执行辅助功能服务相关操作
        }
    }

效果如下:

img

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处于前台还是后台的方案

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

如何判断Android应用置于前后台

猜你喜欢

转载自blog.csdn.net/fromVillageCoolBoy/article/details/130523341