Почему работающие обратные вызовы автоматически уничтожают активность?

Я хотел знать, есть ли возможность, что мы могли бы обрабатывать/обнаруживать работающие обратные вызовы с задержкой (метод postDelayed) на Android?

Например, у меня есть один или несколько заставок (которые работают с handler.postDelayed(new Runnable()...) в моем приложении (приложение для тестирования). В этом приложении у меня также есть библиотека (которую я создаю и использую в приложении) и несколько доступных там классов, которые работают в классе IntentService.

Иногда, когда приложение выполняет эти splashscreen действия (для Testing purpose), создаваемая библиотека может автоматически отображать некоторые действия в пользовательском интерфейсе. Но похоже, что если эти действия происходят в действии splashscreen и что splashscreen уничтожается, эти действия (которые всплывают автоматически) также будут уничтожены, и в logcat будет зарегистрировано сообщение "утечка окна".

Проблема в том, что :

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

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

  • Есть ли способ определить, был ли в приложении создан какой-либо метод postDelayed относительно библиотеки? Если да, то как я могу справиться с проблемой?

P.S.: Обратите внимание, что обычно я использую диалоговое окно для предполагаемой активности, которая появляется автоматически.

ОБНОВЛЕНИЕ

Диаграмма

Пояснение к схеме:

Прямо сейчас у меня есть случай, когда выполняется Splashscreen.

Класс, который расширяет класс IntentService, получил запрос из Интернета, который запускает Activity.

Тем временем заставка включена postdelayed, другая активность создана и отображается в пользовательском интерфейсе. Когда пройдет X секунд, а другое действие не будет уничтожено, будет создано Следующее действие, которое автоматически уничтожит другое действие. При этом Android выдает сообщение об утечке окна относительно Activity.


person Damiii    schedule 16.08.2017    source источник
comment
Можете ли вы предоставить соответствующий код для этих исполняемых модулей и действий.   -  person petey    schedule 18.08.2017
comment
@YvetteColomb проблема в том, что код является конфиденциальным ... Поэтому, к сожалению, я не могу вставить код .... Но я подумал, что моя диаграмма очень поможет, а также объяснение. Что я могу сделать, чтобы вы лучше поняли мою проблему? В чем проблема с моим объяснением?   -  person Damiii    schedule 19.08.2017
comment
Одна вещь, которую мне все еще нужно понять о вашей проблеме: если ваша всплывающая активность создается из IntentService, я предполагаю, что NextActivity понятия не имеет об этом, то как тогда, когда NextActivity уничтожит вашу всплывающую активность, когда она появится? Это ваша всплывающая активность автоматически завершается, если поверх нее есть другие действия?   -  person Binh Tran    schedule 19.08.2017
comment
Хорошо, если Activity отображается, а NextActivity выполняется и отображается сразу после (из-за времени, прошедшего методом onPostDelayed). Активность будет уничтожена автоматически без какого-либо взаимодействия со стороны пользователя, который должен выйти из этой активности собственноручно. @БинТран   -  person Damiii    schedule 20.08.2017
comment
Нормальное поведение, если NextActivity идет поверх вашей Activity, должно быть только в фоновом режиме. Если он уничтожен, в вашей деятельности должна быть какая-то логика, чтобы сделать это, это то, что я хотел прояснить. И еще один вопрос: ваша цель — удерживать всплывающее окно «Активность» наверху до тех пор, пока пользователь не закроет его, вместо того, чтобы отображать какие-либо другие действия сверху, верно?   -  person Binh Tran    schedule 20.08.2017
comment
@Damiiii Я думаю, вы смешиваете в своем вопросе всевозможные термины, делая свой вопрос слишком общим. Было бы в ваших интересах, если бы вы разбили вопрос на более конкретные, четко определенные вопросы.   -  person Alex.F    schedule 21.08.2017
comment
Вы пытаетесь показать диалог, используя действие, которое уже завершилось? Вам может быть полезно прочитать ответы Окно активности просочилось, которое было изначально добавлено   -  person Victor B    schedule 23.08.2017
comment
@Damiiii, Leaked Window предупреждение никогда не закрывает какую-либо активность, это просто предупреждение. Проблема кроется где-то в коде, где был вызван finish().   -  person Akash Kava    schedule 24.08.2017
comment
@AkashKava, и вы правы, когда NextActivity запускается, если я не ошибаюсь, Android SO удалит Activity, вызвав функцию onDestroyed.   -  person Damiii    schedule 24.08.2017
comment
@Damii Ты не можешь быть в этом уверен. OnStop обязательно вызовут. OnDestroy может быть вызван сразу после этого, через полчаса или даже никогда, если есть утечка объектов.   -  person rupps    schedule 25.08.2017


Ответы (8)


Есть ли способ определить, был ли в приложении создан какой-либо метод postDelayed относительно библиотеки?

Вы можете использовать API MessageQueue.IdleHandler. См. LooperIdlingResource как эспрессо определяет, подходит ли время для активации в утверждении.


    @Override
    public boolean queueIdle() {

      QueueState queueState = myInterrogator.determineQueueState();
      if (queueState == QueueState.EMPTY || queueState == QueueState.TASK_DUE_LONG) {
        ...
      } else if (queueState == QueueState.BARRIER) {
        ...
      }

      return true;
    }

Это поможет вам понять, есть ли сообщение в MessageQueue, но не скажет вам, какое именно сообщение.

Решение, которое я бы выбрал, состоит в том, чтобы отменить планирование Runnable, которое у вас есть postDelayed в пределах onStop действия, потому что, если Activity (тот, что из библиотеки) был запущен, то вызывается onStop из SplashScreen:


public class SplashActivity extends AppCompatActivity {

  private final Runnable myRunnable = () -> {
    // launch `NextActivity`
  };
  private final Handler handler = new Handler();

  @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_splash);

    handler.postDelayed(myRunnable, 3000);
  }

  @Override
  protected void onStop() {
    super.onStop();
    handler.removeCallbacks(myRunnable);
  }

}

