Android技术问询:如何列出所有已安装应用并获取及展示各应用总运行时长
Android 实现列出所有已安装应用并展示运行时长
一、声明必要权限
首先在AndroidManifest.xml中添加特殊权限:
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
该权限无法通过常规动态申请流程获取,需引导用户跳转到系统设置开启:
private fun checkUsageStatsPermission() { val appOpsManager = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager val mode = appOpsManager.checkOpNoThrow( AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), packageName ) val hasPermission = mode == AppOpsManager.MODE_ALLOWED if (!hasPermission) { startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)) } }
二、获取已安装应用列表
通过PackageManager获取所有已安装应用的基础信息,可按需过滤系统应用或禁用应用:
private fun getInstalledApps(): List<AppInfo> { val appList = mutableListOf<AppInfo>() val packageManager = packageManager val packages = packageManager.getInstalledPackages(PackageManager.GET_META_DATA) for (packageInfo in packages) { // 可选:跳过系统应用 val isSystemApp = packageInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0 if (isSystemApp) continue // 可选:跳过已禁用应用 val isEnabled = packageManager.getApplicationEnabledSetting(packageInfo.packageName).let { it == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || it == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT } if (!isEnabled) continue val appName = packageManager.getApplicationLabel(packageInfo.applicationInfo).toString() val appIcon = packageManager.getApplicationIcon(packageInfo.applicationInfo) val packageName = packageInfo.packageName appList.add(AppInfo(appName, packageName, appIcon, 0)) } return appList } // 数据类存储应用信息 data class AppInfo( val appName: String, val packageName: String, val appIcon: Drawable, var totalUsageTime: Long // 运行时长(毫秒) )
三、获取应用运行时长
使用UsageStatsManager(API 21+)查询应用使用统计,计算总运行时长:
private fun getAppUsageTime(appList: List<AppInfo>): List<AppInfo> { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // 低版本设备无法获取时长,直接返回原列表 return appList } val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager val endTime = System.currentTimeMillis() val startTime = endTime - 7 * 24 * 60 * 60 * 1000 // 查询过去7天的数据,可自定义时间范围 val usageStatsList = usageStatsManager.queryUsageStats( UsageStatsManager.INTERVAL_DAILY, startTime, endTime ) val usageMap = mutableMapOf<String, Long>() usageStatsList.forEach { stats -> // 累加前台+可见时长,可按需选择单一维度 val totalTime = stats.totalTimeInForeground + stats.totalTimeVisible usageMap[stats.packageName] = usageMap.getOrDefault(stats.packageName, 0) + totalTime } return appList.map { appInfo -> appInfo.copy(totalUsageTime = usageMap.getOrDefault(appInfo.packageName, 0)) } }
四、展示应用信息
用RecyclerView实现列表展示,先创建item布局item_app_info.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:orientation="horizontal" android:gravity="center_vertical"> <ImageView android:id="@+id/iv_app_icon" android:layout_width="48dp" android:layout_height="48dp" android:scaleType="centerCrop"/> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:layout_marginStart="16dp"> <TextView android:id="@+id/tv_app_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:textStyle="bold"/> <TextView android:id="@+id/tv_package_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp" android:textColor="@android:color/darker_gray" android:layout_marginTop="4dp"/> </LinearLayout> <TextView android:id="@+id/tv_usage_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="@android:color/holo_blue_dark"/> </LinearLayout>
再实现适配器AppInfoAdapter:
class AppInfoAdapter(private val appList: List<AppInfo>) : RecyclerView.Adapter<AppInfoAdapter.ViewHolder>() { inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val ivAppIcon: ImageView = itemView.findViewById(R.id.iv_app_icon) val tvAppName: TextView = itemView.findViewById(R.id.tv_app_name) val tvPackageName: TextView = itemView.findViewById(R.id.tv_package_name) val tvUsageTime: TextView = itemView.findViewById(R.id.tv_usage_time) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_app_info, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val appInfo = appList[position] holder.ivAppIcon.setImageDrawable(appInfo.appIcon) holder.tvAppName.text = appInfo.appName holder.tvPackageName.text = appInfo.packageName holder.tvUsageTime.text = formatUsageTime(appInfo.totalUsageTime) } override fun getItemCount(): Int = appList.size // 将毫秒转换为友好的时分秒格式 private fun formatUsageTime(millis: Long): String { val seconds = (millis / 1000) % 60 val minutes = (millis / (1000 * 60)) % 60 val hours = (millis / (1000 * 60 * 60)) % 24 val days = millis / (1000 * 60 * 60 * 24) return when { days > 0 -> "${days}天${hours}时" hours > 0 -> "${hours}时${minutes}分" minutes > 0 -> "${minutes}分${seconds}秒" else -> "${seconds}秒" } } }
最后在Activity中初始化并加载数据:
class MainActivity : AppCompatActivity() { private lateinit var recyclerView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) recyclerView = findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) checkUsageStatsPermission() // 延迟执行,确保用户完成权限授权 Handler(Looper.getMainLooper()).postDelayed({ loadAppData() }, 2000) } private fun loadAppData() { // 建议放在子线程执行,避免阻塞主线程 CoroutineScope(Dispatchers.IO).launch { val appList = getInstalledApps() val appListWithUsage = getAppUsageTime(appList) withContext(Dispatchers.Main) { recyclerView.adapter = AppInfoAdapter(appListWithUsage) } } } // 上述checkUsageStatsPermission、getInstalledApps、getAppUsageTime方法放在此处 }
五、注意事项
- 版本兼容:
UsageStatsManager仅支持API 21及以上设备,低版本需做降级处理。 - 权限校验:必须确保用户开启使用统计权限,否则无法获取运行时长数据。
- 性能优化:获取应用列表和查询统计数据属于耗时操作,建议放在子线程执行。
内容的提问来源于stack exchange,提问作者RAMZI SHAIBAN
相关产品推荐
相关产品推荐

