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调试”,通过数据线直接连接真机调试,那样速度会快很多。

创建模拟器的步骤:

  1. 点击 Tools → Device Manager
  2. 点击 Create Device
  3. 选择一款手机(推荐Pixel 6,性能模拟准确)
  4. 选择一个系统镜像(推荐Android 14,API 34)
  5. 完成创建后启动模拟器

三、第一个项目: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"))
    }
}

这段代码做了三件事:

  1. 通过 setContentView 把XML界面加载进来
  2. findViewById 获取界面中的控件引用
  3. 修改控件的属性

点击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,再看官方文档,最后才是问人。

祝你写代码愉快!有任何具体问题随时来问。