如何静默更新Kiosk模式下的设备所有者托管Android应用
Kiosk启动器静默更新解决方案(设备所有者应用)
可以实现无需用户确认的静默更新——因为设备所有者应用拥有系统级的静默安装权限,只要修正当前代码的问题并正确利用权限即可。
你的代码存在的问题
- 下载完成后传入
installPackage的网络输入流已经被读取完毕(数据已写入本地文件),无法再用来读取APK内容。 - 未设置
USER_ACTION_NOT_REQUIRED参数,即使是设备所有者,默认也可能触发用户确认弹窗。
修正步骤与代码
1. 确认应用是设备所有者
设备所有者权限是静默安装的必要前提,先通过代码验证:
import android.app.admin.DevicePolicyManager import android.content.Context fun isDeviceOwner(context: Context): Boolean { val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager return dpm.isDeviceOwnerApp(context.packageName) }
如果返回false,需要先通过ADB命令(dpm set-device-owner 你的包名/.你的DeviceAdminReceiver)或EMM渠道将应用设为设备所有者。
2. 配置Manifest权限
在AndroidManifest.xml中添加所需权限:
<uses-permission android:name="android.permission.INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
3. 修正下载后的安装逻辑
将耗尽的网络输入流替换为本地文件的输入流,确保安装时有可用的APK数据:
override fun doInBackground(vararg p0: String?): Boolean { var flag = false try { val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/" var outputFile = File("$path$fileName.apk") var repetition = 1 while (outputFile.exists()) { outputFile = File("$path$fileName ($repetition).apk") repetition++ } val directory = File(path) if (!directory.exists()) { directory.mkdirs() } val url = URL(downloadUrl) val c = url.openConnection() as HttpURLConnection c.requestMethod = "GET" c.connect() val fos = FileOutputStream(outputFile) val inputStream = c.inputStream val totalSize = c.contentLength.toFloat() val buffer = ByteArray(1024) var len1: Int var per: Float var downloaded = 0f while (inputStream.read(buffer).also { len1 = it } != -1) { fos.write(buffer, 0, len1) downloaded += len1 per = (downloaded * 100 / totalSize) publishProgress(per.toInt()) } // 关闭已耗尽的网络流和文件输出流 fos.close() inputStream.close() // 使用本地文件的输入流执行安装 val fileInputStream = FileInputStream(outputFile) flag = installPackage(context, fileInputStream, "com.nurse.carenurse") fileInputStream.close() } catch (e: MalformedURLException) { Log.e("DownloadApk", "更新错误: " + e.message) flag = false } catch (e: IOException) { e.printStackTrace() flag = false } return flag }
4. 修改安装方法,禁用用户交互
在PackageInstaller的Session参数中添加USER_ACTION_NOT_REQUIRED,确保安装过程无需用户确认:
fun installPackage(context: Context, `in`: InputStream, packageName: String?): Boolean { val packageInstaller = context.packageManager.packageInstaller val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) params.setAppPackageName(packageName) // 关键配置:告知系统不需要用户确认 params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED) return try { val sessionId = packageInstaller.createSession(params) val session = packageInstaller.openSession(sessionId) val out = session.openWrite("KioskUpdate", 0, -1) val buffer = ByteArray(65536) var c: Int while (`in`.read(buffer).also { c = it } != -1) { out.write(buffer, 0, c) } session.fsync(out) `in`.close() out.close() // 提交安装请求 val intentSender = createIntentSender(context, sessionId) session.commit(intentSender) true } catch (e: IOException) { Log.e("InstallApk", "安装错误: " + e.message) false } } private fun createIntentSender(context: Context, sessionId: Int): IntentSender { val ACTION_INSTALL_COMPLETE = "com.nurse.carenurse.ACTION_INSTALL_COMPLETE" val pendingIntent = PendingIntent.getBroadcast( context, sessionId, Intent(ACTION_INSTALL_COMPLETE), PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) return pendingIntent.intentSender }
额外注意点
- Android 11+ 建议将APK下载到应用私有目录,避免申请外部存储权限。
- 安装完成后,可监听
ACTION_INSTALL_COMPLETE广播,触发Kiosk应用的重启逻辑。
内容的提问来源于stack exchange,提问作者Maksood
相关产品推荐
相关产品推荐

