Determinar o aplicativo de primeiro plano atual a partir de uma tarefa ou serviço em segundo plano

112

Desejo ter um aplicativo que seja executado em segundo plano, que saiba quando qualquer um dos aplicativos integrados (mensagens, contatos, etc) está em execução.

Então, minhas perguntas são:

  1. Como devo executar meu aplicativo em segundo plano.

  2. Como meu aplicativo em segundo plano pode saber o que é o aplicativo atualmente em execução em primeiro plano.

As respostas de pessoas com experiência seriam muito apreciadas.

dhaiwat
fonte
Não acho que você tenha dado uma explicação adequada sobre o que está tentando fazer. O que seu aplicativo de segundo plano está tentando fazer? De que forma ele deve ser capaz de interagir com o usuário? Por que você precisa saber qual é o aplicativo de primeiro plano atual? Etc.
Charles Duffy
1
para detectar o aplicativo em primeiro plano, você pode usar github.com/ricvalerio/foregroundappchecker
rvalerio

Respostas:

104

Com relação a "2. Como meu aplicativo em segundo plano pode saber o que é o aplicativo atualmente em execução em primeiro plano."

NÃO use o getRunningAppProcesses()método, pois isso retorna todos os tipos de lixo de sistema da minha experiência e você obterá vários resultados RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Use ao getRunningTasks()invés

Este é o código que uso em meu serviço para identificar o aplicativo de primeiro plano atual, é realmente fácil:

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

É isso, então você pode acessar facilmente os detalhes do aplicativo / atividade em primeiro plano:

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

Isso requer uma permissão adicional em manifesto de atividade e funciona perfeitamente.

<uses-permission android:name="android.permission.GET_TASKS" />
Oliver Pearmain
fonte
1
Essa deve ser marcada como a resposta correta. Permissão necessária: android.permission.GET_TASKS é a única desvantagem muito pequena para outros métodos que podem evitá-lo.
brandall
3
EDITAR PARA O ACIMA: Observação: este método destina-se apenas a depurar e apresentar interfaces de usuário de gerenciamento de tarefas. <- Acabei de ver isso no documento Java. Portanto, esse método pode mudar sem aviso no futuro.
brandall
47
Observação: getRunningTasks()está obsoleto na API 21 (Lollipop) - developer.android.com/reference/android/app/…
dtyler
@dtyler, OK então. Existe algum outro método? Embora o Google não queira que aplicativos de terceiros obtenham informações confidenciais, eu tenho a chave de plataforma do dispositivo. Existe algum outro método para fazer o mesmo se eu tiver privilégios de sistema?
Yeung
Há uma maneira de usar a "API de estatísticas de uso" introduzida em 21
KunalK
38

Tive que descobrir a solução certa da maneira mais difícil. o código abaixo é parte do cyanogenmod7 (os ajustes do tablet) e é testado no Android 2.3.3 / gingerbread.

métodos:

  • getForegroundApp - retorna o aplicativo em primeiro plano.
  • getActivityForApp - retorna a atividade do aplicativo encontrado.
  • isStillActive - determina se um aplicativo encontrado anteriormente ainda é o aplicativo ativo.
  • isRunningService - uma função auxiliar para getForegroundApp

espero que isso responda a este problema em todas as extensões (:

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;
}
Sven Dawitz
fonte
1
se você não se importa que eu pergunte, por que você precisa dos métodos para ver se ele está executando um serviço, se ainda está ativo e para obter atividade apenas para obter o aplicativo em primeiro plano?
2
desculpe, mas não está funcionando. Alguns aplicativos são filtrados sem motivo, acho que é quando rodam algum serviço. Portanto, isRunningService os está expulsando.
setembro de
15
Infelizmente, getRunningTasks () está obsoleto desde o Android L (API 20). A partir de L, esse método não está mais disponível para aplicativos de terceiros: a introdução de dados recentes centrados em documento significa que ele pode vazar informações pessoais para o chamador. Para compatibilidade com versões anteriores, ele ainda retornará um pequeno subconjunto de seus dados: pelo menos as próprias tarefas do chamador e, possivelmente, algumas outras tarefas, como home, que são conhecidas por não serem confidenciais.
Sam Lu
Alguém sabe como essa abordagem é melhor do que simplesmente fazer mActivityManager.getRunningTasks(1).get(0).topActivity?
Sam
35

