Android 默认短信应用查询技术文档
1. 需求背景
开发一个功能,用于获取 Android 系统中当前默认短信应用的包名,并查询所有具备成为默认短信应用资格的应用列表。
2. 初始实现
2.1 基础方法
kotlin
fun getDefaultSmsAppPackageName(context: Context): String? {
return Telephony.Sms.getDefaultSmsPackage(context)
}
这是 Android 官方提供的 API,用于获取当前默认短信应用的包名。
3. 遇到的问题
3.1 Java 版本兼容性问题
问题:
Android Gradle Plugin 8.5.1 requires Java 17 to run.
You are currently using Java 11.
解决方案:
- 降级 Android Gradle Plugin 到 7.4.2(支持 Java 11)
- 或在
gradle.properties中设置org.gradle.java.home指向 Java 17+ 的路径
3.2 PackagingOptions DSL 兼容性
问题:
Unresolved reference: packaging
AGP 7.4.2 不支持新的 packaging {} DSL 语法。
解决方案:
kotlin
// 旧版本语法
packagingOptions {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
3.3 Settings.Secure 返回 null
问题:
log
From Settings.Secure: null
From Telephony.Sms: com.android.mms
Settings.Secure 中的 sms_default_application 键返回 null,但 Telephony.Sms API 能返回值。
原因:
- 某些 ROM 可能没有正确保存设置到 Settings.Secure
- 或者使用了不同的存储机制
解决方案:
同时使用两种方式,优先使用 Settings.Secure 的值:
kotlin
fun getDefaultSmsAppPackageName(context: Context): String? {
val fromSettings = Settings.Secure.getString(
context.contentResolver,
"sms_default_application"
)
val fromTelephony = Telephony.Sms.getDefaultSmsPackage(context)
return fromSettings?.takeIf { it.isNotEmpty() } ?: fromTelephony
}
3.4 切换默认应用后值不更新
问题:
使用 remember { mutableStateOf(...) } 缓存了初始值,切换默认应用后显示的还是旧值。
解决方案:
使用 remember(key) 方式,当 key 改变时重新获取:
kotlin
var refreshCounter by remember { mutableIntStateOf(0) }
val defaultSmsPackage = remember(refreshCounter) {
SmsUtils.getDefaultSmsAppPackageName(context)
}
3.5 无法查询到所有短信应用 ⭐核心问题
问题:
系统设置中有 2 个短信应用(短信、测试短信),但只能查询到 1 个。
log
具备资格的应用:
- com.vivo.easyshare (互传)
- com.android.mms (信息)
缺少 com.test.mms 。
原因分析:
通过 dumpsys package 可以看到这些应用确实注册了 sms: scheme:
sms:
1b41c51 com.test.mms/...ComposeSmsActivity
1189e3d com.android.mms/.ui.ComposeMessageActivity
8e016f1 com.vivo.easyshare/.activity.DefaultSmsActivity
但 queryIntentActivities 无法查询到它们。
根本原因:缺少 QUERY_ALL_PACKAGES 权限
在 Android 11(API 30)+ 中,引入了包可见性过滤。默认情况下,应用只能查询到以下内容:
- 自己
- 通过
<queries>声明的应用 - 具有相同签名的应用
解决方案:
在 AndroidManifest.xml 中添加:
xml
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
这个权限是特殊权限,不需要用户授权,但需要在 Google Play 审核时提供正当理由。
3.6 Intent 查询方式的限制
尝试了多种 Intent 查询方式:
kotlin
// 方式1: 使用 ACTION_VIEW + sms: scheme
Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("sms:")
addCategory(Intent.CATEGORY_DEFAULT)
}
// 方式2: 使用 ACTION_SENDTO + sms: scheme
Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("sms:")
addCategory(Intent.CATEGORY_DEFAULT)
}
// 方式3: 不指定 action,只用 scheme
Intent().apply {
data = Uri.parse("sms:")
addCategory(Intent.CATEGORY_DEFAULT)
}
即使添加了各种 category 组合,仍然无法查询到所有应用。
这说明某些应用的 intent-filter 配置可能比较特殊,或者使用了非标准的注册方式。
4. 最终实现
4.1 AndroidManifest.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 查询所有应用的权限 (Android 11+) -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application
...>
</application>
</manifest>
4.2 SmsUtils.kt
kotlin
object SmsUtils {
private const val TAG = "SmsUtils"
/**
* 获取当前默认短信应用的包名
*/
fun getDefaultSmsAppPackageName(context: Context): String? {
// 方式1: Settings.Secure
val fromSettings = Settings.Secure.getString(
context.contentResolver,
"sms_default_application"
)
Log.d(TAG, "From Settings.Secure: $fromSettings")
// 方式2: Telephony.Sms API
val fromTelephony = Telephony.Sms.getDefaultSmsPackage(context)
Log.d(TAG, "From Telephony.Sms: $fromTelephony")
// 优先使用 Settings.Secure 的值
val result = fromSettings?.takeIf { it.isNotEmpty() } ?: fromTelephony
Log.d(TAG, "Final result: $result")
return result
}
/**
* 获取所有具备资格成为默认短信应用的应用列表
*/
fun getEligibleSmsApps(context: Context): List<AppInfo> {
val apps = mutableListOf<AppInfo>()
val packageManager = context.packageManager
val seenPackages = mutableSetOf<String>()
try {
val intents = listOf(
Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("sms:")
addCategory(Intent.CATEGORY_DEFAULT)
addCategory(Intent.CATEGORY_BROWSABLE)
},
Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("sms:")
addCategory(Intent.CATEGORY_DEFAULT)
}
)
for (intent in intents) {
val resolveInfos = packageManager.queryIntentActivities(
intent,
PackageManager.MATCH_DEFAULT_ONLY
)
for (resolveInfo in resolveInfos) {
val packageName = resolveInfo.activityInfo.packageName
if (!seenPackages.contains(packageName)) {
seenPackages.add(packageName)
val appName = resolveInfo.loadLabel(packageManager).toString()
apps.add(AppInfo(packageName, appName))
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Error: ${e.message}")
}
return apps.sortedBy { it.name }
}
/**
* 打开系统默认短信应用设置页面
*/
fun openDefaultSmsAppSettings(context: Context) {
try {
val intent = Intent("android.settings.DEFAULT_APP_SETTINGS")
context.startActivity(intent)
} catch (e: Exception) {
val intent = Intent("android.settings.SETTINGS")
context.startActivity(intent)
}
}
data class AppInfo(val packageName: String, val name: String)
}
4.3 MainActivity.kt
kotlin
@Composable
fun SmsInfoScreen(modifier: Modifier = Modifier) {
val context = LocalContext.current
var refreshCounter by remember { mutableIntStateOf(0) }
var showDiagnosis by remember { mutableStateOf(false) }
val defaultSmsPackage = remember(refreshCounter) {
SmsUtils.getDefaultSmsAppPackageName(context)
}
val isDefaultApp = remember(refreshCounter) {
SmsUtils.isDefaultSmsApp(context)
}
val diagnosis = remember(refreshCounter) {
SmsUtils.diagnoseDefaultSmsApp(context)
}
Column(modifier = modifier.fillMaxSize().padding(16.dp)) {
Text("默认短信应用: ${defaultSmsPackage ?: "未设置"}")
Row {
Button(onClick = { SmsUtils.openDefaultSmsAppSettings(context) }) {
Text("打开设置")
}
Button(onClick = { refreshCounter++ }) {
Text("刷新")
}
Button(onClick = { showDiagnosis = !showDiagnosis }) {
Text(if (showDiagnosis) "隐藏诊断" else "显示诊断")
}
}
if (showDiagnosis) {
Text(diagnosis, style = MaterialTheme.typography.bodySmall)
}
}
}
5. 关键注意事项
5.1 QUERY_ALL_PACKAGES 权限
- 作用:允许应用查询设备上所有已安装的应用
- 适用版本:Android 11 (API 30) 及以上
- 授权方式:特殊权限,不需要用户运行时授权
- Google Play 政策:使用此权限需要提供正当的使用理由
5.2 默认短信应用的条件
一个应用要成为默认短信应用,必须满足:
-
必需权限:
SEND_SMSRECEIVE_SMSREAD_SMSRECEIVE_MMSRECEIVE_WAP_PUSH
-
必需组件:
- BroadcastReceiver 处理
SMS_DELIVERaction - BroadcastReceiver 处理
WAP_PUSH_DELIVERaction - Service 处理
RESPOND_VIA_MESSAGEaction
- BroadcastReceiver 处理
-
Intent Filter:
- 处理
ACTION_SENDTO与sms:或smsto:scheme
- 处理
5.3 调试命令
bash
# 查看所有处理短信的应用
adb shell dumpsys package | grep -A 5 "sms:"
# 查看指定应用的权限
adb shell dumpsys package <package_name> | grep -i permission
6. 总结
- Android 11+ 的包可见性限制是需要特别注意的问题
- QUERY_ALL_PACKAGES 权限是解决查询不到应用的关键
- Settings.Secure 和 Telephony.Sms 两种方式获取默认应用各有优缺点,建议结合使用
- remember 的使用方式会影响数据的实时更新,需要配合状态管理