Как проверить, находится ли активность на переднем или на видимом фоне?


104

У меня на таймере есть заставка. Моя проблема в том, что перед finish()тем, как приступить к выполнению своего действия, мне нужно проверить, что следующее действие началось, потому что всплывает системное диалоговое окно, и я только хочу finish(); после того, как пользователь выбрал опцию в диалоговом окне?

Я знаю, что есть много вопросов о том, как узнать, находится ли ваше действие на переднем плане, но я не знаю, позволяет ли это также создавать диалоговые окна поверх действия.

Вот в чем проблема, красный цвет - это моя активность, которая находится в фоновом режиме, а диалог находится на переднем плане:

красный - это моя активность, которая находится на заднем плане, а диалог - на переднем плане

РЕДАКТИРОВАТЬ: Я пытался просто не использовать, finish()но затем моя деятельность может быть возвращена в стек приложений, которых я пытаюсь избежать.


Может быть актуально: stackoverflow.com/questions/4414171/…
jarmod 04

Чтобы уточнить, вы хотите запустить средство выбора намерений и дождаться завершения вашего приложения () до тех пор, пока пользователь не выберет один из вариантов? Похоже, вам нужны Intent.createChooser () и startActivityForResult (), а затем finish () после получения результата.
alanv


ProcessLifecycleOwner - новейшее решение
SR

Ответы:


189

Вот что рекомендуется в качестве правильного решения:

Правильное решение (кредиты принадлежат Дэну, CommonsWare и NeTeInStEiN). Отслеживайте видимость вашего приложения самостоятельно, используя методы Activity.onPause, Activity.onResume. Сохраните статус "видимости" в каком-то другом классе. Хороший выбор - это ваша собственная реализация приложения или службы (есть также несколько вариантов этого решения, если вы хотите проверить видимость активности со стороны службы).

Пример Реализуйте собственный класс приложения (обратите внимание на статический метод isActivityVisible ()):

public class MyApplication extends Application {

  public static boolean isActivityVisible() {
    return activityVisible;
  }  

  public static void activityResumed() {
    activityVisible = true;
  }

  public static void activityPaused() {
    activityVisible = false;
  }

  private static boolean activityVisible;
}

Зарегистрируйте свой класс приложения в AndroidManifest.xml:

<application
    android:name="your.app.package.MyApplication"
    android:icon="@drawable/icon"
    android:label="@string/app_name" >

Добавьте onPause и onResume к каждому действию в проекте (вы можете создать общего предка для своих действий, если хотите, но если ваша активность уже расширена от MapActivity / ListActivity и т. Д., Вам все равно нужно написать следующее вручную) :

@Override
protected void onResume() {
  super.onResume();
  MyApplication.activityResumed();
}

@Override
protected void onPause() {
  super.onPause();
  MyApplication.activityPaused();
}

В своем finish()методе вы хотите использовать, isActivityVisible()чтобы проверить, видно ли действие или нет. Там вы также можете проверить, выбрал ли пользователь вариант или нет. Продолжайте, когда оба условия будут выполнены.

Источник также упоминает два неправильных решения ... так что избегайте этого.

Источник: stackoverflow


Есть один небольшой момент между финишом и началом активности, и мне нужно добавить немного задержки и противодействия
sagus_helgy

28
Это не работает надежно. У вас может быть следующая ситуация: Возобновить A Возобновить B Пауза A. Теперь activityVisible имеет значение false, тогда как приложение отображается. Возможно, вы используете счетчик видимости: visibleCounter ++ в onResume и visibleCounter - в onPause.
Йорис Веймар,

4
Согласен с Йорисом Веймаром, что это не надежное решение. Один из сценариев не является ли пользователь разобрали панель уведомлений, а затем ни onPause, onStop, ни onResumeсобытие называется. Так что же делать, если ни одно из этих событий не сработало ?!

1
Фактически, ни один из других ответов также не работает на 100%.

