#android #android-lifecycle
#Android #android-жизненный цикл
Вопрос:
Я работаю над приложением для Android, которому необходимо выполнить задачу очистки (отключить устройства BLE), когда все приложение переходит в фоновый режим. Я реализовал стандартные обратные вызовы ActivityLifecycleCallbacks и веду бухгалтерский учет в классе Application. Все работает хорошо, пока я использую свои экземпляры AppCompatActivity в своем пакете.
class MainApplication : Application(), Application.ActivityLifecycleCallbacks {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(this)
}
/// Activity counter
var startedActivities = AtomicInteger(0)
private fun activityStarted() {
if (startedActivities.incrementAndGet() == 1) {
Log.d("PICKERTEST", ">>> APPLICATION STARTED")
}
}
private fun activityStopped() {
if (startedActivities.decrementAndGet() == 0) {
Log.d("PICKERTEST", ">>> APPLICATION STOPPED")
}
}
/// Activity lifecycle callbacks
override fun onActivityStarted(activity: Activity) {
activityStarted()
}
override fun onActivityStopped(activity: Activity) {
activityStopped()
}
...
МОЯ ПРОБЛЕМА: проблема начинается, когда мне нужно выбрать файлы (например, обновление прошивки) с помощью системных действий. В этом случае я не получаю обратных вызовов для действия средства выбора, и мое приложение считает, что оно приостанавливается, как только появляется средство выбора. Это отключает устройство BLE, и, как только я вернусь из средства выбора файлов, я не смогу загрузить файл прошивки на устройство.
Код для открытия средства выбора файлов довольно стандартный:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pickButton.setOnClickListener { pickFile() }
}
private fun pickFile() {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
this.addCategory(Intent.CATEGORY_OPENABLE)
this.type = "*/*"
}
startActivityForResult(intent, 0)
}
...
Это открывает системную активность со стандартным средством выбора ОС. Согласно активности adb shell dumpsys:
ActivityRecord{e05754 u0 com.android.documentsui/.picker.PickActivity t162}]
Это действие происходит из android.documentsui:
Базовая активность > https://android.googlesource.com/platform/packages/apps/DocumentsUI/ /android-cts-8.0_r16/src/com/android/documentsui/BaseActivity.java
Этот пакет не основан на androidx и использует стандартный android.app.Activity. Честно говоря, я не уверен, что это проблема, или обратные вызовы жизненного цикла просто не вызываются, когда я запускаю действие из другого пакета.
Я, конечно, могу добавить пользовательский обратный вызов в MainApplication, чтобы уведомить, что мы выполняем внешнюю активность
class MainApplication : Application(), Application.ActivityLifecycleCallbacks {
...
/// Custom callback
fun onStartActivityForResult() {
Log.d("PICKERTEST", "onStartActivityForResult")
activityStarted()
}
fun onActivityResult() {
Log.d("PICKERTEST", "onActivityResult")
activityStopped()
}
}
и вызовите это в моей деятельности
class MainActivity : AppCompatActivity() {
...
private fun pickFile() {
...
val mainApplication = applicationContext as? MainApplication
mainApplication?.onStartActivityForResult()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val mainApplication = applicationContext as? MainApplication
mainApplication?.onActivityResult()
}
}
Однако это взлом, а также не распространяется на случай, когда мое приложение приостанавливается при отображении средства выбора файлов.
Кто-нибудь знает решение для прослушивания событий жизненного цикла внешних действий?
Комментарии:
1.
In this case, I don't get callbacks for the picker activity
Я понятия не имею, что вы под этим подразумеваете.and my app believes it gets paused, as soon as the picker appears.
. Да, это вполне нормально. Ваша активность может быть прервана в любой момент, если она не является верхней.2. Создайте свой собственный сборщик в виде диалогового окна, чтобы ваша активность была покрыта только частично и оставалась видимой за ним.
3. @blackapps если вы хорошо прочитали начало моего поста, у меня есть обратные вызовы жизненного цикла, реализованные для действий. Кроме того, я сказал, что «мое приложение» считает, что оно приостановлено, а не моя активность. Конечно, моя основная деятельность приостанавливается, но мое приложение все еще работает. Кроме того, я не хочу писать пользовательский код для чего-то, что хорошо работает и имеет встроенное ощущение, как встроенный сборщик файлов. В любом случае спасибо за ваш отзыв!
4. «или если обратные вызовы жизненного цикла просто не вызываются, когда я запускаю действие из другого пакета» — это совершенно другой процесс и совершенно другое приложение. Следовательно, вы не получите обратных вызовов жизненного цикла.
5. @PhilipBulley, к сожалению, не чистый. Я добавляю явный флаг в свое приложение, который я устанавливаю перед переходом к внешнему действию (например, к сборщику), в сочетании с большим таймаутом (в моем случае 1 минута).
Ответ №1:
В интересах тех, кто задает или проверяет этот вопрос, вот мой подход к проблеме, описанной в моем вопросе выше. Я понимаю, что это не самое чистое возможное решение, но оно решает задачу.
class MainApplication: Application(), Application.ActivityLifecycleCallbacks
{
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(this)
}
/// Activity counter
private var startedActivities = AtomicInteger(0)
private var waitingForExternalActivity = AtomicBoolean(false)
private fun activityIsRunning(): Boolean {
return (startedActivities.get() > 0)
}
/// Activity lifecycle callbacks
override fun onActivityStarted(activity: Activity) {
if (waitingForExternalActivity.getAndSet(false)) {
cancelWaitingForExternalActivityTimeout()
startedActivities.incrementAndGet()
} else {
if (startedActivities.incrementAndGet() == 1) {
onApplicationStarted()
}
}
}
override fun onActivityStopped(activity: Activity) {
if (startedActivities.decrementAndGet() == 0) {
if (!waitingForExternalActivity.get() amp;amp; !activity.isChangingConfigurations) {
onApplicationStopped()
}
}
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { }
override fun onActivityResumed(activity: Activity) { }
override fun onActivityPaused(activity: Activity) { }
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { }
override fun onActivityDestroyed(activity: Activity) { }
/// Custom callback
fun onStartExternalActivity() {
waitingForExternalActivity.set(true)
scheduleWaitingForExternalActivityTimeout()
}
private val waitingForExternalActivityHandler = Handler()
private val waitingForExternalActivityTimeout = object : Runnable {
override fun run() {
waitingForExternalActivity.set(false)
onApplicationStopped()
}
}
private fun scheduleWaitingForExternalActivityTimeout() {
waitingForExternalActivityHandler.postDelayed(waitingForExternalActivityTimeout, 60000)
}
private fun cancelWaitingForExternalActivityTimeout() {
waitingForExternalActivityHandler.removeCallbacks(waitingForExternalActivityTimeout)
}
/// Application lifecycle - custom code
private fun onApplicationStarted() {
/// *** your code here
}
private fun onApplicationStopped() {
/// *** your code here
}
}
И в остальной части вашего кода, где бы вы ни запускали внешнюю активность, вам нужно будет сначала уведомить свое приложение. Например:
fun pickFile(callback: ((Uri?) -> Unit)) {
mainApplication.onStartExternalActivity()
pickFileCallback = callback
pickFileLauncher.launch(arrayOf("*/*"))
}