根据后台任务或服务确定当前的前台应用程序


112

我希望有一个在后台运行的应用程序,该应用程序知道何时运行任何内置应用程序(消息,联系人等)。

所以我的问题是:

  1. 我应该如何在后台运行我的应用程序。

  2. 我的后台应用程序如何知道当前在前台运行的应用程序是什么。

有经验的人们的回应将不胜感激。


我认为您没有对要执行的操作给予足够的解释。什么是你的后台应用意欲何为?它应该以什么方式与用户交互?为什么您需要知道当前的前台应用程序是什么?等等
查尔斯·达菲


1
要检测前景应用程序,可以使用github.com/ricvalerio/foregroundappchecker
rvalerio

Answers:


104

关于“ 2.我的后台应用程序如何才能知道当前在前台运行的应用程序是什么”。

不要使用该getRunningAppProcesses()方法,因为根据我的经验,这会返回各种各样的系统垃圾,您将获得多个具有的结果RunningAppProcessInfo.IMPORTANCE_FOREGROUND。使用getRunningTasks()替代

这是我在服务中使用的代码,用于标识当前的前台应用程序,这非常容易:

ActivityManager am = (ActivityManager) AppService.this.getSystemService(ACTIVITY_SERVICE);
// The first in the list of RunningTasks is always the foreground task.
RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);

就是这样,然后您可以轻松访问前台应用程序/活动的详细信息:

String foregroundTaskPackageName = foregroundTaskInfo .topActivity.getPackageName();
PackageManager pm = AppService.this.getPackageManager();
PackageInfo foregroundAppPackageInfo = pm.getPackageInfo(foregroundTaskPackageName, 0);
String foregroundTaskAppName = foregroundAppPackageInfo.applicationInfo.loadLabel(pm).toString();

这需要在活动清单中获得额外的许可,并且运行良好。

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

1
这应该标记为正确答案。所需的权限:android.permission.GET_TASKS是其他可以避免它的方法的唯一非常小的缺点。
brandall 2013年

3
上面的编辑:注意:此方法仅用于调试和显示任务管理用户界面。<-刚刚在Java文档中发现了这一点。因此,此方法将来可能会更改,恕不另行通知。
brandall 2013年

47
注:getRunningTasks()在API弃用21(棒棒堂) - developer.android.com/reference/android/app/...
dtyler

@dtyler,好的。还有其他方法吗?尽管Google不想让第三方应用程序获取敏感信息,但我拥有设备的平台密钥。如果我具有系统特权,是否还有其他方法可以做到这一点?

有一种使用“用法统计API”的方法,它于15
KunalK

38

我不得不用困难的方式找出正确的解决方案。以下代码是cyanogenmod7的一部分(平板电脑进行了调整),并在android 2.3.3 / gingerbread上进行了测试。

方法:

  • getForegroundApp-返回前台应用程序。
  • getActivityForApp-返回找到的应用程序的活动。
  • isStillActive-确定先前找到的应用是否仍然是活动应用。
  • isRunningService-getForegroundApp的辅助功能

