Lollipop上的“PackageInstaller”类是什么,以及如何使用它?

时间:2015-05-05 12:38:57

标签: android session apk android-package-managers packageinstaller

背景

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

我已经到了“PackageInstaller”课程,这就是它的内容:

  

提供安装,升级和删除应用程序的功能   设备。这包括对作为单个打包的应用程序的支持   “monolithic”APK,或打包为多个“拆分”APK的应用程序。

     

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

     

会话可以安装全新的应用,升级现有应用或添加新应用   分成现有的应用程序。

问题

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

1 个答案:

答案 0 :(得分:0)

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

  1. 可用于安装/更新APK文件,包括拆分APK文件。甚至更多。
  2. 是的,但用户需要先确认一个应用程序。
  3. 也许该应用是内置的。
  4. 似乎要求在请求用户安装之前先读取整个APK文件。
  5. 需要权限REQUEST_INSTALL_PACKAGES
  6. 还没有找到任何东西,但有人在这里向我展示了如何安装split-apk文件,这是在有或没有PackageInstaller的情况下,如何使用SAF对单个文件进行安装的方法。请注意,这只是一个示例。我认为在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
}

MainActivity

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()
            }
        }
    }
}