2
Если в приложении несколько Activity, эта схема работать не будет. Заменить на счетчики хотя бы
ruX

70

Если ориентирован на уровень API 14 или выше, можно использовать android.app.Application.ActivityLifecycleCallbacks

public class MyApplication extends Application implements ActivityLifecycleCallbacks {
    private static boolean isInterestingActivityVisible;

    @Override
    public void onCreate() {
        super.onCreate();

        // Register to be notified of activity state changes
        registerActivityLifecycleCallbacks(this);
        ....
    }

    public boolean isInterestingActivityVisible() {
        return isInterestingActivityVisible;
    }

    @Override
    public void onActivityResumed(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = true;
        }
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = false;
        }
    }

    // Other state change callback stubs
    ....
}

18
Вы можете просто сделать это в обычных обратных вызовах жизненного цикла активности (onResume (), onStop ()), я бы сказал.
Дэниел Уилсон

5
@DanielWilson Я думаю, что дело не в том, чтобы создать систему для выполнения чего-то там, где она уже существует. ИМХО, это должен быть принятый ответ.
Джеффри Блаттман

26

UPD : обновлено до гос Lifecycle.State.RESUMED. Спасибо за это @htafoya .

В 2019 году с помощью новой библиотеки поддержки 28+или AndroidX вы можете просто использовать:

val isActivityInForeground = activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)

Вы можете прочитать больше в документации, чтобы понять, что произошло под капотом.


2
Не очень, наверное лучше разместить activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) или НАЧАТЬ. INITIALIZEDне гарантирует, что он находится на переднем плане.
htafoya 05

11

Activity :: hasWindowFocus () возвращает вам необходимое логическое значение.

public class ActivityForegroundChecker extends TimerTask
{
    private static final long FOREGROUND_CHECK_PERIOD = 5000;
    private static final long FIRST_DELAY             = 3000;

    private Activity m_activity;
    private Timer    m_timer;

    public ActivityForegroundChecker (Activity p_activity)
    {
        m_activity = p_activity;
    }

    @Override
    public void run()
    {
        if (m_activity.hasWindowFocus() == true) {
            // Activity is on foreground
            return;
        }
        // Activity is on background.
    }

    public void start ()
    {
        if (m_timer != null) {
            return;
        }
        m_timer = new Timer();
        m_timer.schedule(this, FIRST_DELAY, FOREGROUND_CHECK_PERIOD);
    }

    public void stop ()
    {
        if (m_timer == null) {
            return;
        }
        m_timer.cancel();
        m_timer.purge();
        m_timer = null;
    }
}

Вот пример класса для проверки видимости ваших действий, где бы вы ни находились.

Помните, что если вы показываете диалог , результат будет ложным, так как диалог будет иметь основной фокус. В остальном это действительно удобно и надежнее предлагаемых решений.


1
Спасибо за изменение ответа @Burak Day, это на самом деле ответ сейчас
Ник,

Это не работает, я бы предпочел использовать логическое свойство в классе, для которого установлено значение true в OnResume и значение false в OnPause ();
Чендлер

@Chandler, какая у вас проблема с этим кодом? И какая версия?
День Бурак,

@Chandler также, что, если у вас нет доступа к методам жизненного цикла активности. Представьте, что вы просто проверяете видимость активности из библиотеки.
День Бурак

Настоящая проблема этого ответа в том, что он НЕ работает, activity.hasWindowFocus имеет значение true, не может гарантировать, что активность находится между состояниями onResume и onPause. Я бы рекомендовал добавить в это действие свойство bool isResumed, вручную установить значение и добавить метод get.
Чендлер

10

Именно разница между onPauseи onStopсобытиями деятельности , как описано в документации о классе активности .

Если я правильно вас понял, вы хотите позвонить finish()из своей деятельности, onStopчтобы прекратить ее. См. Прикрепленное изображение демонстрационного приложения жизненного цикла деятельности . Вот как это выглядит, когда действие B запускается из действия A. Порядок событий - снизу вверх, поэтому вы можете видеть, что действие A onStopвызывается после того, как действие B onResumeуже было вызвано.