希望可以全面解决此问题(:

private RunningAppProcessInfo getForegroundApp() {
    RunningAppProcessInfo result=null, info=null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningAppProcessInfo> l = mActivityManager.getRunningAppProcesses();
    Iterator <RunningAppProcessInfo> i = l.iterator();
    while(i.hasNext()){
        info = i.next();
        if(info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                && !isRunningService(info.processName)){
            result=info;
            break;
        }
    }
    return result;
}

private ComponentName getActivityForApp(RunningAppProcessInfo target){
    ComponentName result=null;
    ActivityManager.RunningTaskInfo info;

    if(target==null)
        return null;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <ActivityManager.RunningTaskInfo> l = mActivityManager.getRunningTasks(9999);
    Iterator <ActivityManager.RunningTaskInfo> i = l.iterator();

    while(i.hasNext()){
        info=i.next();
        if(info.baseActivity.getPackageName().equals(target.processName)){
            result=info.topActivity;
            break;
        }
    }

    return result;
}

private boolean isStillActive(RunningAppProcessInfo process, ComponentName activity)
{
    // activity can be null in cases, where one app starts another. for example, astro
    // starting rock player when a move file was clicked. we dont have an activity then,
    // but the package exits as soon as back is hit. so we can ignore the activity
    // in this case
    if(process==null)
        return false;

    RunningAppProcessInfo currentFg=getForegroundApp();
    ComponentName currentActivity=getActivityForApp(currentFg);

    if(currentFg!=null && currentFg.processName.equals(process.processName) &&
            (activity==null || currentActivity.compareTo(activity)==0))
        return true;

    Slog.i(TAG, "isStillActive returns false - CallerProcess: " + process.processName + " CurrentProcess: "
            + (currentFg==null ? "null" : currentFg.processName) + " CallerActivity:" + (activity==null ? "null" : activity.toString())
            + " CurrentActivity: " + (currentActivity==null ? "null" : currentActivity.toString()));
    return false;
}

private boolean isRunningService(String processname){
    if(processname==null || processname.isEmpty())
        return false;

    RunningServiceInfo service;

    if(mActivityManager==null)
        mActivityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List <RunningServiceInfo> l = mActivityManager.getRunningServices(9999);
    Iterator <RunningServiceInfo> i = l.iterator();
    while(i.hasNext()){
        service = i.next();
        if(service.process.equals(processname))
            return true;
    }

    return false;
}

1
如果您不介意我问,为什么需要一些方法来查看它是否正在运行服务,是否仍处于活动状态以及为了获得前台应用程序而获得活动?

2
对不起,但是它不起作用。有些应用被无故过滤,我认为这是它们运行某些服务的时候。因此,isRunningService正在将他们踢出局。
2012年

15
不幸的是,自Android L(API 20)开始不赞成使用getRunningTasks()。从L开始,此方法不再对第三方应用程序可用:以文档为中心的最新消息的引入意味着它可以将个人信息泄漏给调用者。为了向后兼容,它仍将返回其数据的一小部分:至少调用者自己的任务,以及可能已知的一些其他不敏感的任务,例如home。
2014年

有谁知道这种方法比单纯的方法更好mActivityManager.getRunningTasks(1).get(0).topActivity
山姆

35

尝试以下代码:

ActivityManager activityManager = (ActivityManager) newContext.getSystemService( Context.ACTIVITY_SERVICE );
List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
for(RunningAppProcessInfo appProcess : appProcesses){
    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
        Log.i("Foreground App", appProcess.processName);
    }
}

进程名称是在前台运行的应用程序的程序包名称。将其与应用程序的程序包名称进行比较。如果相同,则您的应用程序在前台运行。

我希望这回答了你的问题。


7
从Android版本L开始,这是唯一不起作用的解决方案,因为其他解决方案中使用的方法已被弃用。
androidGuy 2014年

4
但是,如果应用程序在前景中且屏幕被锁定,则此方法不起作用
Krit 2015年

正确答案应该打勾
A_rmas

1
是否需要其他权限,例如已接受的答案?
wonsuc

1
这不是很正确。进程的名称并不总是与相应的应用程序包名称相同。
山姆

25

从棒棒糖开始,这种情况发生了变化。请先找到以下代码,然后该用户必须进入设置->安全->(向下滚动到最后)具有使用权限的应用->将权限授予我们的应用

private void printForegroundTask() {
    String currentApp = "NULL";
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        UsageStatsManager usm = (UsageStatsManager) this.getSystemService(Context.USAGE_STATS_SERVICE);
        long time = System.currentTimeMillis();
        List<UsageStats> appList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,  time - 1000*1000, time);
        if (appList != null && appList.size() > 0) {
            SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
            for (UsageStats usageStats : appList) {
                mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
            }
            if (mySortedMap != null && !mySortedMap.isEmpty()) {
                currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
            }
        }
    } else {
        ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> tasks = am.getRunningAppProcesses();
        currentApp = tasks.get(0).processName;
    }

    Log.e(TAG, "Current App in foreground is: " + currentApp);
}

1
无法以编程方式设置此使用情况统计信息?
rubmz

@rubmz您对此有任何解决方案吗?
VVB

1
在我使用Android 5.1的doogee中,没有任何选项“将权限授予我们的应用程序”
djdance,2013年

这不会产生100%的准确结果...大多数时候
都是

