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

带INSTALL_PACKAGES权限的Root设备安装器APP无启动图标问题

问题分析与解决方案

一、启动器不显示的核心原因

手动将应用复制到system/priv-app后,系统虽会将其识别为系统应用,但未触发PackageManager的应用扫描流程,导致启动器无法读取到应用的桌面图标配置信息。另外,你当前的PackageInstaller代码存在关键参数错误,也会影响安装行为。

二、PackageInstaller代码的关键修正

你代码里的params.setAppPackageName(packageName)是错误的——这里应该传入要安装的目标APK的包名,而非自身应用的包名。如果不知道目标APK的包名,可以先解析APK获取:

// 解析目标APK的包名(兼容旧Android版本)
val packageInfo = packageManager.getPackageArchiveInfo(apk.path, PackageManager.GET_ACTIVITIES)
    ?: throw IllegalArgumentException("无效的APK文件")
val targetPackageName = packageInfo.packageName

修正后的SessionParams配置:

val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL).apply {
    setAppPackageName(targetPackageName)
    // 旧系统设备上,系统应用强制安装在内部存储
    setInstallLocation(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY)
}

三、系统应用下的完整安装流程

作为system/priv-app中的应用,除了权限,还需处理安装后的应用列表刷新:

  1. 修正PendingIntent参数:用会话ID作为请求码,避免广播冲突
  2. 安装完成后触发应用扫描:通知系统更新应用信息,让启动器识别图标

完整安装代码示例:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    try {
        val apk = File(Environment.getExternalStorageDirectory(), "sample_app.apk")
        Log.d("MainActivity", "APK路径: ${apk.path}")

        // 解析目标APK包名
        val packageInfo = packageManager.getPackageArchiveInfo(apk.path, 0)
            ?: throw IllegalArgumentException("无效APK")
        val targetPackageName = packageInfo.packageName

        val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL).apply {
            setAppPackageName(targetPackageName)
            setInstallLocation(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY)
        }

        val sessionId = packageManager.packageInstaller.createSession(params)
        val session = packageManager.packageInstaller.openSession(sessionId)

        // 写入APK文件到会话
        apk.inputStream().use { apkStream ->
            session.openWrite("apk-install", 0, apk.length()).use { sessionStream ->
                Log.d("MainActivity", "复制APK中...")
                apkStream.copyTo(sessionStream)
                Log.d("MainActivity", "同步文件...")
                session.fsync(sessionStream)
            }
        }

        // 配置安装结果广播
        val intent = Intent(this, InstallReceiver::class.java).apply {
            putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, targetPackageName)
            putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId)
        }
        // 旧Android版本(<12)替换FLAG_IMMUTABLE为FLAG_UPDATE_CURRENT
        val piFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT
        }
        val pi = PendingIntent.getBroadcast(this, sessionId, intent, piFlags)

        Log.d("MainActivity", "提交安装会话...")
        session.commit(pi.intentSender)
        session.close()
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

配套的广播接收器(处理安装结果并刷新应用列表):

class InstallReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)
        val targetPackageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME) ?: return

        if (status == PackageInstaller.STATUS_SUCCESS) {
            // 发送广播刷新启动器应用列表
            context.sendBroadcast(Intent(Intent.ACTION_PACKAGE_ADDED).apply {
                data = Uri.parse("package:$targetPackageName")
                flags = Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
            })
            Log.d("InstallReceiver", "应用安装成功: $targetPackageName")
        } else {
            val errorMsg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) ?: "未知错误"
            Log.e("InstallReceiver", "安装失败: $errorMsg")
        }
    }
}

四、手动移到system/priv-app后的启动器显示修复

如果你已经手动将自身应用移到system/priv-app,可以通过以下方式让启动器识别:

  • 重启设备后,在应用内通过Root权限执行一次pm install -r /system/priv-app/YourApp/YourApp.apk(仅需一次,之后用SDK方案)
  • 或者通过反射调用PackageManager的扫描方法(Root设备可用):
// 反射触发应用扫描,更新自身应用信息
fun scanSelfApp(context: Context) {
    val pm = context.packageManager
    try {
        val method = pm.javaClass.getMethod(
            "scanPackage",
            File::class.java,
            Int::class.java,
            String::class.java,
            Int::class.java
        )
        val selfApkPath = context.applicationInfo.sourceDir
        method.invoke(pm, File(selfApkPath), 0, null, 0)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

内容的提问来源于stack exchange,提问作者tyczj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 21:15:15