Демонстрация жизненного цикла деятельности

В случае отображения диалогового окна, ваша активность в фоновом режиме неактивна и onPauseвызывается только .


7

Два возможных решения:

1) Обратные вызовы Activity LifeCycle

Используйте приложение , реализующее ActivityLifecycleCallbacks и используйте его для отслеживания событий жизненного цикла в вашем приложении. Обратите внимание, что ActivityLifecycleCallbacks предназначены для Android api> = 14. Для предыдущего Android api вам нужно будет реализовать его самостоятельно во всех ваших действиях ;-)

Используйте приложение, когда вам нужно поделиться / сохранить состояния в разных действиях.

2) Проверьте информацию о запущенном процессе

Вы можете проверить статус запущенного процесса с помощью этого класса RunningAppProcessInfo

Получите список запущенных процессов с помощью ActivityManager.getRunningAppProcesses () и отфильтруйте список результатов, чтобы проверить наличие желаемого RunningAppProcessInfo и проверить его «важность»



3

Используйте временной интервал между паузой и возобновлением из фона, чтобы определить, не спит ли он из фона

В пользовательском приложении

private static boolean isInBackground;
private static boolean isAwakeFromBackground;
private static final int backgroundAllowance = 10000;

public static void activityPaused() {
    isInBackground = true;
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            if (isInBackground) {
                isAwakeFromBackground = true;
            }
        }
    }, backgroundAllowance);
    Log.v("activity status", "activityPaused");
}

public static void activityResumed() {
    isInBackground = false;
    if(isAwakeFromBackground){
        // do something when awake from background
        Log.v("activity status", "isAwakeFromBackground");
    }
    isAwakeFromBackground = false;
    Log.v("activity status", "activityResumed");
}

В классе BaseActivity

@Override
protected void onResume() {
  super.onResume();
  MyApplication.activityResumed();
}

@Override
protected void onPause() {
  super.onPause();
  MyApplication.activityPaused();
}

3

Думаю, у меня есть решение получше. Потому что вы можете просто встроить MyApplication.activityResumed (); на каждое действие на одну степень.

Сначала вам нужно создать (например, CyberneticTwerkGuruOrc)

public class MyApplication extends Application {

  public static boolean isActivityVisible() {
    return activityVisible;
  }  

  public static void activityResumed() {
    activityVisible = true;
  }

  public static void activityPaused() {
    activityVisible = false;
  }

  private static boolean activityVisible;
}

Затем вам нужно добавить класс приложения в AndroidManifest.xml

<application
    android:name="your.app.package.MyApplication"
    android:icon="@drawable/icon"
    android:label="@string/app_name" >

Затем создайте класс ActivityBase

public class ActivityBase extends Activity {

    @Override
    protected void onPause() {
        super.onPause();
        MyApplication.activityPaused();
    }

    @Override
    protected void onResume() {
        super.onResume();
        MyApplication.activityResumed();
    }
}

Наконец, когда вы создаете новый Activity, вы можете просто расширить его с помощью ActivityBase вместо Activity.

public class Main extends ActivityBase {
    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }
}

Для меня это лучший метод, потому что вам нужно просто помнить о расширении с помощью ActivityBase. Кроме того, в будущем вы можете расширить свою базовую функцию. В моем случае я добавил приемники для своей службы и оповещения о сети в один класс.

Если вы хотите проверить видимость своего приложения, вы можете просто позвонить

MyApplication.isActivityVisible()

Что делать, если мне нужны мои действия для расширения AppCombatActivity?
winklerrr

2

Этого можно добиться эффективным способом, используя Application.ActivityLifecycleCallbacks.

Например, возьмем имя класса Activity, поскольку ProfileActivity позволяет определить , находится ли он на переднем или в заднем плане.

сначала нам нужно создать наш класс приложения, расширив Application Class