1
这不会在前台显示最后一个应用程序,但是可能会以某种方式显示最后一个“使用过的”应用程序-如果您离开一个活动并转到另一个活动,请从上方拉启动器菜单,然后释放它,您将位于“错误”活动,直到您转到另一个活动为止。滚动屏幕与否无关紧要,它会报告错误的活动,直到您启动另一个活动为止。
Azurlake

9

对于需要从我们自己的服务/后台线程检查我们的应用程序是否在前台的情况。这是我实施的方式,对我来说很好用:

public class TestApplication extends Application implements Application.ActivityLifecycleCallbacks {

    public static WeakReference<Activity> foregroundActivityRef = null;

    @Override
    public void onActivityStarted(Activity activity) {
        foregroundActivityRef = new WeakReference<>(activity);
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (foregroundActivityRef != null && foregroundActivityRef.get() == activity) {
            foregroundActivityRef = null;
        }
    }

    // IMPLEMENT OTHER CALLBACK METHODS
}

现在要从其他类中检查应用是否在前台,只需调用:

if(TestApplication.foregroundActivityRef!=null){
    // APP IS IN FOREGROUND!
    // We can also get the activity that is currently visible!
}

更新(如SHS所指出):

不要忘记在Application类的onCreate方法中注册回调。

@Override
public void onCreate() {
    ...
    registerActivityLifecycleCallbacks(this);
}

2
这是过去两个小时以来我一直在寻找的东西。谢谢!:)
waseefakhtar

1
我们需要调用“ registerActivityLifecycleCallbacks(this);” 在Application类(TestApplication)的Override方法“ onCreate()”内部。这将在我们的应用程序类中启动回调。
SHS

@SarthakMittal,如果设备进入待机模式或当我们锁定它时,将调用onActivityStopped()。根据您的代码,这将表明该应用程序处于后台。但这实际上是前景。
Ayaz Alifov

@AyazAlifov如果电话处于待机状态或电话被锁定,我不会认为它处于前台状态,但是还是要取决于偏好。
Sarthak米塔尔

8

为了确定前台应用程序,可以使用来检测前台应用程序,可以使用https://github.com/ricvalerio/foregroundappchecker。根据设备的android版本,它使用不同的方法。

至于服务,存储库还提供您需要的代码。本质上,让android studio为您创建服务,然后onCreate添加使用appChecker的代码段。但是,您将需要请求许可。


完善!!在android 7上测试
Pratik Tank

非常感谢。这是解决我们在收到应用通知时面临的问题的唯一答案。弹出通知时,所有其他答案将返回发送通知的应用程序的程序包名称。您的LollipopDetector解决了此问题。一个提示:从API 29开始,UsageEvents.Event中不推荐使用MOVE_TO_FOREGROUND,因此从Android Q开始,应使用ACTIVITY_RESUMED。然后它将像魅力一样工作!
Jorn Rigter,

8

考虑到getRunningTasks()过时和getRunningAppProcesses()不可靠,我决定组合使用StackOverflow中提到的2种方法:

   private boolean isAppInForeground(Context context)
    {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
        {
            ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
            ActivityManager.RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);
            String foregroundTaskPackageName = foregroundTaskInfo.topActivity.getPackageName();

            return foregroundTaskPackageName.toLowerCase().equals(context.getPackageName().toLowerCase());
        }
        else
        {
            ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo();
            ActivityManager.getMyMemoryState(appProcessInfo);
            if (appProcessInfo.importance == IMPORTANCE_FOREGROUND || appProcessInfo.importance == IMPORTANCE_VISIBLE)
            {
                return true;
            }

            KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
            // App is foreground, but screen is locked, so show notification
            return km.inKeyguardRestrictedInputMode();
        }
    }

4
您需要提及将清单中已弃用的权限添加到清单中<uses-permission android:name =“ android.permission.GET_TASKS” />否则应用程序将崩溃
RJFares

1
android.permission.GET_TASKS -目前禁止在Play商店
杜纳


1

这对我有用。但是它仅给出主菜单名称。就是说,如果用户打开了设置->蓝牙->设备名称屏幕,RunningAppProcessInfo便将其称为设置。无法深入研究

ActivityManager activityManager = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE );
                PackageManager pm = context.getPackageManager();
                List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
                for(RunningAppProcessInfo appProcess : appProcesses) {              
                    if(appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                        CharSequence c = pm.getApplicationLabel(pm.getApplicationInfo(appProcess.processName, PackageManager.GET_META_DATA));
                        Log.i("Foreground App", "package: " + appProcess.processName + " App: " + c.toString());
                    }               
                }

