You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何静默更新Kiosk模式下的设备所有者托管Android应用

Kiosk启动器静默更新解决方案(设备所有者应用)

可以实现无需用户确认的静默更新——因为设备所有者应用拥有系统级的静默安装权限,只要修正当前代码的问题并正确利用权限即可。

你的代码存在的问题

  1. 下载完成后传入installPackage的网络输入流已经被读取完毕(数据已写入本地文件),无法再用来读取APK内容。
  2. 未设置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 08:35:17