Что такое класс PackageInstaller на Lollipop и как его использовать?

Фон

Я заметил, что в PackageManager появилась новая функция getPackageInstaller с minAPI 21 (Lollipop).

Я достиг класса PackageInstaller, и это то, что написано об этом:

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

Приложение поставляется для установки через PackageInstaller.Session, который может создать любое приложение. После создания сеанса установщик может передавать один или несколько APK-файлов на место до тех пор, пока он не решит зафиксировать или уничтожить сеанс. Передача может потребовать вмешательства пользователя для завершения установки.

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

Вопросы

  1. Для чего используется этот класс? Это даже доступно для сторонних приложений (я не вижу никакого упоминания об этом)?
  2. Это действительно может установить приложения?
  3. Это делает это в фоновом режиме?
  4. Какие ограничения?
  5. Требуются ли разрешения? Если так, то какой?
  6. Есть ли учебник, как его использовать?

0 ответов

ОК, я нашел несколько ответов:

  1. Может использоваться для установки / обновления файлов APK, включая файлы split-APK. Может быть, даже больше.
  2. Да, но пользователь должен будет подтвердить одно приложение за другим.
  3. Может быть, если приложение встроено.
  4. Кажется, нужно прочитать весь APK-файл (ы) перед тем, как просить пользователя установить.
  5. Требуется разрешение REQUEST_INSTALL_PACKAGES
  6. Не нашли, но кто-то показал мне, как установить split-apk файлы, и вот как это сделать для одного файла с использованием SAF, с PackageInstaller и без него. Обратите внимание, что это всего лишь образец. Я не считаю хорошей практикой делать все это в потоке пользовательского интерфейса.

манифест

<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
          package="com.android.apkinstalltest">
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

    <application tools:ignore="AllowBackup,GoogleAppIndexingWarning"
                 android:allowBackup="true"
                 android:icon="@mipmap/ic_launcher"
                 android:label="@string/app_name"
                 android:roundIcon="@mipmap/ic_launcher_round"
                 android:supportsRtl="true"
                 android:theme="@style/AppTheme">
        <activity
                android:name=".MainActivity"
                android:label="@string/app_name"
                android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <service android:name=".APKInstallService"/>

    </application>

</manifest>

APKInstallService

class APKInstallService : Service() {
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)) {
            PackageInstaller.STATUS_PENDING_USER_ACTION -> {
                Log.d("AppLog", "Requesting user confirmation for installation")
                val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
                confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                try {
                    startActivity(confirmationIntent)
                } catch (e: Exception) {
                }
            }
            PackageInstaller.STATUS_SUCCESS -> Log.d("AppLog", "Installation succeed")
            else -> Log.d("AppLog", "Installation failed")
        }
        stopSelf()
        return START_NOT_STICKY
    }

    override fun onBind(intent: Intent) = null
}

Основная деятельность

class MainActivity : AppCompatActivity() {
    private lateinit var packageInstaller: PackageInstaller

    @TargetApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
        packageInstaller = packageManager.packageInstaller
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.type = "application/vnd.android.package-archive"
        startActivityForResult(intent, 1)
    }

//    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
//        super.onActivityResult(requestCode, resultCode, resultData)
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && requestCode == 1 && resultCode == Activity.RESULT_OK && resultData != null) {
//            val uri = resultData.data
//            grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
//            val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)//
//                    .setDataAndType(uri, "application/vnd.android.package-archive")
//                    .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
//                    .putExtra(Intent.EXTRA_RETURN_RESULT, false)
//                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
//            startActivity(intent)
//        }

    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
        super.onActivityResult(requestCode, resultCode, resultData)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && requestCode == 1 && resultCode == Activity.RESULT_OK && resultData != null) {
            val uri = resultData.data ?: return
            grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
            val installParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
            var cursor: Cursor? = null
            var outputStream: OutputStream? = null
            var inputStream: InputStream? = null
            var session: PackageInstaller.Session? = null
            try {
                cursor = contentResolver.query(uri, null, null, null, null)
                if (cursor != null) {
                    cursor.moveToNext()
                    val fileSize = cursor.getLong(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_SIZE))
                    val fileName = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME))
                    installParams.setSize(fileSize)
                    cursor.close()
                    val sessionId = packageInstaller.createSession(installParams)
                    Log.d("AppLog", "Success: created install session [$sessionId] for file $fileName")
                    session = packageInstaller.openSession(sessionId)
                    outputStream = session.openWrite(System.currentTimeMillis().toString(), 0, fileSize)
                    inputStream = contentResolver.openInputStream(uri)
                    inputStream.copyTo(outputStream)
                    session.fsync(outputStream)
                    outputStream.close()
                    outputStream = null
                    inputStream.close()
                    inputStream = null
                    Log.d("AppLog", "Success: streamed $fileSize bytes")
                    val callbackIntent = Intent(applicationContext, APKInstallService::class.java)
                    val pendingIntent = PendingIntent.getService(applicationContext, 0, callbackIntent, 0)
                    session!!.commit(pendingIntent.intentSender)
                    session.close()
                    session = null
                    Log.d("AppLog", "install request sent. sessions:" + packageInstaller.mySessions)
                }
            } catch (e: Exception) {
                Log.d("AppLog", "error:$e")
            } finally {
                outputStream?.close()
                inputStream?.close()
                session?.close()
                cursor?.close()
            }
        }
    }
}
Другие вопросы по тегам