Experimente o seguinte código:

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

O nome do processo é o nome do pacote do aplicativo em execução em primeiro plano. Compare-o com o nome do pacote do seu aplicativo. Se for o mesmo, seu aplicativo está sendo executado em primeiro plano.

Espero que isso responda sua pergunta.

Friedrich Bhaer
fonte
7
A partir da versão L do Android, esta é a única solução que funciona, pois os métodos usados ​​nas outras soluções tornaram-se obsoletos.
androidGuy
4
no entanto, isso não funciona caso o aplicativo esteja em primeiro plano e a tela esteja bloqueada
Krit
A resposta correta deve ser marcada
A_rmas
1
Ele precisa de permissão adicional, como resposta aceita?
wonsuc
1
Isso não está totalmente correto. O nome de um processo nem sempre é igual ao nome do pacote do aplicativo correspondente.
Sam
25

De pirulito em diante, isso mudou. Encontre o código abaixo, antes que o usuário acesse Configurações -> Segurança -> (Role para baixo até o último) Aplicativos com acesso de uso -> Conceda as permissões para nosso aplicativo

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);
}
Suman
fonte
1
essas estatísticas de uso não podem ser definidas de forma programática?
rubmz
@rubmz Você tem alguma solução para isso?
VVB
1
no meu doogee com Android 5.1 não há opção "Dar as permissões para nosso aplicativo"
djdance
Isso não produz resultados 100% precisos ... está certo na maioria das vezes
CamHart
1
Isso não mostrará o último aplicativo em primeiro plano, mas talvez o último "usado" de alguma forma - se você sair de uma atividade e for para outra, puxar o menu inicializador de cima e soltá-lo, você estará no " atividade errada "até que você vá para outra. Não importa se você rola a tela ou não, ele reporta a atividade errada até que você inicie outro.
Azurlake
9

Para casos em que precisamos verificar em nosso próprio serviço / thread de segundo plano se nosso aplicativo está em primeiro plano ou não. É assim que eu implementei, e funciona bem para mim:

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
}

Agora, para verificar em outras classes, se o aplicativo está em primeiro plano ou não, basta chamar:

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

Atualização (conforme apontado por SHS ):

Não se esqueça de se registrar para os callbacks no onCreatemétodo de sua classe de aplicativo .

@Override
public void onCreate() {
    ...
    registerActivityLifecycleCallbacks(this);
}
Sarthak Mittal
fonte
2
É isso que procuro desde as últimas duas horas. Obrigado! :)
waseefakhtar
1
Precisamos chamar "registerActivityLifecycleCallbacks (this);" dentro do método Override "onCreate ()" da classe Application (TestApplication). Isso iniciará os callbacks em nossa classe de aplicativo.
SHS
@SarthakMittal, se um dispositivo entrar em modo de espera ou quando for bloqueado, então onActivityStopped () será chamado. Com base em seu código, isso indicará que o aplicativo está em segundo plano. Mas está em primeiro plano, na verdade.
Ayaz Alifov
@AyazAlifov Se o telefone estiver em espera ou bloqueado, não o considerarei em primeiro plano, mas novamente depende da preferência.
Sarthak Mittal
8

Para determinar o aplicativo de primeiro plano, você pode usar para detectar o aplicativo de primeiro plano, você pode usar https://github.com/ricvalerio/foregroundappchecker . Ele usa métodos diferentes dependendo da versão do Android do dispositivo.

Quanto ao serviço, o repo também fornece o código de que você precisa. Essencialmente, deixe o android studio criar o serviço para você e, em seguida, onCreate adicione o trecho que usa o appChecker. Você precisará solicitar permissão no entanto.

