从后台任务或服务中确定当前前台应用程序

Posted

技术标签:

【中文标题】从后台任务或服务中确定当前前台应用程序【英文标题】:Determining the current foreground application from a background task or service 【发布时间】:2011-01-11 03:18:29 【问题描述】:

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

所以我的问题是:

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

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

非常感谢有经验的人的回应。

【问题讨论】:

我认为你没有对你想要做什么给出充分的解释。 什么您的后台应用程序试图做什么?它应该以何种方式与用户交互? 为什么你需要知道当前前台应用是什么?等等。 codingaffairs.blogspot.com/2016/05/… 检测前台应用,可以使用github.com/ricvalerio/foregroundappchecker 【参考方案1】:

关于“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" />

【讨论】:

这应该被标记为正确答案。所需权限:android.permission.GET_TASKS 是其他可以避免它的方法的唯一非常小的缺点。 以上内容编辑:注意:此方法仅用于调试和呈现任务管理用户界面。 注意:getRunningTasks() 在 API 21 (Lollipop) 中已弃用 - developer.android.com/reference/android/app/… @dtyler,那好吧。有没有其他方法代替?虽然谷歌不希望第三方应用获取敏感信息,但我有设备的平台密钥。如果我确实有系统权限,还有其他方法可以做到吗? 有一种方法使用21中引入的“Usage statistics api”【参考方案2】:

我必须以艰难的方式找出正确的解决方案。下面的代码是 cyanogenmod7(平板电脑调整)的一部分,并在 android 2.3.3 / 姜饼上进行了测试。

方法:

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;

【讨论】:

如果您不介意我问,为什么您需要这些方法来查看它是否正在运行服务、是否仍然处于活动状态以及获取活动只是为了获取前台应用程序? 抱歉,它不起作用。有些应用程序被无故过滤,我认为是在它们运行某些服务时。所以 isRunningService 正在把他们踢出去。 不幸的是,自 Android L(API 20) 以来,getRunningTasks() 已被弃用。从 L 开始,这种方法不再适用于第三方应用程序:引入以文档为中心的最新消息意味着它可以将个人信息泄露给调用者。为了向后兼容,它仍然会返回一小部分数据:至少是调用者自己的任务,可能还有其他一些已知不敏感的任务,例如 home。 有谁知道这种方法比简单地做mActivityManager.getRunningTasks(1).get(0).topActivity更好吗?【参考方案3】:

试试下面的代码:

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);
    

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

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

【讨论】:

从 Android 版本 L 开始,这是唯一可行的解​​决方案,因为其他解决方案中使用的方法已被弃用。 但是,如果应用程序处于前台并且屏幕被锁定,这将不起作用 newContext 应该是什么变量? 是否需要额外的权限,例如接受的答案? 这不太正确。进程的名称并不总是与相应的应用程序包名称相同。【参考方案4】:

从棒棒糖开始,这种情况发生了变化。请在该用户必须转到设置 -> 安全 ->(向下滚动到最后一个)具有使用权限的应用程序之前找到下面的代码 -> 授予我们应用程序的权限

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);

【讨论】:

不能以编程方式设置此使用统计信息吗? @rubmz 你对此有什么解决方案吗? 在我的 android 5.1 的 doogee 中没有选项“授予我们的应用程序权限” 这不会产生 100% 准确的结果...大多数时候都是正确的 这不会在前台显示最后一个应用程序,但可能是以某种方式显示最后一个“使用”的应用程序 - 如果您离开一个活动并转到另一个活动,请从上方拉启动器菜单,然后释放它,你将处于“错误”的活动中,直到你去另一个活动。不管你是否滚动屏幕,它都会报告错误的活动,直到你启动另一个活动。【参考方案5】:

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

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);

【讨论】:

这就是我过去两个小时以来一直在寻找的东西。谢谢! :) 我们需要调用“registerActivityLifecycleCallbacks(this);”在应用程序类(TestApplication)的覆盖方法“onCreate()”中。这将在我们的应用程序类中启动回调。 @SarthakMittal,如果设备进入待机模式或我们锁定它,则会调用 onActivityStopped()。根据您的代码,这将表明应用程序处于后台。但它实际上是在前台。 @AyazAlifov 如果手机处于待机状态或手机被锁定,我不会认为它在前台,但这取决于偏好。【参考方案6】:

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