который реализует

Application.ActivityLifecycleCallbacks

Давайте будем моим классом приложения следующим образом

Класс приложения

public class AppController extends Application implements Application.ActivityLifecycleCallbacks {


private boolean activityInForeground;

@Override
public void onCreate() {
    super.onCreate();

//register ActivityLifecycleCallbacks  

    registerActivityLifecycleCallbacks(this);

}



public static boolean isActivityVisible() {
    return activityVisible;
}

public static void activityResumed() {
    activityVisible = true;
}

public static void activityPaused() {
    activityVisible = false;
}

private static boolean activityVisible;

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

}

@Override
public void onActivityStarted(Activity activity) {

}

@Override
public void onActivityResumed(Activity activity) {
    //Here you can add all Activity class you need to check whether its on screen or not

    activityInForeground = activity instanceof ProfileActivity;
}

@Override
public void onActivityPaused(Activity activity) {

}

@Override
public void onActivityStopped(Activity activity) {

}

@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

}

@Override
public void onActivityDestroyed(Activity activity) {

}

public boolean isActivityInForeground() {
    return activityInForeground;
}
}

в приведенном выше классе есть метод переопределения onActivityResumed для ActivityLifecycleCallbacks

 @Override
public void onActivityResumed(Activity activity) {
    //Here you can add all Activity class you need to check whether its on screen or not

    activityInForeground = activity instanceof ProfileActivity;
}

где можно найти все экземпляры активности, которые в настоящее время отображаются на экране, просто проверьте, отображается ли ваша активность на экране, указанным выше методом.

Зарегистрируйте свой класс приложения в manifest.xml

<application
    android:name=".AppController" />

Чтобы проверить, активна ли погода на переднем плане или в фоновом режиме, в соответствии с приведенным выше решением, вызовите следующий метод в местах, которые вам нужно проверить

AppController applicationControl = (AppController) getApplicationContext();
    if(applicationControl.isActivityInForeground()){
     Log.d("TAG","Activity is in foreground")
    }
    else
    {
      Log.d("TAG","Activity is in background")
    }

1

Если вы хотите знать, отображается ли на экране какое-либо действие вашего приложения, вы можете сделать что-то вроде этого:

public class MyAppActivityCallbacks implements Application.ActivityLifecycleCallbacks {
private Set<Class<Activity>> visibleActivities = new HashSet<>();

@Override
public void onActivityResumed(Activity activity) {
    visibleActivities.add((Class<Activity>) activity.getClass());
}

@Override
public void onActivityStopped(Activity activity) {
     visibleActivities.remove(activity.getClass());
}

public boolean isAnyActivityVisible() {
    return !visibleActivities.isEmpty();
}

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

@Override
public void onActivityStarted(Activity activity) {}

@Override
public void onActivityPaused(Activity activity) {}

@Override
public void onActivityDestroyed(Activity activity) {}

@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}}

Просто создайте синглтон этого класса и установите его в своем экземпляре приложения, как показано ниже:

class App extends Application{
     @Override
     public void onCreate() {
         registerActivityLifecycleCallbacks(myAppActivityCallbacks);
     }
}

Затем вы можете использовать метод isAnyActivityVisible () своего экземпляра MyAppActivityCallbacks везде!


0

вы пытались не вызывать finish и указывать в манифесте «android: noHistory =« true »? Это предотвратит попадание активности в стек.


0

Я должен сказать, что ваш рабочий процесс отличается от стандартного для Android. В Android вам не нужно finish()ваше действие, если вы хотите открыть другое действие из Intent. Что касается удобства пользователя, Android позволяет пользователю использовать клавишу «назад», чтобы вернуться из активности, которую вы открыли в своем приложении.

Так что просто позвольте системе остановить вашу активность и сохранить все, что нужно, когда ваша активность будет отозвана.


