说实话,刚开始接触Android开发那会儿,我也挺懵的。看着Android Studio那个庞大的界面,Kotlin和Java混在一起的文档,还有各种Gradle配置,真的有点想打退堂鼓。但当你真正跑通第一个HelloWorld,看到手机屏幕上弹出“Hello World”的时候,那种成就感简直无法形容。今天我就把自己踩过的坑、走过的路,还有那些教科书上不会告诉你的实战技巧,统统拿出来跟你聊聊。
咱们不整那些虚头巴脑的理论,直接上手。Android开发其实就像搭积木,你先把基础积木块认识清楚,后面搭建复杂建筑自然就顺了。
环境搭建:别在起跑线上摔跟头
很多新手在这里就放弃了,因为配置环境问题太烦人了。我先带你快速过一遍,保证你20分钟内能跑起来第一个项目。
首先,你需要安装JDK(Java Development Kit)。去Oracle官网下载JDK 17或者更高版本,安装完成后打开命令行,输入java -version看看有没有输出版本号。这一步很关键,Android Studio会依赖它。
然后下载Android Studio,这是官方IDE,功能强大到让你怀疑人生。下载链接是developer.android.com/studio,选择对应你操作系统的版本。安装过程一路Next就行,但有个地方要注意:安装时会自动下载Android SDK,这个过程可能有点慢,取决于你的网速。等它下完后,打开Android Studio,创建一个新项目。
这时候你会看到一堆模板选项,新手直接选”Empty Views Activity”或者”Empty Activity”,别选那些花里胡哨的,我们先学最基础的。项目名随便取,比如”MyFirstApp”,包名就按默认来,语言选Kotlin(现在是主流,Java也还能用,但我推荐Kotlin,语法更简洁)。
等Gradle同步完成,你的第一个项目就搭建好了。注意右上角有个模拟器图标,点一下选择你下载的虚拟设备,然后点运行按钮。第一次启动模拟器可能有点慢,耐心等待。如果模拟器跑不起来,检查一下你的CPU是否开启了虚拟化,在BIOS里设置一下就好。
看,是不是没那么难?环境搭好了,咱们开始写代码。
HelloWorld:揭开神秘面纱
新建完项目后,你会看到一堆文件。别慌,我们只关心几个关键的。首先打开MainActivity.kt,这是你的主界面控制器,相当于网页的body部分。你会发现里面已经有几行代码了:
package com.example.myfirstapp
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.widget.TextView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
这段代码的意思是:创建一个活动(Activity),当它创建时(onCreate),加载一个叫activity_main的布局文件。setContentView这个方法特别重要,它把UI界面和代码逻辑连接起来了。
接下来打开res/layout/activity_main.xml,这是布局文件,相当于HTML。你会看到默认有一个TextView,显示”Hello World!“。现在我们来修改它,让它在屏幕中央显示一行更友好的话:
<?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/helloTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="欢迎来到Android世界!"
android:textSize="24sp"
android:textColor="#FF0000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
注意那些app:layout_constraint属性,这是ConstraintLayout的约束系统,让控件定位更灵活。wrap_content表示根据内容自动调整大小,match_parent表示匹配父容器大小。sp是字体单位,比dp更适合文字。
回到MainActivity.kt,我们可以动态修改这个TextView:
class MainActivity : AppCompatActivity() {
private lateinit var helloTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 找到TextView控件
helloTextView = findViewById(R.id.helloTextView)
// 动态修改文本
helloTextView.text = "今天天气真好!"
// 添加点击事件
helloTextView.setOnClickListener {
helloTextView.text = "你点我了!"
}
}
}
这样,当你点击文字时,它会变化。这就是Android开发的基本模式:找到控件→修改属性→响应事件。看着简单,但理解了这个模式,后面的东西都类似。
按钮与事件:让界面活起来
光有文字太无聊了,咱们加点交互。在布局文件中加个按钮:
<Button
android:id="@+id/clickButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击我"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/helloTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
然后在MainActivity中处理点击:
private lateinit var clickButton: Button
private var clickCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
helloTextView = findViewById(R.id.helloTextView)
clickButton = findViewById(R.id.clickButton)
clickButton.setOnClickListener {
clickCount++
helloTextView.text = "你点击了 $clickCount 次"
if (clickCount >= 5) {
helloTextView.text = "别点了,累了!"
clickButton.isEnabled = false // 禁用按钮
}
}
}
这里我们引入了一个变量clickCount来记录点击次数,每次点击都更新TextView的文本。当点击次数达到5次时,禁用按钮,给用户一个反馈。这种简单的交互逻辑贯穿整个Android开发。
你可能会问,如果点击事件要处理复杂逻辑怎么办?别急,后面会讲到。现在先感受一下这种”找到控件→绑定事件→处理逻辑”的模式。
列表展示:RecyclerView是神器
当你要展示大量数据时,比如新闻列表、联系人列表,千万不要用传统的ListView,现在都用RecyclerView。它更灵活,性能更好,虽然配置稍微复杂一点,但值得。
首先,在build.gradle(app级别)中添加依赖:
dependencies {
// ...其他依赖
implementation 'androidx.recyclerview:recyclerview:1.3.2'
}
Sync Now后,我们来创建列表项的布局。新建一个文件res/layout/item_news.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:id="@+id/titleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:text="新闻标题" />
<TextView
android:id="@+id/descText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="14sp"
android:text="新闻描述"
android:maxLines="2"
android:ellipsize="end" />
</LinearLayout>
</androidx.cardview.widget.CardView>
用了CardView给列表项加圆角和阴影,看起来更现代。接下来创建数据类:
data class NewsItem(
val title: String,
val description: String
)
然后创建Adapter,这是RecyclerView的核心,负责把数据绑定到视图:
class NewsAdapter(
private val items: List<NewsItem>
) : RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {
class NewsViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val titleText: TextView = view.findViewById(R.id.titleText)
val descText: TextView = view.findViewById(R.id.descText)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_news, parent, false)
return NewsViewHolder(view)
}
override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
val item = items[position]
holder.titleText.text = item.title
holder.descText.text = item.description
}
override fun getItemCount(): Int = items.size
}
最后在主界面使用:
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: NewsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
// 准备数据
val newsList = listOf(
NewsItem("Android 14发布", "谷歌正式发布Android 14,带来新特性"),
NewsItem("Kotlin 2.0来袭", "Kotlin新版本将支持更多现代编程特性"),
NewsItem("Jetpack Compose成熟", "声明式UI开发已成主流")
)
adapter = NewsAdapter(newsList)
recyclerView.adapter = adapter
}
}
布局文件中只需要一个RecyclerView:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
看,RecyclerView用起来其实不复杂。Adapter负责数据绑定,ViewHolder负责缓存视图引用,LayoutManager负责布局排列。理解了这三部分,你就掌握了RecyclerView的精髓。
网络请求:从假数据到真实API
实际应用中,数据通常来自网络。我们用 Retrofit 来做网络请求,它是目前最流行的HTTP客户端之一。
先在build.gradle中添加依赖:
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
}
创建一个数据模型类,对应API返回的JSON结构:
data class ApiResponse(
val id: Int,
val title: String,
val body: String
)
创建 Retrofit API 接口:
interface NewsApiService {
@GET("posts")
suspend fun getPosts(): List<ApiResponse>
}
注意suspend关键字,这是Kotlin协程的特性,让网络请求变成异步操作而不阻塞主线程。
创建Retrofit实例:
val retrofit = Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(NewsApiService::class.java)
然后在ViewModel中处理网络请求(后面会详细讲ViewModel):
class NewsViewModel : ViewModel() {
private val _posts = MutableLiveData<List<ApiResponse>>()
val posts: LiveData<List<ApiResponse>> = _posts
fun loadPosts() {
viewModelScope.launch {
try {
val response = apiService.getPosts()
_posts.value = response
} catch (e: Exception) {
// 处理错误
_posts.value = emptyList()
}
}
}
}
这里引入了LiveData和ViewModel,这是Android官方推荐的架构组件,能让数据管理更清晰。LiveData会自动观察数据变化并更新UI,ViewModel在配置变更(如屏幕旋转)时保持数据不丢失。
主界面这样使用:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: NewsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(NewsViewModel::class.java)
viewModel.posts.observe(this) { posts ->
// 更新列表
adapter.submitList(posts)
}
// 加载数据
viewModel.loadPosts()
}
}
这样,当你启动应用时,它会自动从网络获取数据并显示。如果网络失败,用户也能看到空状态。
架构模式:MVVM让你代码更整洁
前面我们用了ViewModel和LiveData,这就是MVVM架构的一部分。MVVM代表Model-View-ViewModel,它把UI逻辑和数据逻辑分离开,让代码更易测试、更易维护。
Model层:数据类和网络请求逻辑 View层:Activity/Fragment和布局文件 ViewModel层:处理UI逻辑,暴露数据给View
在实际项目中,你会看到这样的目录结构:
com.example.newsapp
├── data
│ ├── model
│ │ └── NewsItem.kt
│ ├── repository
│ │ └── NewsRepository.kt
│ └── network
│ └── NewsApiService.kt
├── ui
│ ├── main
│ │ ├── MainViewModel.kt
│ │ └── MainActivity.kt
│ └── detail
│ └── DetailActivity.kt
└── NewsApp.kt
NewsRepository负责协调数据源(网络和本地缓存):
class NewsRepository(private val apiService: NewsApiService) {
suspend fun getNews(): Result<List<NewsItem>> {
return try {
val data = apiService.getPosts()
Result.success(data.map { NewsItem(it.id, it.title, it.body) })
} catch (e: Exception) {
Result.failure(e)
}
}
}
ViewModel只依赖Repository,不直接依赖网络或服务:
class MainViewModel(private val repository: NewsRepository) : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState
fun loadNews() {
viewModelScope.launch {
_uiState.value = UiState isLoading = true)
repository.getNews().fold(
onSuccess = { news ->
_uiState.value = UiState data = news, isLoading = false)
},
onFailure = { error ->
_uiState.value = UiState error = error, isLoading = false)
}
)
}
}
}
data class UiState(
val data: List<NewsItem> = emptyList(),
val isLoading: Boolean = false,
val error: Throwable? = null
)
主界面观察状态:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val repository = NewsRepository(createApiService())
viewModel = ViewModelProvider(this,
ViewModelProvider.NewInstanceFactory() { repository }.get(MainViewModel::class.java)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when {
state.isLoading -> showLoading()
state.error != null -> showError(state.error)
else -> showNews(state.data)
}
}
}
}
viewModel.loadNews()
}
}
这种架构刚开始可能觉得复杂,但一旦习惯了,你会发现代码组织非常清晰,后续维护和测试都方便得多。
常见问题:我踩过的坑,你不用踩
问题1:NullPointerException崩崩崩
这是Android开发最常见的错误,几乎每个新手都会遇到。原因是你调用了一个null对象的方法或属性。比如:
// 错误示范
val text = someString.substring(0, 5) // someString可能是null
// 正确做法
val text = someString?.substring(0, 5) ?: "默认值"
// 或者先检查
if (someString != null) {
val text = someString.substring(0, 5)
}
Kotlin的空安全机制能帮你避免大部分这个问题,但如果你混用Java代码,还是要注意。
问题2:内存泄漏
当Activity被销毁但还有引用指向它时,就会发生内存泄漏。常见原因:
- 静态引用Context
- 非静态内部类持有Activity引用
- 注册了监听器但没注销
解决方案:
”`kotlin // 避免静态引用Context // 错误:static Context context; // 正确:使用Application Context
// 使用弱引用
class MyListener(private val activity: WeakReference
override fun onClick(v: View?) {
activity.get()?.doSomething()
}
}
// 及时注销监听器 override fun onDestroy() {
super.onDestroy()
locationManager.removeUpdates(this)
}
