如何在Lollipop中获取顶部活动名称或获取当前运行的应用程序包名称?

31
我正在创建一个应用锁程序。如何在Android5.0(棒棒糖)中获取当前正在运行的任务?因为在该版本中,getRunningTaskinfo方法已经被弃用,那么如何解决这个问题?

您可以使用UsageStatsManager查询事件(而不是使用统计信息),但根据文档,最后几分钟(实际上是几秒钟)被切断,以防止非系统应用程序知道当前正在运行的应用程序。 - David Corsalini
9个回答

45

试试这个:

ActivityManager mActivityManager =(ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);

if(Build.VERSION.SDK_INT > 20){
String mPackageName = mActivityManager.getRunningAppProcesses().get(0).processName;
}
else{
  String mpackageName = mActivityManager.getRunningTasks(1).get(0).topActivity.getPackageName();
}

我们可以使用UsageStats来获取:

public static String getTopAppName(Context context) {
    ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    String strName = "";
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            strName = getLollipopFGAppPackageName(context);
        } else {
            strName = mActivityManager.getRunningTasks(1).get(0).topActivity.getClassName();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return strName;
}


private static String getLollipopFGAppPackageName(Context ctx) {

    try {
        UsageStatsManager usageStatsManager = (UsageStatsManager) ctx.getSystemService("usagestats");
        long milliSecs = 60 * 1000;
        Date date = new Date();
        List<UsageStats> queryUsageStats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, date.getTime() - milliSecs, date.getTime());
        if (queryUsageStats.size() > 0) {
            Log.i("LPU", "queryUsageStats size: " + queryUsageStats.size());
        }
        long recentTime = 0;
        String recentPkg = "";
        for (int i = 0; i < queryUsageStats.size(); i++) {
            UsageStats stats = queryUsageStats.get(i);
            if (i == 0 && !"org.pervacio.pvadiag".equals(stats.getPackageName())) {
                Log.i("LPU", "PackageName: " + stats.getPackageName() + " " + stats.getLastTimeStamp());
            }
            if (stats.getLastTimeStamp() > recentTime) {
                recentTime = stats.getLastTimeStamp();
                recentPkg = stats.getPackageName();
            }
        }
        return recentPkg;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "";
}

//启用USAGE_STATS

    // Declare USAGE_STATS permisssion in manifest

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


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

这将返回进程名称而不是包名称。它们有时是不同的。 - Sam
1
我认为它返回应用程序的基本包名称。 - WenChao
@Sam,我遇到了你在第一条评论中提到的困难。在21以上的版本中,名称与上面的包名称不同。有什么解决方法吗? - Tejas Pandya
@Tej,请参考https://dev59.com/tm865IYBdhLWcg3wUs7z,里面提供了多种选项。但简单的答案是,Android不支持这样做,你不应该这样做。 - Sam
@Tej,哦,看起来我在我的第二条评论中找到了一个解决方法。 - Sam
显示剩余2条评论

12

获取API 21或更高版本的正在运行的应用程序的最佳解决方案如下,请尝试使用。这对我起作用了。

private String retriveNewApp() {
    if (VERSION.SDK_INT >= 21) {
        String currentApp = null;
        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<>();
            for (UsageStats usageStats : applist) {
                mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
            }
            if (mySortedMap != null && !mySortedMap.isEmpty()) {
                currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
            }
        }
        Log.e(TAG, "Current App in foreground is: " + currentApp);

        return currentApp;

    }
    else {

        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        String mm=(manager.getRunningTasks(1).get(0)).topActivity.getPackageName();
        Log.e(TAG, "Current App in foreground is: " + mm);
        return mm;
    }
}

关于 SDK_INT >=21 的情况,哪些应用程序的进程仍然在运行? - android developer
public static final String USAGE_STATS_SERVICE 自 API 级别 22 中添加 与 getSystemService(Class) 一起使用,以检索 UsageStatsManager 以查询设备使用情况统计信息。 - Ahmad Shahwaiz
我使用了上面的代码,但在我的情况下,applist始终为空。Android版本为5.1,minSdkVersion 19,compileSdkVersion 28,targetSdkVersion 28 ... 请问如何解决这个问题? - Bhavesh Moradiya
1
Android.provider.Settings.ACTION_USAGE_ACCESS_SETTINGS的运行时权限问题已解决。 - Bhavesh Moradiya
1
在 Android 10(Nokia)中不正常工作! - Javad Shirkhani