3
Ответы типа «буу, это не андроид» утомительны и не отвечают на поставленный вопрос. кроме того, есть веские причины для использования finish (); - например, возможно, что возвращение к нему после того, как действие было выполнено, не имеет никакой цели. другими словами, как вы думаете, они добавили finish () туда для развлечения? это именно то, чего хотел избежать задающий вопрос
Ласси Киннунен

Ответы типа «буу, это не андроид» утомительны и не отвечают на поставленный вопрос. Ваша похвала была несправедливой. Хотя я указал, что это не способ Android, я дал ответ после этого предложения, а не ничего. Я просто не дал ответа в коде, поскольку в этом не было необходимости. Так что было несправедливо говорить, что я вообще не ответил на вопрос.
Оуэн Чжао

0

Сохраните флаг, если вы приостановили или возобновили. Если вы возобновили, значит, вы на переднем плане

boolean  isResumed = false;

@Override
public void onPause() {
  super.onPause();    
  isResumed = false;
}

@Override
public void onResume() {
  super.onResume();    
  isResumed = true;
}

private void finishIfForeground() {
  if (isResumed) {
    finish();
  }
}

0

Одним из возможных решений может быть установка флага при отображении системного диалога, а затем в методе onStop жизненного цикла активности проверить флаг, если он истинен, завершить действие.

Например, если системный диалог запускается нажатием какой-нибудь кнопки, то слушатель onclick может быть похож на

private OnClickListener btnClickListener = new OnClickListener() {

    @Override
    public void onClick(View v) {           
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_SEND);
        intent.setType("text/plain");
        CheckActivity.this.startActivity(Intent.createChooser(intent, "Complete action using"));
        checkFlag = true;  //flag used to check

    }
};

и при остановке деятельности:

@Override
protected void onStop() {
    if(checkFlag){
        finish();
    }
    super.onStop();
}

0

Почему бы не использовать для этого трансляции? второе действие (то, которое должно быть активировано) может отправлять локальную трансляцию следующим образом:

//put this in onCreate(..) or any other lifecycle method that suits you best
//notice the string sent to the intent, it will be used to register a receiver!
Intent result = new Intent("broadcast identifier");
result.putString("some message");//this is optional
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(result);

затем напишите простой приемник внутри всплеска:

//this goes on the class level (like a class/instance variable, not in a method) of your splash activity:
private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        //kill activity here!!!
        //mission accomplished!
    }
};

и зарегистрируйте новый приемник в LocalBroadcastManager, чтобы прослушивать трансляцию вашего второго действия:

//notice the string sent to the intent filter, this is where you tell the BroadcastManager which broadcasts you want to listen to!
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(receiver, new IntentFilter("broadcast identifier"));

ЗАМЕТЬТЕ, что вы можете использовать константу или строковый ресурс для строки «широковещательного идентификатора».


Для повышения безопасности используйте рекламу LocalBroadcastManagerздесь
Александр Фарбер

0

Если вы используете finish()только для того, чтобы новое приложение не запускалось в стеке (задаче) вашего приложения, вы можете использовать Intent.FLAG_ACTIVITY_NEW_TASKфлаг при запуске нового приложения и не вызывать его finish()вообще. Согласно документации , это флаг, который будет использоваться для реализации поведения стиля «пусковая установка».

// just add this line before you start an activity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

0

Используйте эти методы внутри файла Activity.

isDestroyed()

Добавлено в Api 17.
Возвращает истину, если последний вызов onDestroy () был выполнен для Activity, поэтому этот экземпляр теперь мертв.

isFinishing()

Добавлено в Api 1.
Проверьте, находится ли это действие в процессе завершения, потому что вы вызвали для него finish () или кто-то другой запросил его завершение. Это часто используется в onPause (), чтобы определить, приостановлено ли действие или полностью завершено.


Из документации по утечкам памяти

Распространенной ошибкой AsyncTaskявляется захват сильной ссылки на хост Activity(или Fragment):

