Android开发踩坑记录
做Android开发多年,从权限管理到应用上架,踩了不少坑。记录一下实战经验和常见问题的解决方案。
危险权限管理
危险权限列表
Android 6.0+引入了运行时权限,危险权限涉及用户隐私,需要动态请求。
| 权限组 |
危险权限 |
| CALENDAR |
READ_CALENDAR, WRITE_CALENDAR |
| CAMERA |
CAMERA |
| CONTACTS |
READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS |
| LOCATION |
ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION |
| MICROPHONE |
RECORD_AUDIO |
| PHONE |
READ_PHONE_STATE, CALL_PHONE, READ_CALL_LOG |
| SENSORS |
BODY_SENSORS |
| SMS |
SEND_SMS, RECEIVE_SMS, READ_SMS |
| STORAGE |
READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE |
坑1:Android 10+存储权限变化
Android 10引入了Scoped Storage,存储权限行为发生变化:
1 2 3 4 5 6 7 8 9 10 11
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { } else { if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { } }
|
运行时权限请求
AndroidManifest.xml声明:
1 2 3 4 5 6 7 8
| <manifest> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" /> </manifest>
|
Kotlin权限请求代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class MainActivity : AppCompatActivity() {
companion object { private const val PERMISSION_REQUEST_CODE = 100 private val REQUIRED_PERMISSIONS = mutableListOf( Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION ).apply { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { add(Manifest.permission.WRITE_EXTERNAL_STORAGE) } }.toTypedArray() }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) checkAndRequestPermissions() }
private fun checkAndRequestPermissions() { val permissionsToRequest = REQUIRED_PERMISSIONS.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }
if (permissionsToRequest.isNotEmpty()) { ActivityCompat.requestPermissions( this, permissionsToRequest.toTypedArray(), PERMISSION_REQUEST_CODE ) } }
override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_CODE) { grantResults.forEachIndexed { index, result -> if (result == PackageManager.PERMISSION_GRANTED) { handlePermissionGranted(permissions[index]) } else { handlePermissionDenied(permissions[index]) } } } } }
|
Activity Result API(推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class MainActivity : AppCompatActivity() {
private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> if (isGranted) { } else { } }
private fun requestCameraPermission() { when { ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED -> { } shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> { showRationaleDialog() } else -> { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } } }
|
权限申请最佳时机
| 场景 |
最佳时机 |
| 存储权限 |
用户点击”保存”按钮时 |
| 相机权限 |
打开相机前 |
| 位置权限 |
需要定位功能时 |
| 联系人权限 |
导入联系人时 |
| 短信权限 |
验证码自动填充时 |
应用自启动配置
坑2:国内系统自启动限制
国内Android系统(小米、华为、OPPO、vivo等)默认限制应用后台运行,用户需要手动开启”自启动”权限。
AutoStarter库使用
1 2 3
| dependencies { implementation 'com.github.judemanutd:autostarter:1.1.0' }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) checkAutoStartPermission() }
private fun checkAutoStartPermission() { val helper = AutoStartPermissionHelper.getInstance()
if (helper.isAutoStartPermissionAvailable(this)) { if (!helper.getAutoStartPermission(this)) { showAutoStartDialog() } } }
private fun showAutoStartDialog() { AlertDialog.Builder(this) .setTitle("需要自启动权限") .setMessage("为保证消息推送及时送达,请在设置中开启自启动权限") .setPositiveButton("去设置") { _, _ -> AutoStartPermissionHelper.getInstance().getAutoStartPermission(this) } .setNegativeButton("取消", null) .show() } }
|
各厂商自启动设置跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| object AutoStartUtils {
fun openAutoStartSettings(context: Context) { when (getDeviceManufacturer()) { "xiaomi" -> tryXiaomi(context) "huawei" -> tryHuawei(context) "oppo" -> tryOppo(context) "vivo" -> tryVivo(context) else -> tryGeneric(context) } }
private fun getDeviceManufacturer(): String { return Build.MANUFACTURER.lowercase() }
private fun tryXiaomi(context: Context) { try { val intent = Intent() intent.component = ComponentName( "com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity" ) context.startActivity(intent) } catch (e: Exception) { tryGeneric(context) } }
private fun tryHuawei(context: Context) { try { val intent = Intent() intent.component = ComponentName( "com.huawei.systemmanager", "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity" ) context.startActivity(intent) } catch (e: Exception) { tryGeneric(context) } }
private fun tryGeneric(context: Context) { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) intent.data = Uri.parse("package:${context.packageName}") context.startActivity(intent) } }
|
邓白氏编码申请
坑3:邓白氏编码申请流程复杂
苹果开发者企业账号需要邓白氏编码,申请流程比较繁琐。
申请步骤:
联系苹果支持:
- 访问 Apple Developer Contact
- 选择”Membership and Account” → “D-U-N-S Number”
- 填写表格提交申请
等待苹果邮件(约1个工作日):
- 收到来自
chinadev@apple.com 的邮件
回复邮件提供企业信息:
1 2 3 4 5 6 7 8 9 10 11
| 企业英文名称 [法人实体名称]:(如果是有限责任公司,英文名必须以 CO., LTD. 结尾) 企业中文名称: 税号或企业登记编号: 实际地址: 城市: 省/自治区/直辖市: 邮政编码: 电话号码: 国家或地区: 工作电话号码: 工作电子邮件:
|
邓白氏官网提交资料:
等待审核通过(约1个工作日)
注意事项
- 公司英文名必须以”CO., LTD.”结尾(有限责任公司)
- 所有信息必须与营业执照完全一致
- 工作邮箱必须是公司分配的企业邮箱
- 整个流程通常需要5-10个工作日
- 一个公司只能申请一个邓白氏编码
开发者账号类型
| 类型 |
价格 |
适用对象 |
App Store上架 |
| 个人账号 |
$99/年 |
独立开发者 |
可以 |
| 公司账号 |
$99/年 |
企业/组织 |
可以 |
| 企业账号 |
$299/年 |
大型企业 |
不可以(内部使用) |
Google Play上架
应用签名
1 2
| keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias
|
build.gradle配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| android { signingConfigs { release { storeFile file("my-release-key.jks") storePassword "password" keyAlias "my-alias" keyPassword "password" } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
|
上架准备
- 注册Google Play开发者账号($25一次性费用)
- 准备应用签名密钥
- 生成应用包(AAB格式)
- 编写隐私政策文档
- 填写内容分级问卷
- 准备应用截图和描述
国内应用商店上架
| 商店 |
官网 |
审核周期 |
| 华为应用市场 |
appgallery.huawei.com |
1-3天 |
| 小米应用商店 |
dev.mi.com |
1-2天 |
| OPPO应用商店 |
open.oppomobile.com |
2-3天 |
| vivo应用商店 |
dev.vivo.com.cn |
2-3天 |
| 腾讯应用宝 |
open.qq.com |
1-3天 |
| 百度手机助手 |
app.baidu.com |
1-2天 |
通用上架材料:
- APK/AAB文件
- 应用图标(多尺寸)
- 应用截图(5-8张)
- 应用描述
- 隐私政策链接
- ICP备案号(如涉及网络服务)
- 软件著作权证书(推荐)
坑4:权限被拒绝处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| fun handlePermissionDenied(context: Activity, permission: String) { when { ActivityCompat.shouldShowRequestPermissionRationale(context, permission) -> { AlertDialog.Builder(context) .setTitle("需要权限") .setMessage("该功能需要访问您的存储空间来保存文件") .setPositiveButton("授权") { _, _ -> ActivityCompat.requestPermissions( context, arrayOf(permission), PERMISSION_REQUEST_CODE ) } .setNegativeButton("取消", null) .show() } else -> { AlertDialog.Builder(context) .setTitle("权限已被拒绝") .setMessage("请手动开启权限:设置 > 应用 > 权限") .setPositiveButton("去设置") { _, _ -> val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) intent.data = Uri.parse("package:${context.packageName}") context.startActivity(intent) } .show() } } }
|
总结
Android开发常见坑点:
- Android 10+存储权限要用Scoped Storage
- 国内系统要处理自启动权限,各厂商跳转页面不同
- 邓白氏编码申请要准备完整企业信息,英文名格式要注意
- 权限申请时机要合理,不要启动就申请一堆权限
- Google Play上架要用AAB格式,配置好签名
- 国内商店上架需要ICP备案号和软著
Android权限管理和上架流程越来越规范,但国内系统适配还是要多测试。建议多准备几套UI文案,引导用户开启必要权限。