Android Kotlin监听APK安装状态 跨页面同步方案咨询
解决Android全局监听APK安装状态的最优方案
针对你开发应用商店时遇到的「跨页面监听APK安装成功/失败/取消状态」问题,以下是两种最优实现方案,优先推荐使用官方现代API:
方案一:PackageInstaller API + 全局状态容器(ViewModel + LiveData)
PackageInstaller是Android官方推荐的安装API,能完整监听安装全流程(成功、失败、用户取消),结合全局ViewModel可实现跨页面状态同步。
1. 定义全局状态容器
创建一个全局ViewModel,用于保存和分发安装状态:
class AppViewModel(application: Application) : AndroidViewModel(application) { // 用LiveData分发安装状态 val installStatus = MutableLiveData<InstallStatus>() // 密封类定义所有可能的安装状态 sealed class InstallStatus { object Success : InstallStatus() // 安装成功 object Canceled : InstallStatus() // 用户取消 data class Failed(val errorCode: Int) : InstallStatus() // 安装失败(带错误码) } }
在Application类中初始化全局ViewModel实例:
class MyApp : Application() { lateinit var appViewModel: AppViewModel override fun onCreate() { super.onCreate() appViewModel = ViewModelProvider( this, ViewModelProvider.AndroidViewModelFactory.getInstance(this) )[AppViewModel::class.java] } }
2. 实现安装状态监听回调
创建PackageInstaller.SessionCallback监听安装全流程:
class InstallSessionCallback( private val appViewModel: AppViewModel, private val packageInstaller: PackageInstaller ) : PackageInstaller.SessionCallback() { override fun onCreated(sessionId: Int) {} override fun onBadgingChanged(sessionId: Int) {} override fun onActiveChanged(sessionId: Int, active: Boolean) {} override fun onProgressChanged(sessionId: Int, progress: Float) {} override fun onFinished(sessionId: Int, success: Boolean) { when { success -> appViewModel.installStatus.postValue(AppViewModel.InstallStatus.Success) else -> { val sessionInfo = packageInstaller.getSessionInfo(sessionId) // 判断是否为用户取消安装 val isCanceled = sessionInfo?.isActive == false if (isCanceled) { appViewModel.installStatus.postValue(AppViewModel.InstallStatus.Canceled) } else { appViewModel.installStatus.postValue(AppViewModel.InstallStatus.Failed(-1)) } } } // 监听完成后注销回调 packageInstaller.unregisterSessionCallback(this) } }
3. 发起APK安装请求
封装安装方法,写入APK文件并启动安装流程:
fun startApkInstall(apkUri: Uri, context: Context) { val packageInstaller = context.packageManager.packageInstaller val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) val sessionId = packageInstaller.createSession(params) val session = packageInstaller.openSession(sessionId) // 将APK写入安装会话 context.contentResolver.openInputStream(apkUri)?.use { inputStream -> session.openWrite("apk", 0, -1).use { outputStream -> inputStream.copyTo(outputStream) session.fsync(outputStream) } } // 注册安装状态回调 val appViewModel = (context.applicationContext as MyApp).appViewModel val callback = InstallSessionCallback(appViewModel, packageInstaller) packageInstaller.registerSessionCallback(callback, Handler(Looper.getMainLooper())) // 发起安装请求 val intent = Intent(context, InstallResultReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( context, sessionId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) session.commit(pendingIntent.intentSender) session.close() } // 可选:用BroadcastReceiver补充接收安装结果(也可仅依赖SessionCallback) class InstallResultReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { val status = intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) val appViewModel = (context?.applicationContext as MyApp).appViewModel when (status) { PackageInstaller.STATUS_SUCCESS -> { appViewModel.installStatus.postValue(AppViewModel.InstallStatus.Success) } PackageInstaller.STATUS_FAILURE_USER_CANCELED -> { appViewModel.installStatus.postValue(AppViewModel.InstallStatus.Canceled) } else -> { val errorCode = intent?.getIntExtra(PackageInstaller.EXTRA_STATUS_CODE, -1) ?: -1 appViewModel.installStatus.postValue(AppViewModel.InstallStatus.Failed(errorCode)) } } } }
4. 在任意页面监听状态更新
在需要更新UI的Activity/Fragment中观察ViewModel的状态:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val appViewModel = (application as MyApp).appViewModel appViewModel.installStatus.observe(this) { status -> when (status) { is AppViewModel.InstallStatus.Success -> { // 处理安装成功UI更新,比如弹窗、刷新列表 Toast.makeText(this, "APK安装成功", Toast.LENGTH_SHORT).show() } is AppViewModel.InstallStatus.Canceled -> { Toast.makeText(this, "用户取消安装", Toast.LENGTH_SHORT).show() } is AppViewModel.InstallStatus.Failed -> { Toast.makeText(this, "安装失败,错误码:${status.errorCode}", Toast.LENGTH_SHORT).show() } } } }
方案二:registerForActivityResult + 全局ViewModel(兼容旧API)
如果暂时无法迁移到PackageInstaller,可使用替代方案:用registerForActivityResult(替代已废弃的startActivityForResult)发起安装,再通过全局ViewModel分发结果。
1. 发起安装请求
在任意页面注册安装请求的Launcher:
private val installLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> val appViewModel = (application as MyApp).appViewModel when (result.resultCode) { Activity.RESULT_OK -> { appViewModel.installStatus.postValue(AppViewModel.InstallStatus.Success) } Activity.RESULT_CANCELED -> { appViewModel.installStatus.postValue(AppViewModel.InstallStatus.Canceled) } else -> { appViewModel.installStatus.postValue(AppViewModel.InstallStatus.Failed(result.resultCode)) } } } // 启动安装的方法 fun startApkInstall(apkUri: Uri) { val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply { data = apkUri flags = Intent.FLAG_GRANT_READ_URI_PERMISSION } installLauncher.launch(intent) }
2. 监听状态更新
和方案一一样,在任意Activity/Fragment中观察appViewModel.installStatus即可更新UI。
关键注意事项
- 权限处理:确保APK Uri有正确的读取权限,Android 10+需注意存储权限或使用MediaStore获取文件。
- PendingIntent Flags:Android 12+必须添加
FLAG_IMMUTABLE或FLAG_MUTABLE,避免崩溃。 - 页面生命周期:使用LiveData自动处理生命周期,避免内存泄漏。
内容的提问来源于stack exchange,提问作者HieuNguyen
相关产品推荐
相关产品推荐