至于服务,repo 也提供了你需要的代码。本质上,让android studio为你创建服务,然后onCreate添加使用appChecker的sn-p。但是,您需要申请许可。

【讨论】:

完美!!在安卓 7 上测试 非常感谢。这是解决我们在应用程序通知方面面临的问题的唯一答案。当弹出通知时,所有其他答案将返回发送通知的应用程序的包名称。您的 LollipopDetector 解决了这个问题。一个提示:从 API 29 开始,MOVE_TO_FOREGROUND 在 UsageEvents.Event 中被弃用,因此从 Android Q 开始应该使用 ACTIVITY_RESUMED。然后它会像魅力一样发挥作用!【参考方案7】:

考虑到 getRunningTasks() 已弃用且 getRunningAppProcesses() 不可靠,我决定将 *** 中提到的两种方法结合起来:

   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();
        
    

【讨论】:

您需要提及将已弃用的权限添加到清单中 否则应用程序将崩溃 android.permission.GET_TASKS - 目前在 Play 商店中被禁止【参考方案8】:

ActivityManager 类是查看哪些进程正在运行的合适工具。

要在后台运行,您通常需要使用Service。

【讨论】:

【参考方案9】:

这对我有用。但它只给出主菜单名称。也就是说,如果用户打开了设置 --> 蓝牙 --> 设备名称屏幕,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());
                                   
                

【讨论】:

我认为这只有在您自己的应用程序在前台运行时才有效。当其他应用程序在前台时,它不会报告任何内容。【参考方案10】:

一个简单的解决方案是使用 LiveData。 创建一个单例 LiveData 变量。 (可能在一个普通的 Kotlin 文件中)。

val foregroundHelper = MutableLiveData<Unit>()

从 Activity 或 Fragment 观察:

foregroundHelper.observe(this, Observer ) // for Activity
foregroundHelper.observe(viewLifecycleOwner, Observer ) // for Fragments

现在来自您的后台服务、广播接收器等:

val appIsVisibleToTheUser = foregroundHelper.hasActiveObservers()
// Now your logic goes.
if (!appIsVisibleToUser) 
   // App is in background
   // So In my case:
   // I'm showing Notification to the user for the error happened in Background Service.

谢谢。

【讨论】:

【参考方案11】:

做这样的事情:

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() 的应用。该文档指出它仅用于开发目的:developer.android.com/reference/android/app/… 请参阅初始评论:***.com/questions/4414171/… @ForceMagic - Google 不会仅仅因为使用 不可靠 API 而拒绝应用;此外,Google 并不是 Android 应用程序的唯一分发渠道。也就是说,值得牢记的是,来自此 API 的信息并不可靠。 @ChrisStratton 很有趣,但您可能是对的,尽管不鼓励将其用于核心逻辑,但他们不会拒绝该应用程序。您可能想通过评论链接警告 Sky Kelsey。 "getRunningTasks" 确实适用于 api 21 及更高版本,因此想出另一种方法使其适用于 LOLLIPOP 可能是个好主意(虽然我自己还没有想出这个:()【参考方案12】:

在棒棒糖及以上:

添加到 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;

【讨论】:

不推荐使用棉花糖【参考方案13】:

这就是我检查我的应用程序是否在前台的方式。请注意,我正在使用 AsyncTask,正如官方 Android 文档所建议的那样。`

`

    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();

【讨论】:

你有这个链接吗?【参考方案14】:

我在一种方法中结合了两种解决方案,它适用于 API 24 和 API 21。其他的我没有测试。

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" />

【讨论】:

以上是关于从后台任务或服务中确定当前前台应用程序的主要内容,如果未能解决你的问题,请参考以下文章

如何把linux后台任务调到前台

当应用程序进入后台时,在前台执行的长时间运行的任务被挂起

应用进入前台时取消后台任务

如果前台应用程序终止,如何通知后台任务?

Linux进程和任务管理

fg命令