8
您可以使用辅助功能服务获取当前正在运行的应用程序。辅助功能服务提供了onAccessibilityEvent事件。
以下是一些示例代码。
@Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            if (event.getPackageName() != null && event.getClassName() != null) {
                Log.d("Foreground App", event.getPackageName().toString());  

            }
        }
    }

您可以在这里找到更多有关无障碍服务的信息。

很遗憾,这可能是不正确的。例如,在前台应用程序出现后,启动器应用程序可能会突然触发。 - Vlad

7
根据这里的内容,以下代码对我非常有效: MOVE_TO_FOREGROUNDMOVE_TO_BACKGROUND在sdk 21中添加,在sdk 29中弃用。 ACTIVITY_RESUMEDACTIVITY_PAUSED在sdk 29中添加。
public static String getTopPkgName(Context context) {
    String pkgName = null;

    UsageStatsManager usageStatsManager = (UsageStatsManager) context
            .getSystemService(Context.USAGE_STATS_SERVICE);

    final long timeTnterval= 1000 * 600;
    final long endTime = System.currentTimeMillis();
    final long beginTime = endTime - timeTnterval;
    final UsageEvents myUsageEvents = usageStatsManager .queryEvents(beginTime , endTime );
    while (myUsageEvents .hasNextEvent()) {
        UsageEvents.Event myEvent = new UsageEvents.Event();
        myUsageEvents .getNextEvent(myEvent );
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            switch (myEvent .getEventType()) {
                case UsageEvents.Event.ACTIVITY_RESUMED:
                    pkgName = myEvent .getPackageName();
                    break;
                case UsageEvents.Event.ACTIVITY_PAUSED:
                    if (myEvent .getPackageName().equals(pkgName )) {
                        pkgName = null;
                    }
            }
        }else {
            switch (event.getEventType()) {
                case UsageEvents.Event.MOVE_TO_FOREGROUND:
                    pkgName = myEvent .getPackageName();
                    break;
                case UsageEvents.Event.MOVE_TO_BACKGROUND:
                    if (myEvent .getPackageName().equals(pkgName )) {
                        pkgName = null;
                    }
            }
        }
    }

    return pkgName ;
}

1
非常感谢您的建议。对我来说可靠地工作。 - priojeet priyom
不用谢,祝你好运。 - Javad Shirkhani

5

2

试试这个...这对我有用。

ActivityManager activityManager = (ActivityManager) getSystemService (Context.ACTIVITY_SERVICE);

if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP)    
{

String packageName = activityManager.getRunningAppProcesses().get(0).processName;
} 
else if(Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP)
{
String packageName =  ProcessManager.getRunningForegroundApps(getApplicationContext()).get(0).getPackageName();

}
else
{
String packageName = activityManager.getRunningTasks(1).get(0).topActivity.getPackageName();
}

6
ProcessManager 是什么? - Nitesh Khosla
请注意,.processName 并不总是进程的包名称。 - Sam
3
我刚刚进行了一次快速的谷歌搜索,ProcessManager 似乎是 AndroidProcesses 库中一个已弃用的类。 - Sam

1

0

可以像这样实现......

ActivityManager am =(ActivityManager)context.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
ActivityManager.RunningTaskInfo task = tasks.get(0); // current task
ComponentName rootActivity = task.baseActivity;


rootActivity.getPackageName();//*currently active applications package name*

对于棒棒糖:

ActivityManager mActivityManager =(ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);

if(Build.VERSION.SDK_INT > 20){
    String activityName =mActivityManager.getRunningAppProcesses().get(0).processName;
 }
else{
 String activityName =   mActivityManager.getRunningTasks(1).get(0).topActivity.getPackageName();
}

完整示例,请查看这里...


getRunningAppProcesses 带有一个警告,只有在调试模式下使用它。 - Clocker

0

有三种方法可以获取屏幕上当前活动的名称。

通过UsageStatsManager

这种方式需要用户的额外权限,并且用户需要手动为您的应用启用此权限。

您需要首先在清单中添加权限。

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

然后使用以下代码将用户重定向以启用此权限:
val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
startActivity(intent)