person azizbekian    schedule 20.08.2017

Вам нужно лучше объяснить проблему. Я запутался между отношением экрана-заставки и другими действиями, и если проблема связана с postDelayed() или с жизненным циклом действий. Я бы предложил крошечную графическую диаграмму, объясняющую, какие действия запускают другие.

Что касается postDelayed(), в общем, если вы делаете

 mHandler.postDelayed(new Runnable() { ... });

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

Runnable mLaunchSplashRunnable = new Runnable() { ... };
Runnable mLaunchContactsRunnable = new Runnable() { ... };

.
.

mHandler.postDelayed (mLaunchSplashRunnable, DELAY);
mHandler.postDelayed (mLaunchContactsRunnable, DELAY);

.
.

Поскольку исполняемые объекты теперь не являются анонимными, вы можете удалить их из очереди в любое время:

void removeLibraryDelayedRunnables() {
   mHandler.removeCallbacks(mLaunchSplashRunnable);
   mHandler.removeCallbacks(mLaunchContactsRunnable);
}

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

Метода запроса Handler, если конкретный Runnable поставлен в очередь, на самом деле, не существует, но вы можете использовать флаг boolean, установить его, когда исполняемый файл поставлен в очередь, и сбросить его, когда запускается Runnable, чтобы указать, что исполняемый файл находится в очереди. в ожидании.

Если я лучше пойму вашу проблему, я смогу помочь больше.

person rupps    schedule 18.08.2017
comment
Хорошо, я сделаю схему. И объясните это еще. - person Damiii; 18.08.2017

Почему бы вам не использовать статическую логическую переменную, чтобы определить, работает ли ваш экран-заставка или нет, когда вы собираетесь его вызвать.

person james    schedule 25.08.2017
comment
Потому что это SDK, который я создаю. Таким образом, SDK не имеет ни логики, ни контроля со стороны приложения, которое импортирует SDK. - person Damiii; 25.08.2017

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

https://developer.android.com/guide/components/activities/activity-lifecycle.html

см. после onStop()... это относится к вашему приложению.

Надеюсь, что это поможет вам...

person Abdul Aziz    schedule 24.08.2017
comment
Ну, я уже знаю, что проблема была связана с жизненным циклом активности, и я хочу немного другого... Попробуйте принудительно удерживать View of Activity и увеличить время в этом случае runnable. (Что-то более-менее). - person Damiii; 24.08.2017

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

Для этого вы можете зарегистрировать BroadcastReceiver при создании каждого действия https://developer.android.com/reference/android/content/BroadcastReceiver.html и используйте sendBroadcast из службы https://developer.android.com/guide/components/broadcasts.html, когда необходимо запустить действие, чтобы дать команду широковещательному приемнику начать необходимое действие.

person Anton Malyshev    schedule 18.08.2017
comment
Проблема в том, что я создаю SDK, и этот SDK должен быть включен в любое приложение. В этом случае SDK не имеет представления/информации о приложении, использующем SDK. - person Damiii; 19.08.2017
comment
Но вы можете потребовать, чтобы пользователи вашего SDK вызывали некоторые методы при создании действий с помощью SDK. Для меня это лучше и надежнее, чем пытаться отслеживать поведение пользователя в приложении. - person Anton Malyshev; 20.08.2017

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

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

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

person Brian    schedule 25.08.2017

Так как мы еще не видели ваш код. Мой ответ будет слишком общим, извините за это.

Я думаю, вам следует сначала проверить logcat, чтобы увидеть, есть ли какие-либо исключения, связанные с закрытием этих действий. Если об этом ничего нет. Просто проверьте все блоки try catch, чтобы убедиться в этом. Итак, после того, как вы убедитесь, что речь не идет о каких-либо исключениях, проверьте файл AndroidManifest, чтобы увидеть «Режимы запуска» действий. Это также может привести к автоматическому закрытию.

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

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

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

person Oguz Ozcan    schedule 25.08.2017

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

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

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    listener = new WeakReference<>((MyActionListener) activity);
    activityWeakReference = new WeakReference<>(activity);
}

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

if (myActivityWeakReference.get() != null && !myActivityWeakReference.get().isFinishing()) {
  // show dialog
}

Затем, когда вы хотите управлять вращением устройства, где действия воссоздаются, вы можете использовать следующее вместе со своими диалогами:

    @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
}

/**
 * Prevent the dialog being dismissed when rotated, when using setRetainInstance
 */
@Override
public void onDestroyView() {
    if (getDialog() != null && getRetainInstance()) {
        getDialog().setDismissMessage(null);
    }
    super.onDestroyView();
}
person Wayne    schedule 25.08.2017