class MyActivity extends Activity {
  private AsyncTask<Void, Void, Void> myTask = new AsyncTask<Void, Void, Void>() {
    // Don't do this! Inner classes implicitly keep a pointer to their
    // parent, which in this case is the Activity!
  }
}

Это проблема, потому что AsyncTaskможет легко пережить родителя Activity, например, если изменение конфигурации происходит во время выполнения задачи.

Правильный способ сделать это - сделать вашу задачу staticклассом, который не захватывает родительский элемент и содержит слабую ссылку на хост Activity:

class MyActivity extends Activity {
  static class MyTask extends AsyncTask<Void, Void, Void> {
    // Weak references will still allow the Activity to be garbage-collected
    private final WeakReference<MyActivity> weakActivity;

    MyTask(MyActivity myActivity) {
      this.weakActivity = new WeakReference<>(myActivity);
    }

    @Override
    public Void doInBackground(Void... params) {
      // do async stuff here
    }

    @Override
    public void onPostExecute(Void result) {
      // Re-acquire a strong reference to the activity, and verify
      // that it still exists and is active.
      MyActivity activity = weakActivity.get();
      if (activity == null
          || activity.isFinishing()
          || activity.isDestroyed()) {
        // activity is no longer valid, don't do anything!
        return;
      }

      // The activity is still valid, do main-thread stuff here
    }
  }
}

0

Вот решение с использованием Applicationкласса.

public class AppSingleton extends Application implements Application.ActivityLifecycleCallbacks {

private WeakReference<Context> foregroundActivity;


@Override
public void onActivityResumed(Activity activity) {
    foregroundActivity=new WeakReference<Context>(activity);
}

@Override
public void onActivityPaused(Activity activity) {
    String class_name_activity=activity.getClass().getCanonicalName();
    if (foregroundActivity != null && 
            foregroundActivity.get().getClass().getCanonicalName().equals(class_name_activity)) {
        foregroundActivity = null;
    }
}

//............................

public boolean isOnForeground(@NonNull Context activity_cntxt) {
    return isOnForeground(activity_cntxt.getClass().getCanonicalName());
}

public boolean isOnForeground(@NonNull String activity_canonical_name) {
    if (foregroundActivity != null && foregroundActivity.get() != null) {
        return foregroundActivity.get().getClass().getCanonicalName().equals(activity_canonical_name);
    }
    return false;
}
}

Вы можете просто использовать его следующим образом:

((AppSingleton)context.getApplicationContext()).isOnForeground(context_activity);

Если у вас есть ссылка на требуемое Activity или используется каноническое имя Activity, вы можете узнать, находится оно на переднем плане или нет. Это решение не может быть надежным. Поэтому ваши комментарии действительно приветствуются.


0

Я не знаю, почему никто не говорил о sharedPreferences для Activity A, устанавливая такой SharedPreference (например, в onPause ()):

SharedPreferences pref = context.getSharedPreferences(SHARED_PREF, 0);
SharedPreferences.Editor editor = pref.edit();
editor.putBoolean("is_activity_paused_a", true);
editor.commit();

Я считаю, что это надежный способ отслеживать активность на виду.


0

Было Activity.onWindowFocusChanged(boolean hasFocus)бы здесь полезно? Это, а также флаг уровня класса, что-то вроде isFocusedэтого onWindowFocusChanged, было бы простым способом определить в любой момент вашей деятельности, сфокусирован он или нет. Читая документы, похоже, что он правильно установил бы «false» в любой ситуации, когда действие не находится непосредственно на физическом «переднем плане», например, если отображается диалоговое окно или панель уведомлений опускается.

Пример:

boolean isFocused;
@Override
void onWindowFocusChanged (boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    isFocused = hasFocus;
}

void someMethod() {
    if (isFocused) {
        // The activity is the foremost object on the screen
    } else {
        // The activity is obscured or otherwise not visible
    }
}

0

Если вы используете EventBus , это как вызываемый метод, hasSubscriberForEventкоторый можно использовать для проверки Activity, сфокусирован ли объект .


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.