在添加了该权限之后,您可以使用以下代码获取当前活动名称:
var lastActivityName: String? = null
private fun getCurrentActivityName(): String? {
    val usageStatsManager =
        applicationContext.getSystemService(USAGE_STATS_SERVICE) as UsageStatsManager
    try {
        val currentTimeMillis = System.currentTimeMillis()
        var activityName: String? = null
        var packageName: String? = null
        val queryEvents = usageStatsManager.queryEvents(
            currentTimeMillis - 60000.toLong(),
            currentTimeMillis
        )
        while (queryEvents.hasNextEvent()) {
            val event = UsageEvents.Event()
            queryEvents.getNextEvent(event)
            if (event.eventType == 1) {
                packageName = event.packageName
                activityName = event.className
            }
            else if (event.eventType == 2) {
                if (event.packageName != packageName) {
                    break
                }
                packageName = null
            }
        }
        return if (packageName != null && activityName != null) {
            lastActivityName = activityName
            activityName
        } else lastActivityName
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return null
}

我添加了lastActivityName,因为有时候在同一个活动中可能会得到null,所以你可以将其移除。

通过辅助功能

这可以通过辅助功能服务实现。 但是自从Android 11之后,如果没有添加特殊权限,就无法获取其他包名的列表。

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

<queries>
    <intent>
        <action android:name="android.intent.action.MAIN" />
    </intent>
</queries>

但问题是,如果你没有一个好的解释,GooglePlay可能会拒绝你的应用。
我首先会向你展示如何通过这个特殊权限获取当前活动的名称,然后再添加一个技巧,即如何在只使用辅助功能服务的情况下获取活动,而不需要添加此权限。
要设置辅助功能服务,你需要将以下内容添加到res/xml/accessibility_setting.xml(文件名可以是任何名称)。
<?xml version="1.0" encoding="utf-8"?>

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
   android:accessibilityFeedbackType="feedbackGeneric"
   android:accessibilityFlags="flagDefault|flagReportViewIds|flagIncludeNotImportantViews"
   android:accessibilityEventTypes="typeWindowStateChanged"
   android:canRequestFilterKeyEvents="true"
   android:canRetrieveWindowContent="true" />

然后,您需要像下面这样将您的辅助功能服务添加到Manifest.xml中:
<service
     android:name=".services.MyAccessibilityService"
     android:label="@string/app_name"
     android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
     android:exported="true">

        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService" />
        </intent-filter>

        <meta-data
            android:name="android.accessibilityservice"
            android:resource="@xml/accessibility_setting" />
</service>

接下来我们需要定义我们的MyAccessibilityService服务类和用于获取当前活动名称的函数:
应该是这样的:
class MyAccessibilityService : AccessibilityService() {

   var currentActivityName : String? = null

   override fun onServiceConnected() {
       //Define your initial configs here
   }

   @Synchronized
   override fun onAccessibilityEvent(event: AccessibilityEvent) {
       checkForCurrentActivity(event)
   }

   override fun onInterrupt() {
       // define your interruption code here
   }

然后checkForCurrentActivity(event)将是:
private fun checkForCurrentActivity(event: AccessibilityEvent) {
    if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
        if (event.packageName != null && event.className != null) {
            val componentName = ComponentName(
                event.packageName.toString(),
                event.className.toString()
            )
            if (isActivity(componentName)) {
                val componentFlattenName = componentName.flattenToShortString()
                currentActivityName = componentFlattenName.replace("/.", ".")
                if (currentActivityName.contains("/")) {
                    currentActivityName = currentActivityName.split("/".toRegex())[1]
                }
            }
        }
    }
}

private fun isActivity(componentName: ComponentName): Boolean {
    return try {
        packageManager.getActivityInfo(componentName, 0)
    } catch (e: PackageManager.NameNotFoundException) {
        null
    } != null
}

最后,您可以通过使用currentActivityName轻松获得当前活动的名称。

通过无需额外权限的辅助功能

最后,我将向您展示一种仅通过辅助功能权限而不使用QUERY_ALL_PACKAGES的方法来获取当前活动的名称。

这个解决方案基于第二个解决方案,但有一个小区别。对于这个hack,您需要用以下内容替换isActivity(componentName)

private fun isActivity(componentName: ComponentName):Boolean {
    val name = componentName.flattenToShortString()
    return name.contains("/") &&
        !name.contains("com.android.systemui") &&
        !name.contains("Layout") &&
        !name.contains(".widget") &&
        !name.contains("android.view") &&
        !name.contains("android.material") &&
        !name.contains("android.inputmethodservice") &&
        !name.contains("$") &&
        !name.contains("android.view") &&
        !name.contains("android.app.dialog")
}

而且就像以前一样,您可以通过使用currentActivityName轻松获取当前活动名称。
这个技巧几乎有99%的准确率。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接