Android 10 — не найдено действий для обработки намерения

В моем стороннем приложении конечный пользователь может загрузить обновленный APK с нашего сервера, а затем приложение вызовет диспетчер пакетов установки для этого APK после завершения загрузки. Этот же метод работал для всех версий ОС Android, но теперь он не работает на Android 10 (api 29). Я еще не видел никого с подобной проблемой, любая помощь будет принята с благодарностью!

Вот что я использую для вызова APK-файла из своего приложения:

Intent intent = new Intent(Intent.ACTION_VIEW);
final File apkFile = new File(Files.getApkFileName());
Log.v("dt.update", "Start update from " + apkFile.getAbsolutePath());
intent.setDataAndType(Uri.fromFile(apkFile), application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

И вот трассировка стека возвращается каждый раз, только на Android 10/API29:

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.MyAppHere, PID: 11107
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=file:///storage/emulated/0/Download/updatedapp.apk typ=application/vnd.android.package-archive flg=0x10000000 }
    at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2051)
    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1709)
    at android.app.Activity.startActivityForResult(Activity.java:5192)
    at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:676)
    at android.app.Activity.startActivityForResult(Activity.java:5150)
    at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:663)
    at android.app.Activity.startActivity(Activity.java:5521)
    at android.app.Activity.startActivity(Activity.java:5489)
    at android.view.View.performClick(View.java:7140)
    at android.view.View.performClickInternal(View.java:7117)
    at android.view.View.access$3500(View.java:801)
    at android.view.View$PerformClick.run(View.java:27351)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

person buradd    schedule 24.09.2019    source источник


Ответы (1)


ACTION_VIEW (для APK) и ACTION_INSTALL_PACKAGE устарели в Android 10. Вам нужно переключиться на PackageInstaller API.

Этот пример приложения демонстрирует основы получения простого APK установлены. Кишки в MainMotor:

/*
  Copyright (c) 2019 CommonsWare, LLC

  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  use this file except in compliance with the License. You may obtain   a copy
  of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
  by applicable law or agreed to in writing, software distributed under the
  License is distributed on an "AS IS" BASIS,   WITHOUT WARRANTIES OR CONDITIONS
  OF ANY KIND, either express or implied. See the License for the specific
  language governing permissions and limitations under the License.

  Covered in detail in the book _Elements of Android Q

  https://commonsware.com/AndroidQ
*/

package com.commonsware.q.appinstaller

import android.app.Application
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageInstaller
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

private const val NAME = "mostly-unused"
private const val PI_INSTALL = 3439

class MainMotor(app: Application) : AndroidViewModel(app) {
  private val installer = app.packageManager.packageInstaller
  private val resolver = app.contentResolver

  fun install(apkUri: Uri) {
    viewModelScope.launch(Dispatchers.Main) {
      installCoroutine(apkUri)
    }
  }

  private suspend fun installCoroutine(apkUri: Uri) =
    withContext(Dispatchers.IO) {
      resolver.openInputStream(apkUri)?.use { apkStream ->
        val length =
          DocumentFile.fromSingleUri(getApplication(), apkUri)?.length() ?: -1
        val params =
          PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
        val sessionId = installer.createSession(params)
        val session = installer.openSession(sessionId)

        session.openWrite(NAME, 0, length).use { sessionStream ->
          apkStream.copyTo(sessionStream)
          session.fsync(sessionStream)
        }

        val intent = Intent(getApplication(), InstallReceiver::class.java)
        val pi = PendingIntent.getBroadcast(
          getApplication(),
          PI_INSTALL,
          intent,
          PendingIntent.FLAG_UPDATE_CURRENT
        )

        session.commit(pi.intentSender)
        session.close()
      }
    }
}

Когда действие или фрагмент вызывает install(), предоставляя APK Uri, я использую PackageInstaller для его установки:

  • Получите PackageInstaller от PackageManager
  • Создайте SessionParams и откройте из него сессию
  • Запишите байты APK (прочитанные из InputStream из Uri) в OutputStream, предоставленный этим сеансом.
  • Позвоните commit(), чтобы фактически начать процесс установки, а результаты будут отправлены обратно в приложение через PendingIntent
  • Позвоните close(), чтобы закрыть сеанс

API неуклюжий, но он предназначен для обработки широкого спектра сценариев, включая установку нескольких APK в стиле «App Bundle».

person CommonsWare    schedule 24.09.2019
comment
Я вернулся, чтобы ответить на этот вопрос сам, так как нашел ответ и внедрил исправление, ссылаясь на Google APIDemos, ответ точно такой же, как у вас, поэтому я принял ответ, но пример Kotlin заставляет меня съеживаться :( Меня интересует только Java, и было здорово найти образец, написанный на Java от Google: android.googlesource.com/platform/development/+/master/samples/ - person buradd; 26.09.2019
comment
Есть ли способ показать старый поток обновления/установки таким же образом, если запущено намерение с действием Intent.ACTION_INSTALL_PACKAGE? Я хочу сказать, что использование PackageInstaller не покажет пользователю ход установки обновления, вместо этого он автоматически обновит и закроет приложение, не информируя его. - person mathew11; 17.12.2019
comment
@ mathew11: Насколько я знаю, ты будешь отвечать за этот пользовательский интерфейс. См. разработчик .android.com/reference/kotlin/android/content/pm/ - person CommonsWare; 17.12.2019
comment
@CommonsWare Нужно ли отправлять трансляцию? val intent = Intent(getApplication(), InstallReceiver::class.java) val pi = PendingIntent.getBroadcast( getApplication(), PI_INSTALL, intent, PendingIntent.FLAG_UPDATE_CURRENT) session.commit(pi.intentSender) - person rupesh; 12.05.2020
comment
@rupesh: Извините, но я не понимаю вашего вопроса. Вам нужен IntentSender, чтобы перейти к commit(). Эти строки показывают, как настроить файл IntentSender. Фактическая трансляция отправляется ОС после установки приложения (или нет). - person CommonsWare; 12.05.2020
comment
@CommonsWare спасибо за разъяснения. Мой вопрос: я скачал apk с сервера, теперь это последняя версия, и я хочу обновить то же приложение. В этом случае приложение не будет получать трансляции. - person rupesh; 12.05.2020
comment
@rupesh: Извините, но я не пробовал этот сценарий. - person CommonsWare; 12.05.2020
comment
Где я могу найти официальную документацию, в которой говорится, что ACTION_VIEW устарел для apks? - person AndreaGrnd; 04.08.2020
comment
@AndreaGrnd: я буду удивлен, если на этот счет есть официальная документация. - person CommonsWare; 04.08.2020