问题
Background
I've noticed there is a new function on the PackageManager called "getPackageInstaller" , with minAPI 21 (Lollipop).
I've reached the "PackageInstaller" class, and this is what it is written about it:
Offers the ability to install, upgrade, and remove applications on the device. This includes support for apps packaged either as a single "monolithic" APK, or apps packaged as multiple "split" APKs.
An app is delivered for installation through a PackageInstaller.Session, which any app can create. Once the session is created, the installer can stream one or more APKs into place until it decides to either commit or destroy the session. Committing may require user intervention to complete the installation.
Sessions can install brand new apps, upgrade existing apps, or add new splits into an existing app.
Questions
- What is this class used for? Is it even available for third party apps (I don't see any mentioning of this) ?
- Can it really install apps?
- Does it do it in the background?
- What are the restrictions?
- Does it require permissions? If so, which?
- Is there any tutorial of how to use it?
回答1:
OK, I've found some answers:
- Can be used for installing/updating APK files, including split-APK files. Maybe even more.
- Yes, but the user will need to confirm, one app after another.
- Maybe if the app is built in.
- Seems it requires to read the entire APK file/s before requesting the user to install.
- Needs permission REQUEST_INSTALL_PACKAGES
- Haven't found any, but someone showed me here how to install split-apk files, and here's how to do it for a single file using SAF, with and without PackageInstaller. Note that this is just a sample. I don't think it's a good practice to do it all on the UI thread.
manifest
<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()
}
}
}
}
来源:https://stackoverflow.com/questions/30053200/whats-packageinstaller-class-on-lollipop-and-how-to-use-it