Что такое класс PackageInstaller на Lollipop и как его использовать?
Фон
Я заметил, что в PackageManager появилась новая функция getPackageInstaller с minAPI 21 (Lollipop).
Я достиг класса PackageInstaller, и это то, что написано об этом:
Предоставляет возможность устанавливать, обновлять и удалять приложения на устройстве. Это включает в себя поддержку приложений, упакованных как один "монолитный" APK, или приложений, упакованных как несколько "разделенных" APK.
Приложение поставляется для установки через PackageInstaller.Session, который может создать любое приложение. После создания сеанса установщик может передавать один или несколько APK-файлов на место до тех пор, пока он не решит зафиксировать или уничтожить сеанс. Передача может потребовать вмешательства пользователя для завершения установки.
Сеансы могут устанавливать новые приложения, обновлять существующие приложения или добавлять новые разбиения в существующее приложение.
Вопросы
- Для чего используется этот класс? Это даже доступно для сторонних приложений (я не вижу никакого упоминания об этом)?
- Это действительно может установить приложения?
- Это делает это в фоновом режиме?
- Какие ограничения?
- Требуются ли разрешения? Если так, то какой?
- Есть ли учебник, как его использовать?
0 ответов
ОК, я нашел несколько ответов:
- Может использоваться для установки / обновления файлов APK, включая файлы split-APK. Может быть, даже больше.
- Да, но пользователь должен будет подтвердить одно приложение за другим.
- Может быть, если приложение встроено.
- Кажется, нужно прочитать весь APK-файл (ы) перед тем, как просить пользователя установить.
- Требуется разрешение REQUEST_INSTALL_PACKAGES
- Не нашли, но кто-то показал мне, как установить 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()
}
}
}
}