Android 15(SDK v35)下如何用Kotlin结合PackageManager API获取已安装应用列表(含系统/用户应用)
Android 15(SDK v35)下如何用Kotlin结合PackageManager API获取已安装应用列表(含系统/用户应用)
我来一步步帮你理清思路,你遇到的问题其实是很多刚从Compose入门进阶的开发者都会碰到的——PackageManager的文档确实有点“高冷”,加上Android 11+的权限变化,很容易绕晕。咱们从最基础的配置开始,一步步实现你的需求。
第一步:修正你的AndroidManifest配置
首先你之前的Manifest写法有个关键错误:QUERY_ALL_PACKAGES是权限声明,不是<queries>标签里的内容。<queries>是用来声明你需要访问的特定包名的,而你要获取所有应用,必须用QUERY_ALL_PACKAGES权限。正确的配置应该是这样:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <!-- 声明获取所有应用列表的权限 --> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" /> <!-- 如果是普通应用,不需要下面的<queries>;如果只需要特定应用,才用<queries>指定包名 --> <!-- <queries> <package android:name="com.example.someapp" /> </queries> --> <application ...> ... </application> </manifest>
注意:如果你的应用是系统应用,通常默认会有这个权限,不需要用户授权;但如果是普通应用,Google Play会要求你说明使用这个权限的理由,自己开发自用的话直接加就行。
第二步:创建数据类封装应用信息
PackageManager返回的ApplicationInfo对象包含很多底层字段,直接在Compose里用会很麻烦,咱们先创建一个自己的数据类,只封装你需要展示的核心信息:
import android.graphics.drawable.Drawable data class InstalledApp( val displayName: String, // 应用显示名称(比如"微信") val packageName: String, // 应用包名(比如"com.tencent.mm") val icon: Drawable?, // 应用图标 val isSystemApp: Boolean // 是否是系统应用 )
这样后续在Compose里使用时字段清晰,也避免直接依赖底层API的生命周期。
第三步:实现获取应用列表的核心逻辑
接下来咱们写一个工具函数,用PackageManager获取所有已安装应用,并转换成咱们的InstalledApp列表:
import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager fun getInstalledApps(context: Context): List<InstalledApp> { val packageManager = context.packageManager // 获取所有已安装的应用(包括系统和用户应用) // 参数0表示获取基础信息,如需元数据可传PackageManager.GET_META_DATA val installedApps = packageManager.getInstalledApplications(0) return installedApps.mapNotNull { appInfo -> // 获取应用显示名称,处理可能的空值 val displayName = appInfo.loadLabel(packageManager).toString() // 包名是ApplicationInfo自带的核心字段 val packageName = appInfo.packageName // 获取应用图标 val icon = appInfo.loadIcon(packageManager) // 判断是否是系统应用: // FLAG_SYSTEM = 预装系统应用;FLAG_UPDATED_SYSTEM_APP = 系统应用被用户更新过 val isSystemApp = (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0 || (appInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 InstalledApp(displayName, packageName, icon, isSystemApp) } }
这里的关键细节:
getInstalledApplications(0):返回设备上所有已安装应用的基础信息列表loadLabel(packageManager):加载应用的用户可见名称(对应Manifest里的android:label)flags判断系统应用:这是Android区分系统/用户应用的标准方式,覆盖了“原生系统应用”和“被更新的系统应用”两种情况
第四步:在Compose中集成并展示
现在回到你的Composable函数,咱们需要解决三个核心问题:
- 安全获取Context(你用
LocalContext.current完全正确) - 后台线程执行耗时操作(避免阻塞主线程卡顿)
- 用Compose状态管理自动更新界面
完整的Compose代码如下:
import android.content.Context import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.rememberDrawablePainter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @Composable fun AppSelection(modifier: Modifier = Modifier) { val context = LocalContext.current // 用mutableStateOf存储应用列表,Compose会监听变化自动重组界面 val installedApps = remember { mutableStateOf(emptyList<InstalledApp>()) } // 获取协程作用域,用来启动后台任务 val coroutineScope = rememberCoroutineScope() // 当Composable首次加载时,启动后台线程获取应用列表 LaunchedEffect(key1 = context) { coroutineScope.launch(Dispatchers.IO) { // 后台线程执行耗时操作 val apps = getInstalledApps(context) // 切换回主线程更新State withContext(Dispatchers.Main) { installedApps.value = apps } } } // 用LazyColumn展示列表 LazyColumn(modifier = modifier.fillMaxSize()) { items(installedApps.value) { app -> AppItem(app = app) } } } @Composable fun AppItem(app: InstalledApp) { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { // 展示应用图标:把Drawable转换成Compose支持的Painter app.icon?.let { drawable -> val painter = rememberDrawablePainter(drawable = drawable) Image( painter = painter, contentDescription = app.displayName, modifier = Modifier.size(48.dp) ) } ?: run { // 无图标时的占位符 Box( modifier = Modifier .size(48.dp) .background(color = androidx.compose.material3.MaterialTheme.colorScheme.surfaceVariant), contentAlignment = Alignment.Center ) { Text(text = "无图标", fontSize = 10.sp) } } Spacer(modifier = Modifier.width(16.dp)) Column(modifier = Modifier.weight(1f)) { Text( text = app.displayName, fontWeight = FontWeight.Bold, fontSize = 16.sp ) Text( text = app.packageName, fontSize = 12.sp, color = androidx.compose.material3.MaterialTheme.colorScheme.onSurfaceVariant ) Text( text = if (app.isSystemApp) "系统应用" else "用户应用", fontSize = 10.sp, color = if (app.isSystemApp) androidx.compose.material3.MaterialTheme.colorScheme.primary else androidx.compose.material3.MaterialTheme.colorScheme.secondary ) } } }
关键知识点解释
- LocalContext的使用:完全正确!Composable无法直接访问Context,必须通过
LocalContext.current获取,注意不要在Composable之外长期持有这个引用,避免内存泄漏。 - 线程处理:获取应用列表是同步操作,若应用数量多会阻塞主线程,所以用
Dispatchers.IO在后台执行,再切回主线程更新State,保证界面流畅。 - Compose状态管理:
mutableStateOf是Compose的核心状态容器,当你更新value时,Compose会自动重组使用该状态的UI部分(也就是LazyColumn会自动刷新)。 - 空值处理:用
mapNotNull和?:处理了loadLabel/loadIcon可能返回空的情况,避免空指针异常。
补充说明
- 如需更多应用信息(比如版本号、安装时间),可以在
getInstalledApps里调用packageManager.getPackageInfo(appInfo.packageName, 0)获取PackageInfo对象,里面包含更详细的应用数据。 - 作为系统应用,你还能获取普通应用无法访问的信息(比如应用安装来源),但需要对应的系统权限。
- 若不需要区分系统/用户应用,直接移除
isSystemApp相关逻辑即可。
这样你就能完整获取并展示所有已安装应用的列表了,有细节问题随时问!




