Обратные вызовы жизненного цикла для системной активности (PickActivity / Intent.ACTION_GET_CONTENT)

#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:

PickActivity > https://android.googlesource.com/platform/packages/apps/DocumentsUI/ /android-cts-8.0_r16/src/com/android/documentsui/picker/PickActivity.java

Базовая активность > 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("*/*"))
}