rvalerio
fonte
perfeito!! testado no Android 7
Pratik Tank
Muito obrigado. Esta é a única resposta que resolveu o problema que estávamos enfrentando com as notificações de aplicativos. Quando uma notificação é exibida, todas as outras respostas fornecerão o nome do pacote do aplicativo que enviou a notificação. Seu LollipopDetector resolveu esse problema. Uma dica: a partir da API 29, MOVE_TO_FOREGROUND está obsoleto em UsageEvents.Event, portanto, a partir do Android Q, deve-se usar ACTIVITY_RESUMED. Então vai funcionar como um encanto!
Jorn Rigter
8

Levando em consideração que getRunningTasks()está obsoleto e getRunningAppProcesses()não é confiável, tomei a decisão de combinar 2 abordagens mencionadas no StackOverflow:

   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();
        }
    }
Ayaz Alifov
fonte
4
você precisa mencionar para adicionar ao manifesto a permissão obsoleta <uses-permission android: name = "android.permission.GET_TASKS" /> caso contrário, o aplicativo irá travar
RJFares
1
android.permission.GET_TASKS - atualmente proibido na Play Store
Duna
6

A classe ActivityManager é a ferramenta apropriada para ver quais processos estão em execução.

Para executar em segundo plano, normalmente você deseja usar um serviço .

Charles Duffy
fonte
1

Isso funcionou para mim. Mas fornece apenas o nome do menu principal. Ou seja, se o usuário abriu Configurações -> Bluetooth -> tela Nome do dispositivo, RunningAppProcessInfo o chama apenas de Configurações. Não é capaz de perfurar furthur

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());
                    }               
                }
NakulSargur
fonte
4
Acho que isso funciona apenas se seu próprio aplicativo estiver sendo executado em primeiro plano. Ele não relata nada quando algum outro aplicativo está em primeiro plano.
Alice Van Der Land
0

Faça algo assim:

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;
}
ChristianS
fonte
O Google provavelmente rejeitará um aplicativo que usa ActivityManager.getRunningTasks (). A documentação afirma que é apenas para fins de desenvolvimento : developer.android.com/reference/android/app/… Veja o comentário inicial: stackoverflow.com/questions/4414171/…
ForceMagic
3
@ForceMagic - o Google não rejeita aplicativos simplesmente por usar APIs não confiáveis ; além disso, o Google não é o único canal de distribuição de aplicativos Android. Dito isso, é importante ter em mente que as informações desta API não são confiáveis.
Chris Stratton
@ChrisStratton Interessante, mas você provavelmente está certo, embora seja desencorajado usá-lo para a lógica principal, eles não rejeitarão o aplicativo. Você pode querer avisar Sky Kelsey pelo link de comentário.
ForceMagic
3
"getRunningTasks" funciona para api 21 e além, então pode ser uma boa ideia descobrir outra maneira de fazer funcionar para LOLLIPOP (eu mesmo não descobri isso sozinho :()
amIT
0

Em pirulito e para cima:

Adicionar ao festival principal:

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

E faça algo assim:

if( mTaskId < 0 )
{
    List<AppTask> tasks = mActivityManager.getAppTasks(); 
    if( tasks.size() > 0 )
        mTaskId = tasks.get( 0 ).getTaskInfo().id;
}
rubmz
fonte
4
O uso do marshmallow foi
suspenso
0

É assim que estou verificando se meu aplicativo está em primeiro plano. Note que estou usando AsyncTask conforme sugerido pelo Android oficial documentação .

`

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

e execute AsyncTask assim. new CheckIfForeground().execute();

Developine
fonte
Você tem um link para isso?
user1743524
0

Combinei duas soluções em um método e funciona para mim para API 24 e API 21. Outras que não testei.

O código em 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()
}

e em manifesto

<!-- Check whether app in background or foreground -->
<uses-permission android:name="android.permission.GET_TASKS" />
Denshov
fonte