带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中的应用,除了权限,还需处理安装后的应用列表刷新:
- 修正PendingIntent参数:用会话ID作为请求码,避免广播冲突
- 安装完成后触发应用扫描:通知系统更新应用信息,让启动器识别图标
完整安装代码示例:
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
相关产品推荐
相关产品推荐

