2

背景

我注意到 PackageManager 上有一个名为“ getPackageInstaller ”的新函数,其 minAPI 21 (Lollipop)。

我已经到了“ PackageInstaller ”类,这就是它所写的:

提供在设备上安装、升级和删除应用程序的能力。这包括对打包为单个“单体”APK 或打包为多个“拆分”APK 的应用程序的支持。

应用程序通过 PackageInstaller.Session 交付安装,任何应用程序都可以创建它。创建会话后,安装程序可以将一个或多个 APK 流式传输到适当的位置,直到它决定提交或销毁会话。提交可能需要用户干预才能完成安装。

会话可以安装全新的应用程序、升级现有应用程序或将新拆分添加到现有应用程序中。

问题

  1. 这个类是做什么用的?它甚至可用于第三方应用程序(我没有看到任何提及)?
  2. 它真的可以安装应用程序吗?
  3. 它在后台执行吗?
  4. 有什么限制?
  5. 它需要权限吗?如果是这样,是哪个?
  6. 有没有关于如何使用它的教程?
4

1 回答 1

0

好的,我找到了一些答案:

  1. 可用于安装/更新 APK 文件,包括拆分 APK 文件。也许更多。
  2. 是的,但用户需要一个接一个地确认。
  3. 也许如果应用程序是内置的。
  4. 似乎需要在请求用户安装之前读取整个 APK 文件。
  5. 需要权限REQUEST_INSTALL_PACKAGES
  6. 还没有找到,但是有人在这里向我展示了如何安装拆分 apk 文件,这里是如何使用 SAF 为单个文件执行此操作,有和没有 PackageInstaller。请注意,这只是一个示例。我认为在 UI 线程上完成这一切并不是一个好习惯。

显现

<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()
            }
        }
    }
}
于 2019-05-25T18:53:09.513 回答