You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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函数,咱们需要解决三个核心问题:

  1. 安全获取Context(你用LocalContext.current完全正确)
  2. 后台线程执行耗时操作(避免阻塞主线程卡顿)
  3. 用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
            )
        }
    }
}

关键知识点解释

  1. LocalContext的使用:完全正确!Composable无法直接访问Context,必须通过LocalContext.current获取,注意不要在Composable之外长期持有这个引用,避免内存泄漏。
  2. 线程处理:获取应用列表是同步操作,若应用数量多会阻塞主线程,所以用Dispatchers.IO在后台执行,再切回主线程更新State,保证界面流畅。
  3. Compose状态管理mutableStateOf是Compose的核心状态容器,当你更新value时,Compose会自动重组使用该状态的UI部分(也就是LazyColumn会自动刷新)。
  4. 空值处理:用mapNotNull?:处理了loadLabel/loadIcon可能返回空的情况,避免空指针异常。

补充说明

  • 如需更多应用信息(比如版本号、安装时间),可以在getInstalledApps里调用packageManager.getPackageInfo(appInfo.packageName, 0)获取PackageInfo对象,里面包含更详细的应用数据。
  • 作为系统应用,你还能获取普通应用无法访问的信息(比如应用安装来源),但需要对应的系统权限。
  • 若不需要区分系统/用户应用,直接移除isSystemApp相关逻辑即可。

这样你就能完整获取并展示所有已安装应用的列表了,有细节问题随时问!

火山引擎 最新活动