嘿,朋友!欢迎来到Android开发的世界。我知道你此刻可能既兴奋又有点紧张——毕竟,从零开始构建一个能在真实设备上跑起来的应用,既像第一次做饭手忙脚乱,又像完成了一件很酷的手工作品。别担心,这篇文章就是为你准备的“厨房指南”。我们不讲那些枯燥的理论堆砌,而是像朋友聊天一样,一步步带你把第一个Kotlin Android应用跑起来,顺便把那些让人头秃的权限问题掰扯清楚。
为什么是Kotlin?为什么是现在?
在动手写代码之前,我想先让你心里有个底。 Google在2017年宣布Kotlin为Android开发的官方首选语言,这不是一时兴起。想象一下,你以前写Java要处理一堆NullPointerException(空指针异常),代码里到处都是样板代码,感觉像是在泥潭里走路。Kotlin就像给你换上了一双合脚的跑鞋——它更简洁、更安全,而且能无缝调用你所有的Java代码。
更重要的是,现在学习Kotlin一点都不晚。2023年及以后,新发布的Jetpack Compose声明式UI框架全面拥抱Kotlin,这意味着你用现代方式构建Android应用会越来越顺手。即使你现在是初学者,只要跟对节奏,就能站在巨人的肩膀上看得更远。
所以,深呼吸,打开你的Android Studio(如果你还没安装,去官网下一个最新稳定版,大概2-3GB,喝杯咖啡的功夫就好)。咱们开始这场小冒险。
第一步:建立你的HelloWorld——别小看这个起点
很多新手会跳过HelloWorld,觉得太简单。但我告诉你,这一步错了,后面全错。就像盖房子,地基歪了,楼肯定塌。
创建新项目的“甜蜜细节”
打开Android Studio,点击“New Project”。这时候你可能会遇到各种模板选择:Empty Activity、Navigation Drawer、Gallery之类的。对于初学者,我强烈建议选Empty Activity(空活动)。为什么?因为其他模板塞满了你不需要的代码,容易让你分心。我们要的是干净起步,自己一点一点加东西,这样你才能真正理解每个部分的作用。
填好这些字段时,有几个关键点要记牢:
- Name:就叫“FirstKotlinApp”,简单明了。
- Package name:用反域名格式,比如
com.yourname.firstkotlinapp。如果你没有域名,用com.example.firstkotlinapp也行,但记得后面别随意改,改了会乱。 - Save location:选一个好找的位置,比如
D:\AndroidProjects\FirstKotlinApp,别存到系统盘深处。 - Language:必须选Kotlin。如果选了Java,后面教程全乱。
- Minimum SDK:这里新手容易纠结。我建议选API 21: Android 5.0 (Lollipop)。理由是它覆盖了99%以上的活跃设备(截至2024年数据),而且支持所有现代特性。别选太低,否则你后面想用的很多API都用不了;也别选太高,否则有些老设备跑不起来。
点击“Finish”,Android Studio会开始同步Gradle(那个小进度条会转一会儿,耐心等)。同步完成后,你会看到项目结构。别被吓到,我们只看两个核心文件:
MainActivity.kt(在app/src/main/java/com/yourname/firstkotlinapp/里)activity_main.xml(在app/src/main/res/layout/里)
让HelloWorld真正“活”起来
传统方法是用XML写布局,现在流行Jetpack Compose,但作为初学者,我先带你走XML这条路,因为它能让你理解底层结构,之后转Compose会更快。
打开activity_main.xml,你会看到默认的ConstraintLayout,里面有个TextView显示“Hello World!”。直接运行?太无聊了。我们来加点互动:
修改activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textViewHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击按钮试试!"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/buttonClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点我"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@id/textViewHello"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
这里用了约束布局,把文本居中,按钮放在下面。看起来有点多代码,但约束布局是现在Android UI的标准,学会它,后面自由很多。
然后打开MainActivity.kt:
package com.yourname.firstkotlinapp
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
private lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textViewHello)
button = findViewById(R.id.buttonClick)
button.setOnClickListener {
// 点击时改变文字
textView.text = "你点啦!这是Kotlin的简洁魅力"
}
}
}
注意几个Kotlin特性:
lateinit var:表示这个变量稍后会初始化,避免null警告。setOnClickListener { ... }:Lambda表达式,比Java的匿名内部类简洁太多了。- 没有分号结尾,代码看起来更像自然语言。
用模拟器运行(AVD Manager里创建一个Pixel 5模拟器,API 30),点击按钮,文字变化——恭喜,你的第一个交互应用跑起来了!这个过程可能花半小时,但每一步你都亲手做了,记忆会深刻。
第二步:理解权限——Android的安全基石
现在应用能跑,但现实世界的应用不可能只玩单机。用户会要求访问相机、读取联系人、定位等等。这时,权限系统就登场了。很多人被权限搞疯,因为Android的权限机制从6.0版本开始从“安装时全授权”变成了“运行时动态申请”。这不是刁难你,而是为了保护用户隐私。
权限的分类:别搞混了
Android权限分为两类:
- 普通权限(Normal Permissions):比如网络访问、蓝牙。这些申请了就能用,不需要用户确认。在
AndroidManifest.xml里声明就行。 - 危险权限(Dangerous Permissions):比如相机、存储、位置。这些必须用户在运行时点击“允许”,否则应用无法使用相关功能。
常见危险权限包括:
CAMERA:拍照录像READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE:读写文件(在Android 13以上拆分得更细)ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION:精确定位、粗略定位READ_CONTACTS:读取联系人RECORD_AUDIO:录音
新手最容易踩的坑是:以为在AndroidManifest.xml里声明了权限就万事大吉。错!对于危险权限,你还得在代码里动态申请,并处理用户拒绝的情况。
实战:申请相机权限——完整流程解析
假设你的应用需要拍照功能。我们来一步步实现:
第一步:在AndroidManifest.xml里声明权限
找到AndroidManifest.xml(在app/src/main/AndroidManifest.xml),在<application>标签之前添加:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
这里required="true"表示如果你的应用必须用相机才能工作,否则设成false。声明了只是告诉系统你打算用,不代表已经获得权限。
第二步:在代码里检查并申请权限
回到MainActivity.kt,我们重写一下,加入权限逻辑:
package com.yourname.firstkotlinapp
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
private lateinit var button: Button
companion object {
private const val REQUEST_CODE_CAMERA = 1001
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textViewHello)
button = findViewById(R.id.buttonClick)
button.setOnClickListener {
checkCameraPermission()
}
}
private fun checkCameraPermission() {
// 检查是否已授权
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
// 已有权限,直接执行功能
textView.text = "相机已授权,可以拍照了!"
// 这里调用相机逻辑...
} else {
// 未授权,需要申请
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CODE_CAMERA
)
}
}
// 处理权限申请结果
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_CAMERA) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
textView.text = "用户允许了相机权限!"
} else {
textView.text = "用户拒绝了相机权限,功能不可用"
Toast.makeText(this, "需要相机权限才能拍照哦", Toast.LENGTH_SHORT).show()
}
}
}
}
这里的关键点:
checkSelfPermission:检查当前状态。requestPermissions:弹出系统对话框,用户点允许或拒绝。onRequestPermissionsResult:回调,处理结果。注意要检查requestCode,因为一个应用可能申请多种权限,这个回调会统一处理。
运行应用,点击按钮,你会看到系统弹出权限请求对话框。点允许,文字变化;点拒绝,显示提示。完美!
第三步:深入常见坑——那些文档不会告诉你的事
现在你已经能跑通基本流程,但真实开发中,权限相关的问题层出不穷。我整理了一些新手(甚至老手)常踩的坑,每个都配上解决方案和真实例子。
坑1:权限申请对话框“不弹”或“闪退”
现象:调用requestPermissions后,对话框没出现,应用直接崩溃,Logcat报错ActivityNotFoundException或SecurityException。
原因:
- 没有在
AndroidManifest.xml里声明权限(最常见)。 - 申请了普通权限却误以为是危险权限,或者反过来。
- 在子线程里调用权限申请(必须在主线程)。
解决方案:
- 双重检查
AndroidManifest.xml,确保权限声明在<application>标签外。 - 确认权限类型:查官方文档,相机是危险权限,必须动态申请;网络权限是普通权限,不用动态申请。
- 确保在UI线程调用。可以用
Handler(Looper.getMainLooper()).post { ... }包裹。
例子:有个用户反馈他的应用崩溃,我一看代码,发现他在onCreate里直接调用了requestPermissions,但AndroidManifest.xml漏了声明。加上声明后立刻正常。
坑2:用户点“拒绝”后,下次启动应用不弹对话框
现象:第一次申请权限,用户点了“拒绝”。第二次启动应用,再申请,对话框不出现,直接返回“拒绝”。
原因:Android系统规则——如果用户点了“拒绝”并且勾选了“不再询问”,系统就认为用户永远不想给权限,不再弹窗。这是为了防止应用烦扰用户。
解决方案:
- 检测用户是否勾选了“不再询问”:用
ActivityCompat.shouldShowRequestPermissionRationale(this, permission)。- 返回
true:可以解释为什么需要权限,然后再次申请。 - 返回
false且checkSelfPermission返回DENIED:用户选了“不再询问”,需要引导去设置页面手动开启。
- 返回
代码示例:
private fun requestCameraWithRationale() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
// 用户之前拒绝过但未勾选“不再询问”,显示解释
Toast.makeText(this, "我们需要相机权限来拍照,请允许", Toast.LENGTH_LONG).show()
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CODE_CAMERA
)
} else {
// 直接申请或引导去设置
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CODE_CAMERA
)
}
}
在onRequestPermissionsResult里,如果拒绝且shouldShowRequestPermissionRationale为false,就跳转设置:
if (grantResults[0] == PackageManager.PERMISSION_DENIED &&
!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
// 引导用户去设置
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivity(intent)
}
坑3:权限申请后,功能依然异常(比如相机黑屏)
现象:权限申请通过,但调用相机API时崩溃或黑屏。
原因:
- 混淆了
CAMERA权限和READ_EXTERNAL_STORAGE权限。如果相机要保存照片到相册,还需要存储权限。 - 设备没有相机硬件(虽然声明了
required="true",但有些模拟器或老旧设备没有)。 - 代码逻辑错误,比如没有正确初始化相机。
解决方案:
- 检查所有需要的权限。现代Android应用通常需要一对权限:相机 + 存储(或媒体读取)。
- 用
PackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)检查硬件。 - 使用官方推荐的CameraX库,它内置了权限处理和生命周期管理,比原生Camera API省心很多。
例子:我曾帮一个开发者调试,他的应用在权限申请后调用相机崩溃。原来他只申请了CAMERA,但保存照片时需要WRITE_EXTERNAL_STORAGE。加上这个权限并动态申请后,问题解决。
坑4:在Fragment或ViewModel里申请权限
现象:权限申请代码写在Fragment里,结果回调onRequestPermissionsResult没被调用,或者调用时宿主Activity已经销毁。
原因:权限回调是Activity级别的,Fragment需要委托给宿主Activity。
解决方案:
- 在Fragment里使用
ActivityCompat.requestPermissions并设置fragment参数,或者直接在Fragment里调用requestPermissions(API 28以上支持)。 - 更推荐的做法是使用
ActivityResultLauncher,这是现代Android开发的标准方式,更简洁、更安全。
现代方案示例(使用ActivityContracts):
class MainActivity : AppCompatActivity() {
private val cameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
textView.text = "相机权限已获得"
// 启动相机...
} else {
textView.text = "相机权限被拒绝"
Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
// ...
button.setOnClickListener {
cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
这样不用重写onRequestPermissionsResult,代码更干净,而且自动处理生命周期。强烈建议新手学习这种新API。
坑5:多权限同时申请的处理混乱
现象:应用需要相机和存储权限,一次申请多个,但回调里顺序搞混了。
解决方案:
- 使用
ActivityResultContracts.RequestMultiplePermissions()。 - 用Map或对象来跟踪每个权限的结果。
private val multiplePermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val cameraGranted = permissions[Manifest.permission.CAMERA] ?: false
val storageGranted = permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: false
if (cameraGranted && storageGranted) {
// 两个都允许,继续
} else {
// 部分拒绝,处理错误
}
}
// 调用:
multiplePermissionsLauncher.launch(arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE
))
第四步:进阶技巧——让权限管理变得优雅
当你的应用变得复杂,权限申请散落在各处会很头疼。我分享几个进阶实践:
1. 封装权限工具类
创建一个PermissionHelper.kt,统一管理权限申请和回调。这样主代码更清晰。
”`kotlin object PermissionHelper {
fun checkAndRequestPermission(
activity: AppCompatActivity,
permission: String,
requestCode: Int,
onGranted: () -> Unit,
onDenied: () -> Unit
) {
if (ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED) {
onGranted()
}
