Android编程实例分析从入门到实战完整解析
哈喽!看到你想学Android编程,这绝对是个很棒的选择。现在全球还有几十亿台Android设备在运行,学习Android开发不仅能帮你做出实用的App,还能打开移动开发的大门。别担心,我会一步步带你走完整个学习路径,从零基础到能独立做项目,咱们慢慢来。
一、先搞清楚:Android开发到底在做什么
在动手写代码之前,先理解Android开发的本质。Android App本质上就是一个运行在Android设备上的软件程序,它由Java或Kotlin语言编写,调用Android提供的各种API来完成任务——可能是显示界面、访问网络、读取传感器数据,或者操控硬件。
你不需要一开始就懂所有东西,就像学骑自行车,先上车再找平衡。咱们从最基础的环境搭建开始。
二、环境搭建:让开发工具跑起来
这是很多初学者最容易放弃的第一关,但其实只要按步骤来,十分钟内就能搞定。
2.1 安装JDK(Java开发工具包)
Android项目需要Java环境来编译代码。推荐安装JDK 17或更高版本:
# macOS 用户用Homebrew安装
brew install openjdk@17
# 设置环境变量(添加到 ~/.zshrc 或 ~/.bash_profile)
export JAVA_HOME=$(/usr/libexec/java_home -v 17)
export PATH=$JAVA_HOME/bin:$PATH
# 验证安装成功
java -version
# 应该输出类似:
# openjdk version "17.0.8" 2023-07-18
2.2 安装Android Studio
这是Google官方推荐的Android开发IDE,基于IntelliJ IDEA,功能非常强大。下载地址在 developer.android.com/studio。
安装时记得勾选这几个关键组件:
- Android SDK:系统开发库
- Android Virtual Device (AVD):模拟器
- SDK Platform:你目标版本的平台
- Android SDK Build-Tools:编译工具
2.3 配置模拟器
第一次打开Android Studio时,会提示你创建虚拟设备。如果你有一台Android手机,也可以开启”开发者选项”和”USB调试”,通过数据线直接连接真机调试,那样速度会快很多。
创建模拟器的步骤:
- 点击 Tools → Device Manager
- 点击 Create Device
- 选择一款手机(推荐Pixel 6,性能模拟准确)
- 选择一个系统镜像(推荐Android 14,API 34)
- 完成创建后启动模拟器
三、第一个项目:Hello World,但要有名堂
每个程序员的必经之路,但我们要让它更有意义一点。直接打开Android Studio,选择”New Project”,选”Empty Activity”,项目名称设为 MyFirstApp,语言选Kotlin(现在Google官方推荐Kotlin,比Java更简洁)。
项目创建完成后,你会看到一堆文件结构。别慌,先记住这几个关键目录:
app/
├── src/main/
│ ├── AndroidManifest.xml ← 应用的"身份证",描述权限、Activity等信息
│ ├── java/ ← 写代码的地方
│ │ └── com/example/myfirstapp/
│ │ └── MainActivity.kt ← 主界面逻辑
│ └── res/ ← 资源文件
│ ├── layout/ ← 界面布局XML
│ │ └── activity_main.xml
│ └── values/ ← 字符串、颜色等常量
├── build.gradle.kts ← 构建脚本,管理依赖
└── proguard-rules.pro ← 代码混淆规则
3.1 界面布局:XML说了算
打开 res/layout/activity_main.xml,默认可能是ConstraintLayout。我们改成更直观的方式来理解——先看看默认的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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
这段XML定义了一个全屏的约束布局,中间放了一个显示”Hello World!“的文本。初学者看到这些 layout_constraint 属性会头晕,我给你解释一下:
wrap_content表示大小跟随内容match_parent表示撑满父容器- 约束属性(如
layout_constraintTop_toTopOf)决定了元素之间的位置关系
在Android Studio里,你其实不需要手写这些XML。点击布局文件右上角的”Design”标签,可以用拖拽的方式添加控件,IDE会自动生成对应的XML。但了解XML原理很重要,因为你以后肯定会遇到需要手写或修改的情况。
3.2 逻辑代码:Kotlin让一切变简单
打开 MainActivity.kt:
package com.example.myfirstapp
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 找到刚才定义的TextView
val textView = findViewById<TextView>(R.id.textView)
// 改变文字内容
textView.text = "你好,Android!"
// 让文字变大一点,顺便换个颜色
textView.textSize = 24f
textView.setTextColor(android.graphics.Color.parseColor("#FF5722"))
}
}
这段代码做了三件事:
- 通过
setContentView把XML界面加载进来 - 用
findViewById获取界面中的控件引用 - 修改控件的属性
点击Android Studio顶部的运行按钮(绿色三角),选择你的模拟器或真机,几秒钟后就能看到App跑起来了。
四、核心组件:Android的四大基石
理解了第一个项目,接下来要掌握Android的四大核心组件。这不是为了考试,而是为了让你知道怎么写出一个结构合理的App。
4.1 Activity:一个屏幕就是一个Activity
Activity是Android最核心的组件,代表一个包含用户界面的屏幕。每个页面基本上就是一个Activity。
package com.example.myfirstapp
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val tvResult = findViewById<TextView>(R.id.tvResult)
val btnGo = findViewById<Button>(R.id.btnGo)
btnGo.setOnClickListener {
// 跳转到第二个页面
val intent = Intent(this, SecondActivity::class.java)
// 顺便传递一个数据过去
intent.putExtra("message", "Hello from MainActivity!")
startActivity(intent)
}
}
}
对应的 SecondActivity.kt:
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val tvMessage = findViewById<TextView>(R.id.tvMessage)
// 接收从第一个Activity传来的数据
val message = intent.getStringExtra("message")
tvMessage.text = message ?: "没有收到消息"
}
}
关键点:两个Activity都必须在 AndroidManifest.xml 中注册,否则系统会崩溃。Android Studio创建项目时会自动注册主Activity,但新创建的Activity你需要手动添加:
<activity android:name=".SecondActivity" />
4.2 Fragment:让界面更模块化
当页面比较复杂时,Activity会变得越来越臃肿。Fragment就像Activity里的小模块,可以组合使用。
class MyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 加载fragment的布局
return inflater.inflate(R.layout.fragment_my, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val btnClick = view.findViewById<Button>(R.id.btnClick)
btnClick.setOnClickListener {
// Fragment内部逻辑
Toast.makeText(context, "按钮被点击了", Toast.LENGTH_SHORT).show()
}
}
}
Fragment有一个优势:同一个Fragment可以在不同大小屏幕上灵活组合。手机上一列显示,平板上可以两列并排。这是为什么Google推荐多用Fragment的原因。
4.3 Service:后台默默干活
Service是在后台执行长时间操作的组件,它没有用户界面。常见用途包括:播放音乐、下载文件、网络请求。
class DownloadService : Service() {
private var downloadThread: Thread? = null
override fun onCreate() {
super.onCreate()
// 开始下载任务
downloadThread = Thread {
downloadFile("https://example.com/file.zip")
}.apply {
start()
}
}
private fun downloadFile(url: String) {
// 实际下载逻辑
// 下载完成后发送广播通知Activity
val intent = Intent("DOWNLOAD_COMPLETE")
sendBroadcast(intent)
}
override fun onBind(intent: Intent?): IBinder? = null
}
在Manifest中声明:
<service android:name=".DownloadService" android:exported="false" />
4.4 BroadcastReceiver:接收系统广播
Android系统有很多广播事件,比如电量变化、网络切换、开机完成等。你的App可以监听这些事件。
class BatteryReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)
val percentage = level * 100 / scale
Toast.makeText(context, "电量: $percentage%", Toast.LENGTH_SHORT).show()
}
}
动态注册(在代码中):
val receiver = BatteryReceiver()
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
registerReceiver(receiver, filter)
五、UI开发:从XML到现代Compose
这是Android开发中变化最大的部分。传统方式用XML定义界面,但Google在2021年推出了Jetpack Compose,用声明式的方式写UI,类似于Flutter和React的思路。我两个都会教你,但重点放在Compose上,因为这是未来。
5.1 传统XML方式
XML布局适合理解Android的基础,也适合维护老项目。一个简单的用户登录界面:
<?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="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:background="#FAFAFA">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="欢迎登录"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="#212121"
android:layout_marginBottom="32dp"
android:gravity="center" />
<EditText
android:id="@+id/etUsername"
android:layout_width="match_parent"
android:layout_height="56dp"
android:hint="请输入用户名"
android:inputType="text"
android:background="#FFFFFF"
android:padding="16dp"
android:layout_marginBottom="16dp"
android:elevation="2dp" />
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="56dp"
android:hint="请输入密码"
android:inputType="textPassword"
android:background="#FFFFFF"
android:padding="16dp"
android:layout_marginBottom="24dp"
android:elevation="2dp" />
<Button
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="登录"
android:textSize="18sp"
android:backgroundTint="#6200EE"
android:textColor="#FFFFFF"
android:layout_marginBottom="16dp" />
<TextView
android:id="@+id/tvRegister"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="还没有账号?去注册"
android:textColor="#6200EE"
android:layout_gravity="center_horizontal" />
</LinearLayout>
5.2 Jetpack Compose方式
同样的界面,用Compose写出来:
@Composable
fun LoginScreen(onLoginClick: (String, String) -> Unit) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "欢迎登录",
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF212121),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(32.dp))
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("请输入用户名") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("请输入密码") },
modifier = Modifier.fillMaxWidth(),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { onLoginClick(username, password) },
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF6200EE))
) {
Text("登录", fontSize = 18.sp, color = Color.White)
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "还没有账号?去注册",
color = Color(0xFF6200EE),
fontSize = 14.sp
)
}
}
Composable函数用 @Composable 注解标记,通过状态管理(mutableStateOf)让界面自动响应变化。这种声明式UI让你不用手动找控件再修改属性,而是描述”界面应该长什么样”,框架自动处理渲染。
六、网络请求:让App连上世界
大多数实用App都需要联网。Android开发中最流行的网络库是 Retrofit + OkHttp,配合 Kotlin 协程使用。
6.1 添加依赖
在 build.gradle.kts 中添加:
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")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}
6.2 定义API接口
// 定义数据模型
data class User(
val id: Int,
val name: String,
val email: String
)
// 定义API接口
interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
@GET("users/{id}")
suspend fun getUserById(@Path("id") userId: Int): User
}
6.3 在Activity中使用
class UserListActivity : AppCompatActivity() {
private lateinit var binding: ActivityUserListBinding
private val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserListBinding.inflate(layoutInflater)
setContentView(binding.root)
// 用协程处理网络请求
lifecycleScope.launch {
try {
val users = apiService.getUsers()
binding.recyclerView.adapter = UserAdapter(users)
} catch (e: Exception) {
Toast.makeText(this@UserListActivity, "网络请求失败: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
这里用到了ViewBinding,它让你不用写 findViewById。只要在build.gradle中启用:
android {
buildFeatures {
viewBinding = true
}
}
七、本地数据存储:不只是数据库
7.1 SharedPreferences:存小数据
适合存用户偏好设置、登录状态等轻量数据:
class PreferenceManager(private val context: Context) {
private val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
fun saveToken(token: String) {
prefs.edit().putString("auth_token", token).apply()
}
fun getToken(): String? {
return prefs.getString("auth_token", null)
}
fun clearAll() {
prefs.edit().clear().apply()
}
}
7.2 Room数据库:官方推荐的持久化方案
Room是Google官方的SQLite封装,类型安全、编译期检查、代码量少:
// 1. 定义Entity
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
val name: String,
val email: String
)
// 2. 定义DAO
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAll(): LiveData<List<User>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: User)
@Delete
suspend fun delete(user: User)
}
// 3. 定义Database
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
// 4. 初始化(单例)
val db: AppDatabase by lazy {
Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
"app_database"
).build()
}
使用:
lifecycleScope.launch {
val user = User(id = 1, name = "张三", email = "zhangsan@example.com")
db.userDao().insert(user)
}
八、MVVM架构:让代码更干净
随着项目变大,代码会变得难以维护。MVVM(Model-View-ViewModel)是Android官方推荐的架构模式,把界面、数据和业务逻辑分开。
// Model层:数据模型
data class Product(
val id: Int,
val name: String,
val price: Double,
val imageUrl: String
)
// ViewModel层:业务逻辑和状态管理
class ProductViewModel : ViewModel() {
private val _products = MutableLiveData<List<Product>>()
val products: LiveData<List<Product>> = _products
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
private val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
fun loadProducts() {
_loading.value = true
viewModelScope.launch {
try {
val result = apiService.getProducts()
_products.value = result
} catch (e: Exception) {
// 错误处理
} finally {
_loading.value = false
}
}
}
}
// View层:Activity
class ProductActivity : AppCompatActivity() {
private lateinit var binding: ActivityProductBinding
private val viewModel: ProductViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityProductBinding.inflate(layoutInflater)
setContentView(binding.root)
// 观察数据变化,自动更新UI
viewModel.products.observe(this) { products ->
binding.recyclerView.adapter = ProductAdapter(products)
}
viewModel.loading.observe(this) { isLoading ->
binding.progressBar.isVisible = isLoading
}
viewModel.loadProducts()
}
}
ViewModel的核心优势是配置变更时不丢失数据。手机屏幕旋转时Activity会重建,但ViewModel中的数据会保留,避免了重复请求。
九、完整实战项目:待办事项App
光说不练假把式。来做一个完整的待办事项App,把前面学的东西都用上。
9.1 项目结构
com.example.todolist/
├── data/
│ ├── TodoDao.kt
│ ├── TodoDatabase.kt
│ └── TodoEntity.kt
├── repository/
│ └── TodoRepository.kt
├── viewmodel/
│ └── TodoViewModel.kt
├── ui/
│ ├── MainActivity.kt
│ └── adapter/
│ └── TodoAdapter.kt
├── utils/
│ └── Constants.kt
└── TodoApplication.kt
9.2 数据层
// TodoEntity.kt
@Entity(tableName = "todos")
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val title: String,
val description: String = "",
val completed: Boolean = false,
val createdAt: Long = System.currentTimeMillis()
)
// TodoDao.kt
@Dao
interface TodoDao {
@Query("SELECT * FROM todos ORDER BY createdAt DESC")
fun getAllTodos(): LiveData<List<TodoEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(todo: TodoEntity)
@Update
suspend fun update(todo: TodoEntity)
@Delete
suspend fun delete(todo: TodoEntity)
@Query("DELETE FROM todos WHERE id = :id")
suspend fun deleteById(id: Int)
}
// TodoDatabase.kt
@Database(entities = [TodoEntity::class], version = 1, exportSchema = false)
abstract class TodoDatabase : RoomDatabase() {
abstract fun todoDao(): TodoDao
}
// TodoRepository.kt
class TodoRepository(private val todoDao: TodoDao) {
val allTodos: LiveData<List<TodoEntity>> = todoDao.getAllTodos()
suspend fun insert(todo: TodoEntity) = todoDao.insert(todo)
suspend fun update(todo: TodoEntity) = todoDao.update(todo)
suspend fun delete(todo: TodoEntity) = todoDao.delete(todo)
suspend fun deleteById(id: Int) = todoDao.deleteById(id)
}
9.3 ViewModel层
class TodoViewModel(private val repository: TodoRepository) : ViewModel() {
val allTodos: LiveData<List<TodoEntity>> = repository.allTodos
private val _snackbarMessage = MutableLiveData<String>()
val snackbarMessage: LiveData<String> = _snackbarMessage
fun addTodo(title: String, description: String = "") {
viewModelScope.launch {
repository.insert(TodoEntity(title = title, description = description))
_snackbarMessage.postValue("已添加: $title")
}
}
fun toggleComplete(todo: TodoEntity) {
viewModelScope.launch {
val updated = todo.copy(completed = !todo.completed)
repository.update(updated)
val status = if (updated.completed) "已完成" else "已取消"
_snackbarMessage.postValue("$status: ${updated.title}")
}
}
fun deleteTodo(todo: TodoEntity) {
viewModelScope.launch {
repository.delete(todo)
_snackbarMessage.postValue("已删除: ${todo.title}")
}
}
}
9.4 UI层
// MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: TodoViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 设置RecyclerView
val adapter = TodoAdapter(
onToggle = { viewModel.toggleComplete(it) },
onDelete = { viewModel.deleteTodo(it) }
)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = adapter
itemAnimator = DefaultItemAnimator()
adapter.diffCallback = TodoDiffCallback()
}
// 观察数据变化
viewModel.allTodos.observe(this) { todos ->
adapter.submitList(todos)
}
// 观察提示消息
viewModel.snackbarMessage.observe(this) { message ->
Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show()
}
// 添加新待办
binding.fabAdd.setOnClickListener {
showAddDialog()
}
}
private fun showAddDialog() {
val dialogView = LayoutInflater.from(this)
.inflate(R.layout.dialog_add_todo, null)
val dialog = AlertDialog.Builder(this)
.setTitle("新建待办")
.setView(dialogView)
.setPositiveButton("添加") { _, _ ->
val title = dialogView.findViewById<EditText>(R.id.etTitle).text.toString().trim()
val description = dialogView.findViewById<EditText>(R.id.etDescription).text.toString().trim()
if (title.isNotEmpty()) {
viewModel.addTodo(title, description)
}
}
.setNegativeButton("取消", null)
.create()
dialog.show()
}
}
9.5 列表适配器
class TodoAdapter(
private val onToggle: (TodoEntity) -> Unit,
private val onDelete: (TodoEntity) -> Unit
) : ListAdapter<TodoEntity, TodoAdapter.TodoViewHolder>(TodoDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_todo, parent, false)
return TodoViewHolder(view)
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
holder.bind(getItem(position), onToggle, onDelete)
}
class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvTitle: TextView = itemView.findViewById(R.id.tvTitle)
private val tvDesc: TextView = itemView.findViewById(R.id.tvDesc)
private val cbComplete: CheckBox = itemView.findViewById(R.id.cbComplete)
private val btnDelete: ImageButton = itemView.findViewById(R.id.btnDelete)
fun bind(todo: TodoEntity, onToggle: (TodoEntity) -> Unit, onDelete: (TodoEntity) -> Unit) {
tvTitle.text = todo.title
tvDesc.text = todo.description
cbComplete.isChecked = todo.completed
cbComplete.setOnCheckedChangeListener { _, isChecked ->
if (isChecked != todo.completed) {
onToggle(todo)
}
cbComplete.setOnCheckedChangeListener(null)
cbComplete.isChecked = todo.completed
cbComplete.setOnCheckedChangeListener { _, _ -> onToggle(todo) }
}
btnDelete.setOnClickListener { onDelete(todo) }
}
}
}
class TodoDiffCallback : DiffUtil.ItemCallback<TodoEntity>() {
override fun areItemsTheSame(oldItem: TodoEntity, newItem: TodoEntity) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: TodoEntity, newItem: TodoEntity) =
oldItem == newItem
}
这个待办App包含了完整的MVVM架构、Room数据库、LiveData数据绑定、RecyclerView列表、对话框输入、Snackbar提示等核心功能。把它跑起来,你就已经掌握了Android开发的大部分基础技能。
十、进阶方向:让你的App更上一层楼
当基础掌握后,你可以往这些方向深入:
10.1 Jetpack Compose彻底重构UI
@Composable
fun TodoListScreen(viewModel: TodoViewModel = viewModel()) {
val todos by viewModel.allTodos.collectAsStateWithLifecycle()
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* 添加 */ }) {
Icon(Icons.Default.Add, contentDescription = "添加")
}
}
) { padding ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(padding),
contentPadding = PaddingValues(16.dp)
) {
items(todos, key = { it.id }) { todo ->
TodoItemCard(
todo = todo,
onToggle = { viewModel.toggleComplete(todo) },
onDelete = { viewModel.deleteTodo(todo) }
)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
10.2 依赖注入:Hilt
// 应用级别初始化
@HiltAndroidApp
class TodoApplication : Application()
// 提供Database
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
fun provideDatabase(@ApplicationContext context: Context): TodoDatabase {
return Room.databaseBuilder(
context,
TodoDatabase::class.java,
"todo_database"
).build()
}
@Provides
fun provideTodoDao(database: TodoDatabase): TodoDao {
return database.todoDao()
}
}
// 提供Repository
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
@Provides
fun provideRepository(dao: TodoDao): TodoRepository {
return TodoRepository(dao)
}
}
// ViewModel中直接注入
@HiltViewModel
class TodoViewModel @Inject constructor(
private val repository: TodoRepository
) : ViewModel()
10.3 性能优化要点
// 1. 图片加载用Coil(专为Compose设计)
@Composable
fun TodoImage(url: String) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.crossfade(true)
.build(),
contentDescription = null,
modifier = Modifier.size(48.dp),
contentScale = ContentScale.Crop
)
}
// 2. 列表优化:DiffUtil已经处理了,但大数据量考虑分页
// 使用 PagedKeySet 或 Paging 3 库处理无限滚动
// 3. 避免在Composition中做耗时操作
@Composable
fun HeavyComputation() {
val result by remember { derivedStateOf { computeHeavyResult() } }
Text(result)
}
十一、常见坑和避坑指南
11.1 生命周期问题
// 错误:在Activity销毁后仍然访问UI
class BadExample : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 异步操作完成后可能Activity已经销毁
CoroutineScope(Dispatchers.Main).launch {
val data = fetchData()
binding.tvData.text = data // 可能崩溃!
}
}
}
// 正确:用lifecycleScope
class GoodExample : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
val data = fetchData()
if (isDestroyed) return@launch // 检查Activity状态
binding.tvData.text = data
}
}
}
11.2 内存泄漏
// 错误:非静态内部类持有Activity引用
class LeakActivity : AppCompatActivity() {
private lateinit var handler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handler = Handler(Looper.getMainLooper())
handler.postDelayed({
// 闭包隐式持有了Activity引用
updateUi()
}, 10000)
}
}
// 正确:用弱引用或静态内部类
class LeakActivity : AppCompatActivity() {
private var handler: Handler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handler = Handler(Looper.getMainLooper())
}
override fun onDestroy() {
super.onDestroy()
handler?.removeCallbacksAndMessages(null) // 清理
handler = null
}
}
11.3 线程安全
// ViewModel中的协程默认在主线程执行UI相关操作
class SafeViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch(Dispatchers.IO) {
// IO线程:网络请求、数据库操作
val data = repository.fetchData()
withContext(Dispatchers.Main) {
// 切回主线程:更新UI
_data.postValue(data)
}
}
}
}
十二、推荐学习资源和下一步
官方文档
- developer.android.com 是最权威的教程来源,Google工程师亲自写的
- codelabs.developer.android.com 有免费的交互式课程
推荐的开源项目学习
- 去GitHub搜索”Android Kotlin Architecture Components Sample”,看Google官方示例
- 看别人的代码比看文档更快进步
你可以继续深入的方向
- KMP(Kotlin Multiplatform):一套代码同时运行Android和iOS
- Flutter:如果想学跨平台,Flutter也是一个好选择
- Android游戏开发:用LibGDX或Unity
- 嵌入式Android:车载系统、智能家居
学习Android编程就像盖房子,地基打好了才能建高楼。从Hello World到完整的MVVM项目,你走的每一步都在积累经验。别怕犯错,每个报错信息都是学习的机会。遇到问题时,先搜Stack Overflow,再看官方文档,最后才是问人。
祝你写代码愉快!有任何具体问题随时来问。