4
我认为这仅在您自己的应用程序在前台运行时才有效。当其他一些应用程序出现在前台时,它不会报告任何内容。
爱丽丝·范德兰

0

做这样的事情:

int showLimit = 20;

/* Get all Tasks available (with limit set). */
ActivityManager mgr = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> allTasks = mgr.getRunningTasks(showLimit);
/* Loop through all tasks returned. */
for (ActivityManager.RunningTaskInfo aTask : allTasks) 
{                  
    Log.i("MyApp", "Task: " + aTask.baseActivity.getClassName()); 
    if (aTask.baseActivity.getClassName().equals("com.android.email.activity.MessageList")) 
        running=true;
}

Google可能会拒绝使用ActivityManager.getRunningTasks()的应用程序。文档指出,只有敌人dev的目的:developer.android.com/reference/android/app/... 见INTIAL评论:stackoverflow.com/questions/4414171/...
ForceMagic

3
@ForceMagic-Google不会仅仅因为使用不可靠的 API 而拒绝应用程序;此外,Google并不是Android应用程序的唯一发行渠道。也就是说,值得牢记的是,此API的信息不可靠。
克里斯·斯特拉顿

@ChrisStratton有趣,但您可能是对的,尽管不建议将其用于核心逻辑,他们不会拒绝该应用程序。您可能想从评论链接中警告Sky Kelsey。
ForceMagic 2014年

3
“ getRunningTasks”确实适用于api 21及其以后的版本,因此,想出另一种方法使其适用于LOLLIPOP可能是个好主意(我本人并没有弄清楚这一点:()
amIT 2015年

0

在棒棒糖及以上:

添加到mainfest:

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

并执行以下操作:

if( mTaskId < 0 )
{
    List<AppTask> tasks = mActivityManager.getAppTasks(); 
    if( tasks.size() > 0 )
        mTaskId = tasks.get( 0 ).getTaskInfo().id;
}

4
它已被弃用为棉花糖
jinkal '16

0

这就是我检查应用程序是否处于前台的方式。注意我按照官方Android文档的建议使用AsyncTask。

`

    private class CheckIfForeground extends AsyncTask<Void, Void, Void> {
    @Override
    protected Void doInBackground(Void... voids) {

        ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                Log.i("Foreground App", appProcess.processName);

                if (mContext.getPackageName().equalsIgnoreCase(appProcess.processName)) {
                    Log.i(Constants.TAG, "foreground true:" + appProcess.processName);
                    foreground = true;
                    // close_app();
                }
            }
        }
        Log.d(Constants.TAG, "foreground value:" + foreground);
        if (foreground) {
            foreground = false;
            close_app();
            Log.i(Constants.TAG, "Close App and start Activity:");

        } else {
            //if not foreground
            close_app();
            foreground = false;
            Log.i(Constants.TAG, "Close App");

        }

        return null;
    }
}

并像这样执行AsyncTask。 new CheckIfForeground().execute();


你有这个链接吗?
user1743524 '16

0

我用一种方法组合了两个解决方案,它对我来说适用于API 24和API21。其他我没有测试。

Kotlin中的代码:

private fun isAppInForeground(context: Context): Boolean {
    val appProcessInfo = ActivityManager.RunningAppProcessInfo()
    ActivityManager.getMyMemoryState(appProcessInfo)
    if (appProcessInfo.importance == IMPORTANCE_FOREGROUND ||
            appProcessInfo.importance == IMPORTANCE_VISIBLE) {
        return true
    } else if (appProcessInfo.importance == IMPORTANCE_TOP_SLEEPING ||
            appProcessInfo.importance == IMPORTANCE_BACKGROUND) {
        return false
    }

    val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val foregroundTaskInfo = am.getRunningTasks(1)[0]
    val foregroundTaskPackageName = foregroundTaskInfo.topActivity.packageName
    return foregroundTaskPackageName.toLowerCase() == context.packageName.toLowerCase()
}

并在清单中

<!-- Check whether app in background or foreground -->
<uses-permission android:name="android.permission.GET_TASKS" />
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.