引言

Android作为全球最流行的移动操作系统,拥有超过30亿活跃设备。对于想要进入移动开发领域的开发者来说,掌握Android编程是一项极具价值的技能。本指南将从零基础开始,通过详细的实例分析,带你一步步完成从理论学习到实战开发的完整过程。

第一部分:Android开发基础

1.1 Android开发环境搭建

在开始编程之前,我们需要搭建完整的开发环境。以下是详细的步骤:

安装Android Studio

  • 访问Android开发者官网下载最新版本
  • 安装过程中确保勾选以下组件:
    • Android SDK
    • Android SDK Platform
    • Android Virtual Device (AVD) Manager
    • Intel HAXM(如果使用Intel处理器)

配置环境变量

# Windows系统
# 在系统环境变量中添加
ANDROID_HOME = C:\Users\YourName\AppData\Local\Android\Sdk
PATH = %ANDROID_HOME%\platform-tools;%ANDROID_HOME%\tools

# macOS/Linux系统
# 在~/.bash_profile或~/.zshrc中添加
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/tools

验证安装

# 打开终端或命令提示符
adb version
# 应该显示类似以下内容:
# Android Debug Bridge version 1.0.41
# Version 33.0.2-10561450

1.2 Android项目结构解析

创建一个新项目后,我们来分析其结构:

MyApplication/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/com/example/myapplication/
│   │   │   │   └── MainActivity.java
│   │   │   ├── res/
│   │   │   │   ├── layout/
│   │   │   │   │   └── activity_main.xml
│   │   │   │   ├── values/
│   │   │   │   │   ├── colors.xml
│   │   │   │   │   ├── strings.xml
│   │   │   │   │   └── styles.xml
│   │   │   │   └── drawable/
│   │   │   └── AndroidManifest.xml
│   │   └── test/
│   └── build.gradle
├── build.gradle
└── settings.gradle

关键文件说明:

  • AndroidManifest.xml:应用的配置文件,声明应用的组件、权限等
  • MainActivity.java:主活动类,处理用户交互
  • activity_main.xml:主界面的布局文件
  • build.gradle:项目构建配置文件

1.3 第一个Android应用:Hello World

让我们创建一个简单的”Hello World”应用来理解基本流程:

步骤1:创建新项目

  1. 打开Android Studio
  2. 选择”New Project”
  3. 选择”Empty Activity”
  4. 填写项目名称、包名、保存位置
  5. 选择语言(Java或Kotlin,本指南使用Kotlin)

步骤2:编写代码

// MainActivity.kt
package com.example.helloworld

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView

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 = "Hello, Android Developer!"
    }
}

步骤3:设计界面

<!-- activity_main.xml -->
<?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:gravity="center">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="24sp"
        android:textColor="@color/black" />

</LinearLayout>

步骤4:运行应用

  1. 连接Android设备或启动模拟器
  2. 点击工具栏的”Run”按钮
  3. 应用将安装并运行在设备上

第二部分:Android核心组件详解

2.1 Activity(活动)

Activity是Android应用的基本组件,代表一个屏幕界面。

生命周期方法详解:

class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Activity被创建,初始化UI组件
        Log.d("Lifecycle", "onCreate called")
    }
    
    override fun onStart() {
        super.onStart()
        // Activity变为可见状态
        Log.d("Lifecycle", "onStart called")
    }
    
    override fun onResume() {
        super.onResume()
        // Activity开始与用户交互
        Log.d("Lifecycle", "onResume called")
    }
    
    override fun onPause() {
        super.onPause()
        // Activity失去焦点,但仍然可见
        Log.d("Lifecycle", "onPause called")
    }
    
    override fun onStop() {
        super.onStop()
        // Activity不再可见
        Log.d("Lifecycle", "onStop called")
    }
    
    override fun onDestroy() {
        super.onDestroy()
        // Activity被销毁
        Log.d("Lifecycle", "onDestroy called")
    }
}

Activity间跳转示例:

// 从MainActivity跳转到DetailActivity
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("USER_ID", 123)
intent.putExtra("USER_NAME", "张三")
startActivity(intent)

// 在DetailActivity中获取数据
val userId = intent.getIntExtra("USER_ID", 0)
val userName = intent.getStringExtra("USER_NAME")

2.2 Fragment(片段)

Fragment是Activity的模块化部分,可以复用在多个Activity中。

创建Fragment示例:

class UserFragment : Fragment() {
    
    private lateinit var viewModel: UserViewModel
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 加载布局
        return inflater.inflate(R.layout.fragment_user, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 初始化ViewModel
        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        
        // 观察数据变化
        viewModel.userData.observe(viewLifecycleOwner) { user ->
            // 更新UI
            view.findViewById<TextView>(R.id.userName).text = user.name
            view.findViewById<TextView>(R.id.userEmail).text = user.email
        }
        
        // 加载数据
        viewModel.loadUser()
    }
}

在Activity中使用Fragment:

<!-- activity_main.xml -->
<FrameLayout
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
// 在Activity中添加Fragment
supportFragmentManager.beginTransaction()
    .replace(R.id.fragment_container, UserFragment())
    .commit()

2.3 Service(服务)

Service在后台执行长时间运行的操作,不提供用户界面。

创建后台服务示例:

class DownloadService : Service() {
    
    private val binder = DownloadBinder()
    
    inner class DownloadBinder : Binder() {
        fun getService(): DownloadService = this@DownloadService
    }
    
    override fun onBind(intent: Intent?): IBinder {
        return binder
    }
    
    override fun onStartCommand(intent: Intent?, flags: Int, START_NOT_STICKY): Int {
        // 执行下载任务
        startDownload(intent?.getStringExtra("URL"))
        return START_NOT_STICKY
    }
    
    private fun startDownload(url: String?) {
        // 模拟下载过程
        Thread {
            for (i in 1..100) {
                Thread.sleep(100)
                // 发送进度更新
                val intent = Intent("DOWNLOAD_PROGRESS")
                intent.putExtra("PROGRESS", i)
                sendBroadcast(intent)
            }
            // 下载完成
            val completeIntent = Intent("DOWNLOAD_COMPLETE")
            sendBroadcast(completeIntent)
        }.start()
    }
}

启动Service:

// 启动Service
val intent = Intent(this, DownloadService::class.java)
intent.putExtra("URL", "https://example.com/file.zip")
startService(intent)

// 绑定Service
val serviceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        val binder = service as DownloadService.DownloadBinder
        val downloadService = binder.getService()
        // 使用Service
    }
    
    override fun onServiceDisconnected(name: ComponentName?) {
        // Service断开连接
    }
}

bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)

2.4 BroadcastReceiver(广播接收器)

BroadcastReceiver用于接收和响应系统或应用发出的广播消息。

创建广播接收器:

class NetworkChangeReceiver : BroadcastReceiver() {
    
    override fun onReceive(context: Context?, intent: Intent?) {
        val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val networkInfo = connectivityManager.activeNetworkInfo
        
        if (networkInfo != null && networkInfo.isConnected) {
            // 网络已连接
            Toast.makeText(context, "网络已连接", Toast.LENGTH_SHORT).show()
        } else {
            // 网络断开
            Toast.makeText(context, "网络已断开", Toast.LENGTH_SHORT).show()
        }
    }
}

注册广播接收器:

// 动态注册(在Activity中)
val receiver = NetworkChangeReceiver()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(receiver, filter)

// 静态注册(在AndroidManifest.xml中)
<receiver android:name=".NetworkChangeReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

第三部分:UI设计与布局

3.1 布局管理器

Android提供多种布局管理器来组织UI组件。

LinearLayout(线性布局)示例:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="16dp">

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="用户名"
        android:textSize="16sp" />

    <EditText
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:hint="请输入用户名" />

</LinearLayout>

RelativeLayout(相对布局)示例:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/avatar"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:src="@drawable/ic_avatar" />

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/avatar"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="16dp"
        android:text="张三"
        android:textSize="20sp" />

</RelativeLayout>

ConstraintLayout(约束布局)示例:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="欢迎使用"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnSubmit"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="16dp"
        android:text="提交"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/title" />

</androidx.constraintlayout.widget.ConstraintLayout>

3.2 RecyclerView(列表视图)

RecyclerView是显示大量数据的高效组件。

创建RecyclerView适配器:

class UserAdapter(private val userList: List<User>) : 
    RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
    
    class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val nameTextView: TextView = itemView.findViewById(R.id.userName)
        val emailTextView: TextView = itemView.findViewById(R.id.userEmail)
        val avatarImageView: ImageView = itemView.findViewById(R.id.userAvatar)
    }
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }
    
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = userList[position]
        holder.nameTextView.text = user.name
        holder.emailTextView.text = user.email
        // 加载头像图片
        Glide.with(holder.itemView.context)
            .load(user.avatarUrl)
            .circleCrop()
            .into(holder.avatarImageView)
    }
    
    override fun getItemCount(): Int = userList.size
}

在Activity中使用RecyclerView:

class UserListActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_list)
        
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        
        // 模拟数据
        val users = listOf(
            User("张三", "zhangsan@example.com", "https://example.com/avatar1.jpg"),
            User("李四", "lisi@example.com", "https://example.com/avatar2.jpg"),
            User("王五", "wangwu@example.com", "https://example.com/avatar3.jpg")
        )
        
        val adapter = UserAdapter(users)
        recyclerView.adapter = adapter
    }
}

3.3 自定义View

创建自定义View可以实现独特的UI效果。

创建自定义View示例:

class CircleProgressView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    
    private var progress = 0
    private var maxProgress = 100
    private var progressColor = Color.BLUE
    private var backgroundColor = Color.LTGRAY
    
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val rectF = RectF()
    
    init {
        // 读取自定义属性
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView)
        progress = typedArray.getInteger(R.styleable.CircleProgressView_progress, 0)
        maxProgress = typedArray.getInteger(R.styleable.CircleProgressView_maxProgress, 100)
        progressColor = typedArray.getColor(R.styleable.CircleProgressView_progressColor, Color.BLUE)
        backgroundColor = typedArray.getColor(R.styleable.CircleProgressView_backgroundColor, Color.LTGRAY)
        typedArray.recycle()
    }
    
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        
        val centerX = width / 2f
        val centerY = height / 2f
        val radius = min(centerX, centerY) - paint.strokeWidth
        
        // 绘制背景圆
        paint.color = backgroundColor
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 20f
        canvas.drawCircle(centerX, centerY, radius, paint)
        
        // 绘制进度圆弧
        paint.color = progressColor
        val sweepAngle = (progress.toFloat() / maxProgress) * 360
        canvas.drawArc(
            centerX - radius,
            centerY - radius,
            centerX + radius,
            centerY + radius,
            -90f,
            sweepAngle,
            false,
            paint
        )
        
        // 绘制中心文字
        paint.style = Paint.Style.FILL
        paint.textSize = 40f
        paint.textAlign = Paint.Align.CENTER
        val text = "$progress%"
        canvas.drawText(text, centerX, centerY + 15f, paint)
    }
    
    fun setProgress(newProgress: Int) {
        progress = newProgress.coerceIn(0, maxProgress)
        invalidate() // 触发重绘
    }
}

在布局中使用自定义View:

<com.example.myapp.CircleProgressView
    android:id="@+id/circleProgress"
    android:layout_width="200dp"
    android:layout_height="200dp"
    app:progress="75"
    app:maxProgress="100"
    app:progressColor="#FF5722"
    app:backgroundColor="#E0E0E0" />

第四部分:数据存储与管理

4.1 SharedPreferences(轻量级存储)

SharedPreferences适合存储简单的键值对数据。

使用SharedPreferences示例:

class SettingsActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_settings)
        
        // 获取SharedPreferences
        val prefs = getSharedPreferences("app_settings", Context.MODE_PRIVATE)
        
        // 保存数据
        val editor = prefs.edit()
        editor.putString("username", "zhangsan")
        editor.putInt("age", 25)
        editor.putBoolean("notifications", true)
        editor.apply() // 异步提交
        
        // 读取数据
        val username = prefs.getString("username", "默认用户名")
        val age = prefs.getInt("age", 0)
        val notifications = prefs.getBoolean("notifications", false)
        
        // 显示数据
        findViewById<TextView>(R.id.tvUsername).text = username
        findViewById<TextView>(R.id.tvAge).text = age.toString()
        findViewById<TextView>(R.id.tvNotifications).text = if (notifications) "开启" else "关闭"
    }
}

4.2 SQLite数据库

SQLite是Android内置的轻量级关系型数据库。

创建数据库帮助类:

class DatabaseHelper(context: Context) : SQLiteOpenHelper(
    context, 
    DATABASE_NAME, 
    null, 
    DATABASE_VERSION
) {
    
    companion object {
        private const val DATABASE_NAME = "app.db"
        private const val DATABASE_VERSION = 1
        
        // 用户表
        private const val TABLE_USERS = "users"
        private const val COLUMN_ID = "id"
        private const val COLUMN_NAME = "name"
        private const val COLUMN_EMAIL = "email"
        private const val COLUMN_CREATED_AT = "created_at"
    }
    
    override fun onCreate(db: SQLiteDatabase) {
        // 创建表
        val createTableSQL = """
            CREATE TABLE $TABLE_USERS (
                $COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT,
                $COLUMN_NAME TEXT NOT NULL,
                $COLUMN_EMAIL TEXT UNIQUE,
                $COLUMN_CREATED_AT DATETIME DEFAULT CURRENT_TIMESTAMP
            )
        """.trimIndent()
        db.execSQL(createTableSQL)
    }
    
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        // 数据库升级
        db.execSQL("DROP TABLE IF EXISTS $TABLE_USERS")
        onCreate(db)
    }
    
    // 插入数据
    fun insertUser(name: String, email: String): Long {
        val db = writableDatabase
        val values = ContentValues().apply {
            put(COLUMN_NAME, name)
            put(COLUMN_EMAIL, email)
        }
        return db.insert(TABLE_USERS, null, values)
    }
    
    // 查询数据
    fun getAllUsers(): List<User> {
        val users = mutableListOf<User>()
        val db = readableDatabase
        val cursor = db.query(
            TABLE_USERS,
            null,
            null,
            null,
            null,
            null,
            "$COLUMN_CREATED_AT DESC"
        )
        
        with(cursor) {
            while (moveToNext()) {
                val id = getLong(getColumnIndexOrThrow(COLUMN_ID))
                val name = getString(getColumnIndexOrThrow(COLUMN_NAME))
                val email = getString(getColumnIndexOrThrow(COLUMN_EMAIL))
                users.add(User(id, name, email))
            }
            close()
        }
        return users
    }
    
    // 更新数据
    fun updateUser(id: Long, newName: String, newEmail: String): Int {
        val db = writableDatabase
        val values = ContentValues().apply {
            put(COLUMN_NAME, newName)
            put(COLUMN_EMAIL, newEmail)
        }
        return db.update(TABLE_USERS, values, "$COLUMN_ID = ?", arrayOf(id.toString()))
    }
    
    // 删除数据
    fun deleteUser(id: Long): Int {
        val db = writableDatabase
        return db.delete(TABLE_USERS, "$COLUMN_ID = ?", arrayOf(id.toString()))
    }
}

使用数据库:

class UserDatabaseActivity : AppCompatActivity() {
    
    private lateinit var dbHelper: DatabaseHelper
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_database)
        
        dbHelper = DatabaseHelper(this)
        
        // 插入数据
        btnInsert.setOnClickListener {
            val name = etName.text.toString()
            val email = etEmail.text.toString()
            if (name.isNotEmpty() && email.isNotEmpty()) {
                val id = dbHelper.insertUser(name, email)
                Toast.makeText(this, "用户添加成功,ID: $id", Toast.LENGTH_SHORT).show()
            }
        }
        
        // 查询数据
        btnQuery.setOnClickListener {
            val users = dbHelper.getAllUsers()
            // 显示在RecyclerView中
            showUsers(users)
        }
    }
    
    private fun showUsers(users: List<User>) {
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = UserAdapter(users)
    }
}

4.3 Room数据库(推荐)

Room是Google官方推荐的SQLite抽象层,提供更简洁的API。

添加依赖(build.gradle):

dependencies {
    def room_version = "2.5.2"
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
}

定义实体类:

@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val name: String,
    val email: String,
    @ColumnInfo(name = "created_at")
    val createdAt: Long = System.currentTimeMillis()
)

定义DAO(数据访问对象):

@Dao
interface UserDao {
    @Insert
    suspend fun insert(user: User): Long
    
    @Query("SELECT * FROM users ORDER BY created_at DESC")
    suspend fun getAllUsers(): List<User>
    
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUserById(userId: Long): User?
    
    @Update
    suspend fun update(user: User): Int
    
    @Delete
    suspend fun delete(user: User): Int
    
    @Query("DELETE FROM users WHERE id = :userId")
    suspend fun deleteUserById(userId: Long): Int
}

定义数据库类:

@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
    
    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null
        
        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

在ViewModel中使用Room:

class UserViewModel(application: Application) : AndroidViewModel(application) {
    
    private val userDao = AppDatabase.getDatabase(application).userDao()
    
    // 使用协程处理异步操作
    private val viewModelScope = CoroutineScope(Dispatchers.IO)
    
    fun insertUser(name: String, email: String) {
        viewModelScope.launch {
            val user = User(name = name, email = email)
            userDao.insert(user)
        }
    }
    
    fun getAllUsers(): LiveData<List<User>> {
        return userDao.getAllUsers().asLiveData()
    }
}

第五部分:网络通信与API集成

5.1 HTTP请求基础

使用HttpURLConnection:

class NetworkManager {
    
    companion object {
        private const val TIMEOUT = 10000 // 10秒超时
    }
    
    fun makeHttpRequest(urlString: String): String? {
        return try {
            val url = URL(urlString)
            val connection = url.openConnection() as HttpURLConnection
            connection.apply {
                connectTimeout = TIMEOUT
                readTimeout = TIMEOUT
                requestMethod = "GET"
            }
            
            val inputStream = connection.inputStream
            val reader = BufferedReader(InputStreamReader(inputStream))
            val response = StringBuilder()
            
            var line: String?
            while (reader.readLine().also { line = it } != null) {
                response.append(line)
            }
            
            reader.close()
            inputStream.close()
            connection.disconnect()
            
            response.toString()
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }
}

5.2 Retrofit(推荐)

Retrofit是Square公司开发的HTTP客户端,简化了网络请求。

添加依赖:

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:okhttp:4.11.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
}

定义API接口:

interface ApiService {
    
    @GET("users")
    suspend fun getUsers(): Response<List<User>>
    
    @GET("users/{id}")
    suspend fun getUserById(@Path("id") userId: Long): Response<User>
    
    @POST("users")
    suspend fun createUser(@Body user: User): Response<User>
    
    @PUT("users/{id}")
    suspend fun updateUser(@Path("id") userId: Long, @Body user: User): Response<User>
    
    @DELETE("users/{id}")
    suspend fun deleteUser(@Path("id") userId: Long): Response<Unit>
}

创建Retrofit实例:

object RetrofitClient {
    
    private const val BASE_URL = "https://api.example.com/"
    
    private val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        })
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build()
    
    val instance: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

在ViewModel中使用:

class UserViewModel : ViewModel() {
    
    private val apiService = RetrofitClient.instance
    
    // 使用协程处理网络请求
    private val viewModelScope = CoroutineScope(Dispatchers.IO)
    
    fun fetchUsers() {
        viewModelScope.launch {
            try {
                val response = apiService.getUsers()
                if (response.isSuccessful) {
                    val users = response.body()
                    // 更新UI
                    withContext(Dispatchers.Main) {
                        // 更新LiveData或直接更新UI
                    }
                } else {
                    // 处理错误
                    val error = response.errorBody()?.string()
                    Log.e("API", "Error: $error")
                }
            } catch (e: Exception) {
                Log.e("API", "Exception: ${e.message}")
            }
        }
    }
}

5.3 OkHttp(底层网络库)

OkHttp是Retrofit的底层网络库,也可以单独使用。

创建OkHttp客户端:

class OkHttpManager {
    
    private val client = OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .addInterceptor(HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        })
        .build()
    
    fun makeRequest(url: String, callback: (String?) -> Unit) {
        val request = Request.Builder()
            .url(url)
            .build()
        
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                callback(null)
            }
            
            override fun onResponse(call: Call, response: Response) {
                if (response.isSuccessful) {
                    val body = response.body?.string()
                    callback(body)
                } else {
                    callback(null)
                }
            }
        })
    }
}

第六部分:实战项目:天气预报应用

6.1 项目需求分析

功能需求:

  1. 显示当前天气信息(温度、湿度、风速等)
  2. 支持城市搜索
  3. 保存最近搜索的城市
  4. 显示未来5天天气预报
  5. 支持深色/浅色主题切换

技术栈:

  • UI:Jetpack Compose(现代UI工具包)
  • 网络:Retrofit + OkHttp
  • 数据存储:Room + SharedPreferences
  • 架构:MVVM + Repository模式
  • 协程:处理异步操作

6.2 项目架构设计

WeatherApp/
├── data/
│   ├── api/          # 网络API相关
│   ├── db/           # 数据库相关
│   ├── model/        # 数据模型
│   └── repository/   # 数据仓库
├── ui/
│   ├── components/   # 可复用UI组件
│   ├── screens/      # 屏幕/页面
│   └── theme/        # 主题配置
├── viewmodel/        # ViewModel
└── di/               # 依赖注入

6.3 实现步骤

步骤1:添加依赖

// build.gradle (Module)
dependencies {
    // Jetpack Compose
    implementation 'androidx.activity:activity-compose:1.8.0'
    implementation 'androidx.compose.ui:ui:1.5.4'
    implementation 'androidx.compose.material3:material3:1.1.2'
    implementation 'androidx.compose.ui:ui-tooling-preview:1.5.4'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
    
    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    
    // Room
    implementation "androidx.room:room-runtime:2.5.2"
    kapt "androidx.room:room-compiler:2.5.2"
    implementation "androidx.room:room-ktx:2.5.2"
    
    // 协程
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
    
    // 图片加载
    implementation 'io.coil-kt:coil-compose:2.4.0'
}

步骤2:定义数据模型

// WeatherResponse.kt
data class WeatherResponse(
    val location: Location,
    val current: CurrentWeather,
    val forecast: Forecast
)

data class Location(
    val name: String,
    val country: String,
    val lat: Double,
    val lon: Double
)

data class CurrentWeather(
    val temp_c: Double,
    val temp_f: Double,
    val condition: Condition,
    val wind_kph: Double,
    val humidity: Int,
    val feelslike_c: Double
)

data class Condition(
    val text: String,
    val icon: String
)

data class Forecast(
    val forecastday: List<ForecastDay>
)

data class ForecastDay(
    val date: String,
    val day: Day
)

data class Day(
    val maxtemp_c: Double,
    val mintemp_c: Double,
    val condition: Condition
)

步骤3:创建API接口

interface WeatherApiService {
    
    @GET("v1/forecast.json")
    suspend fun getWeatherForecast(
        @Query("key") apiKey: String,
        @Query("q") location: String,
        @Query("days") days: Int = 5
    ): Response<WeatherResponse>
    
    @GET("v1/search.json")
    suspend fun searchLocations(
        @Query("key") apiKey: String,
        @Query("q") query: String
    ): Response<List<Location>>
}

步骤4:创建Repository

class WeatherRepository(
    private val apiService: WeatherApiService,
    private val weatherDao: WeatherDao,
    private val prefs: SharedPreferences
) {
    
    companion object {
        private const val API_KEY = "YOUR_API_KEY"
        private const val PREFS_LAST_CITY = "last_city"
    }
    
    suspend fun getWeatherForecast(location: String): Result<WeatherResponse> {
        return try {
            val response = apiService.getWeatherForecast(API_KEY, location)
            if (response.isSuccessful) {
                val weather = response.body()
                weather?.let {
                    // 保存到数据库
                    saveWeatherToDb(it)
                    // 保存最后搜索的城市
                    saveLastCity(location)
                    Result.success(it)
                } ?: Result.failure(Exception("Empty response"))
            } else {
                Result.failure(Exception("API error: ${response.code()}"))
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    suspend fun searchLocations(query: String): Result<List<Location>> {
        return try {
            val response = apiService.searchLocations(API_KEY, query)
            if (response.isSuccessful) {
                val locations = response.body() ?: emptyList()
                Result.success(locations)
            } else {
                Result.failure(Exception("API error: ${response.code()}"))
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    fun getLastCity(): String? {
        return prefs.getString(PREFS_LAST_CITY, null)
    }
    
    private fun saveLastCity(city: String) {
        prefs.edit().putString(PREFS_LAST_CITY, city).apply()
    }
    
    private suspend fun saveWeatherToDb(weather: WeatherResponse) {
        // 保存到Room数据库
        // 实现省略
    }
}

步骤5:创建ViewModel

class WeatherViewModel(
    private val repository: WeatherRepository
) : ViewModel() {
    
    private val _weatherState = MutableStateFlow<WeatherState>(WeatherState.Loading)
    val weatherState: StateFlow<WeatherState> = _weatherState
    
    private val _searchResults = MutableStateFlow<List<Location>>(emptyList())
    val searchResults: StateFlow<List<Location>> = _searchResults
    
    fun loadWeather(location: String) {
        viewModelScope.launch {
            _weatherState.value = WeatherState.Loading
            val result = repository.getWeatherForecast(location)
            _weatherState.value = when {
                result.isSuccess -> WeatherState.Success(result.getOrNull()!!)
                else -> WeatherState.Error(result.exceptionOrNull()?.message ?: "Unknown error")
            }
        }
    }
    
    fun searchLocations(query: String) {
        viewModelScope.launch {
            val result = repository.searchLocations(query)
            if (result.isSuccess) {
                _searchResults.value = result.getOrNull() ?: emptyList()
            }
        }
    }
    
    fun loadLastCity() {
        val lastCity = repository.getLastCity()
        if (lastCity != null) {
            loadWeather(lastCity)
        } else {
            // 默认城市
            loadWeather("Beijing")
        }
    }
}

sealed class WeatherState {
    object Loading : WeatherState()
    data class Success(val weather: WeatherResponse) : WeatherState()
    data class Error(val message: String) : WeatherState()
}

步骤6:使用Jetpack Compose创建UI

@Composable
fun WeatherScreen(viewModel: WeatherViewModel) {
    val weatherState by viewModel.weatherState.collectAsState()
    val searchResults by viewModel.searchResults.collectAsState()
    
    var searchText by remember { mutableStateOf("") }
    var showSearch by remember { mutableStateOf(false) }
    
    LaunchedEffect(Unit) {
        viewModel.loadLastCity()
    }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.background)
    ) {
        // 顶部栏
        TopAppBar(
            title = { Text("天气预报") },
            actions = {
                IconButton(onClick = { showSearch = !showSearch }) {
                    Icon(Icons.Default.Search, contentDescription = "搜索")
                }
            }
        )
        
        // 搜索框
        if (showSearch) {
            OutlinedTextField(
                value = searchText,
                onValueChange = { 
                    searchText = it
                    if (it.length >= 2) {
                        viewModel.searchLocations(it)
                    }
                },
                label = { Text("搜索城市") },
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            )
            
            // 搜索结果
            LazyColumn {
                items(searchResults) { location ->
                    SearchItem(location) { selectedLocation ->
                        viewModel.loadWeather(selectedLocation.name)
                        showSearch = false
                        searchText = ""
                    }
                }
            }
        }
        
        // 天气内容
        when (val state = weatherState) {
            is WeatherState.Loading -> {
                Box(
                    modifier = Modifier.fillMaxSize(),
                    contentAlignment = Alignment.Center
                ) {
                    CircularProgressIndicator()
                }
            }
            is WeatherState.Success -> {
                WeatherContent(weather = state.weather)
            }
            is WeatherState.Error -> {
                Box(
                    modifier = Modifier.fillMaxSize(),
                    contentAlignment = Alignment.Center
                ) {
                    Text(
                        text = "错误: ${state.message}",
                        color = MaterialTheme.colorScheme.error
                    )
                }
            }
        }
    }
}

@Composable
fun WeatherContent(weather: WeatherResponse) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        // 当前天气
        item {
            CurrentWeatherCard(weather.current, weather.location)
        }
        
        // 未来天气预报
        items(weather.forecast.forecastday) { forecastDay ->
            ForecastDayCard(forecastDay)
        }
    }
}

@Composable
fun CurrentWeatherCard(current: CurrentWeather, location: Location) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(
                text = location.name,
                style = MaterialTheme.typography.headlineMedium,
                fontWeight = FontWeight.Bold
            )
            
            Text(
                text = "${current.temp_c}°C",
                style = MaterialTheme.typography.displayLarge,
                color = MaterialTheme.colorScheme.primary
            )
            
            Text(
                text = current.condition.text,
                style = MaterialTheme.typography.titleMedium
            )
            
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceEvenly
            ) {
                WeatherStat("湿度", "${current.humidity}%")
                WeatherStat("风速", "${current.wind_kph} km/h")
                WeatherStat("体感", "${current.feelslike_c}°C")
            }
        }
    }
}

@Composable
fun ForecastDayCard(forecastDay: ForecastDay) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Text(
                text = forecastDay.date,
                style = MaterialTheme.typography.titleMedium
            )
            
            Row(
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                Text(
                    text = "${forecastDay.day.mintemp_c}°C",
                    style = MaterialTheme.typography.bodyLarge,
                    color = MaterialTheme.colorScheme.secondary
                )
                Text(
                    text = "${forecastDay.day.maxtemp_c}°C",
                    style = MaterialTheme.typography.bodyLarge,
                    color = MaterialTheme.colorScheme.primary
                )
            }
        }
    }
}

@Composable
fun WeatherStat(label: String, value: String) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = label,
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )
        Text(
            text = value,
            style = MaterialTheme.typography.bodyMedium,
            fontWeight = FontWeight.Bold
        )
    }
}

@Composable
fun SearchItem(location: Location, onClick: (Location) -> Unit) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onClick(location) }
            .padding(horizontal = 16.dp, vertical = 4.dp),
        elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
    ) {
        Row(
            modifier = Modifier.padding(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Icon(
                imageVector = Icons.Default.LocationOn,
                contentDescription = null,
                tint = MaterialTheme.colorScheme.primary
            )
            Spacer(modifier = Modifier.width(12.dp))
            Column {
                Text(
                    text = location.name,
                    style = MaterialTheme.typography.titleMedium
                )
                Text(
                    text = location.country,
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.onSurfaceVariant
                )
            }
        }
    }
}

步骤7:配置主题和深色模式

// Theme.kt
@Composable
fun WeatherAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = if (darkTheme) {
        darkColorScheme(
            primary = Color(0xFFBB86FC),
            secondary = Color(0xFF03DAC5),
            background = Color(0xFF121212),
            surface = Color(0xFF1E1E1E),
            onPrimary = Color.Black,
            onSecondary = Color.Black,
            onBackground = Color.White,
            onSurface = Color.White
        )
    } else {
        lightColorScheme(
            primary = Color(0xFF6200EE),
            secondary = Color(0xFF03DAC5),
            background = Color(0xFFFFFFFF),
            surface = Color(0xFFF5F5F5),
            onPrimary = Color.White,
            onSecondary = Color.Black,
            onBackground = Color.Black,
            onSurface = Color.Black
        )
    }
    
    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

步骤8:配置依赖注入(可选)

// 使用Hilt进行依赖注入
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.weatherapi.com/")
            .client(OkHttpClient.Builder().build())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    
    @Provides
    @Singleton
    fun provideWeatherApi(retrofit: Retrofit): WeatherApiService {
        return retrofit.create(WeatherApiService::class.java)
    }
    
    @Provides
    @Singleton
    fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences("weather_prefs", Context.MODE_PRIVATE)
    }
    
    @Provides
    @Singleton
    fun provideWeatherRepository(
        apiService: WeatherApiService,
        @ApplicationContext context: Context
    ): WeatherRepository {
        val prefs = context.getSharedPreferences("weather_prefs", Context.MODE_PRIVATE)
        return WeatherRepository(apiService, prefs)
    }
}

@HiltViewModel
class WeatherViewModel @Inject constructor(
    private val repository: WeatherRepository
) : ViewModel() {
    // ViewModel实现同上
}

第七部分:高级主题与最佳实践

7.1 性能优化

1. 内存泄漏检测

// 使用LeakCanary检测内存泄漏
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return
        }
        LeakCanary.install(this)
    }
}

// 在Activity中避免内存泄漏
class MyActivity : AppCompatActivity() {
    private var listener: MyListener? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 错误做法:直接使用匿名内部类
        // val listener = object : MyListener { ... }
        
        // 正确做法:使用弱引用或及时清理
        listener = MyListener(this)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        listener = null // 清理引用
    }
}

2. 图片加载优化

// 使用Coil加载图片
@Composable
fun OptimizedImage(
    url: String,
    modifier: Modifier = Modifier
) {
    val painter = rememberAsyncImagePainter(
        model = ImageRequest.Builder(LocalContext.current)
            .data(url)
            .crossfade(true)
            .transformations(CircleCropTransformation())
            .memoryCachePolicy(CachePolicy.ENABLED)
            .diskCachePolicy(CachePolicy.ENABLED)
            .build()
    )
    
    Image(
        painter = painter,
        contentDescription = null,
        modifier = modifier,
        contentScale = ContentScale.Crop
    )
}

3. 列表优化

// 使用DiffUtil优化RecyclerView
class UserDiffCallback(
    private val oldList: List<User>,
    private val newList: List<User>
) : DiffUtil.Callback() {
    
    override fun getOldListSize(): Int = oldList.size
    override fun getNewListSize(): Int = newList.size
    
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition].id == newList[newItemPosition].id
    }
    
    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] == newList[newItemPosition]
    }
}

// 在适配器中使用
fun updateList(newList: List<User>) {
    val diffCallback = UserDiffCallback(currentList, newList)
    val diffResult = DiffUtil.calculateDiff(diffCallback)
    currentList = newList
    diffResult.dispatchUpdatesTo(this)
}

7.2 安全性

1. 数据加密

// 使用Android Keystore进行加密
class SecureStorage(context: Context) {
    
    private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply {
        load(null)
    }
    
    private fun generateKey(alias: String) {
        if (!keyStore.containsAlias(alias)) {
            val keyGenerator = KeyGenerator.getInstance(
                KeyProperties.KEY_ALGORITHM_AES,
                "AndroidKeyStore"
            )
            
            val keyGenSpec = KeyGenParameterSpec.Builder(
                alias,
                KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
            )
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .setKeySize(256)
                .setUserAuthenticationRequired(true)
                .setUserAuthenticationValidityDurationSeconds(30)
                .build()
            
            keyGenerator.init(keyGenSpec)
            keyGenerator.generateKey()
        }
    }
    
    fun encryptData(alias: String, data: String): ByteArray {
        generateKey(alias)
        
        val secretKey = keyStore.getKey(alias, null) as SecretKey
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(Cipher.ENCRYPT_MODE, secretKey)
        
        val iv = cipher.iv
        val encrypted = cipher.doFinal(data.toByteArray(Charsets.UTF_8))
        
        return iv + encrypted
    }
    
    fun decryptData(alias: String, encryptedData: ByteArray): String {
        val iv = encryptedData.copyOfRange(0, 12)
        val data = encryptedData.copyOfRange(12, encryptedData.size)
        
        val secretKey = keyStore.getKey(alias, null) as SecretKey
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        val spec = GCMParameterSpec(128, iv)
        cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
        
        val decrypted = cipher.doFinal(data)
        return String(decrypted, Charsets.UTF_8)
    }
}

2. 网络安全配置

<!-- network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <!-- 允许明文HTTP请求(仅用于开发) -->
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    
    <!-- 生产环境配置 -->
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">api.example.com</domain>
        <trust-anchors>
            <certificates src="system" />
            <certificates src="@raw/my_ca" />
        </trust-anchors>
    </domain-config>
</network-security-config>

7.3 测试

1. 单元测试

// WeatherViewModelTest.kt
class WeatherViewModelTest {
    
    private lateinit var viewModel: WeatherViewModel
    private lateinit var repository: WeatherRepository
    
    @Before
    fun setup() {
        repository = mockk()
        viewModel = WeatherViewModel(repository)
    }
    
    @Test
    fun `loadWeather should update state to Success`() = runTest {
        // 准备测试数据
        val mockWeather = WeatherResponse(
            location = Location("Beijing", "China", 39.9042, 116.4074),
            current = CurrentWeather(
                temp_c = 25.0,
                temp_f = 77.0,
                condition = Condition("Sunny", "icon.png"),
                wind_kph = 10.0,
                humidity = 60,
                feelslike_c = 27.0
            ),
            forecast = Forecast(emptyList())
        )
        
        // Mock仓库行为
        coEvery { repository.getWeatherForecast("Beijing") } returns Result.success(mockWeather)
        
        // 执行测试
        viewModel.loadWeather("Beijing")
        
        // 验证结果
        val state = viewModel.weatherState.value
        assert(state is WeatherState.Success)
        assert((state as WeatherState.Success).weather.location.name == "Beijing")
    }
    
    @Test
    fun `loadWeather should update state to Error`() = runTest {
        // Mock错误情况
        coEvery { repository.getWeatherForecast("InvalidCity") } returns 
            Result.failure(Exception("City not found"))
        
        viewModel.loadWeather("InvalidCity")
        
        val state = viewModel.weatherState.value
        assert(state is WeatherState.Error)
        assert((state as WeatherState.Error).message.contains("City not found"))
    }
}

2. UI测试

// WeatherScreenTest.kt
@RunWith(AndroidJUnit4::class)
class WeatherScreenTest {
    
    @get:Rule
    val composeTestRule = createComposeRule()
    
    @Test
    fun weatherScreenDisplaysLoadingState() {
        // 创建ViewModel并设置为Loading状态
        val viewModel = WeatherViewModel(mockRepository())
        viewModel._weatherState.value = WeatherState.Loading
        
        composeTestRule.setContent {
            WeatherScreen(viewModel = viewModel)
        }
        
        // 验证显示加载指示器
        composeTestRule.onNodeWithText("加载中").assertDoesNotExist()
        composeTestRule.onNodeWithTag("loading_indicator").assertIsDisplayed()
    }
    
    @Test
    fun weatherScreenDisplaysErrorState() {
        val viewModel = WeatherViewModel(mockRepository())
        viewModel._weatherState.value = WeatherState.Error("网络错误")
        
        composeTestRule.setContent {
            WeatherScreen(viewModel = viewModel)
        }
        
        // 验证显示错误信息
        composeTestRule.onNodeWithText("错误: 网络错误").assertIsDisplayed()
    }
}

7.4 发布准备

1. 代码混淆

// build.gradle (Module)
android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

// proguard-rules.pro
# 保持所有Activity类
-keep public class * extends android.app.Activity

# 保持所有Fragment类
-keep public class * extends androidx.fragment.app.Fragment

# 保持所有ViewModel类
-keep public class * extends androidx.lifecycle.ViewModel

# 保持所有数据类
-keep class com.example.weatherapp.data.model.** { *; }

# 保持Retrofit相关类
-keepattributes Signature
-keepattributes *Annotation*
-keep class retrofit2.** { *; }
-keepclasseswithmembers class * {
    @retrofit2.http.* <methods>;
}

# 保持Room相关类
-keep class * extends androidx.room.RoomDatabase
-keep @androidx.room.Entity class *
-dontwarn androidx.room.paging.**

2. 版本管理

// build.gradle (Module)
android {
    defaultConfig {
        versionCode 1
        versionName "1.0.0"
        applicationId "com.example.weatherapp"
        
        // 版本号管理
        def versionMajor = 1
        def versionMinor = 0
        def versionPatch = 0
        versionCode = versionMajor * 10000 + versionMinor * 100 + versionPatch
        versionName = "${versionMajor}.${versionMinor}.${versionPatch}"
    }
}

3. 应用签名

# 生成签名密钥
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000

# 配置gradle.properties
MYAPP_RELEASE_STORE_FILE=my-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=your_password
MYAPP_RELEASE_KEY_PASSWORD=your_password

# 在build.gradle中配置
android {
    signingConfigs {
        release {
            storeFile file(MYAPP_RELEASE_STORE_FILE)
            storePassword MYAPP_RELEASE_STORE_PASSWORD
            keyAlias MYAPP_RELEASE_KEY_ALIAS
            keyPassword MYAPP_RELEASE_KEY_PASSWORD
        }
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

第八部分:学习资源与进阶路径

8.1 官方资源

  1. Android开发者官网https://developer.android.com/
  2. Android开发者指南https://developer.android.com/guide
  3. Jetpack Compose官方文档https://developer.android.com/jetpack/compose
  4. Android Studio官方文档https://developer.android.com/studio

8.2 推荐书籍

  1. 《Android编程权威指南》 - Brian Hardy, Bill Phillips
  2. 《Kotlin编程权威指南》 - Joshua Bloch
  3. 《Android开发艺术探索》 - 任玉刚
  4. 《Jetpack Compose实战》 - Google官方团队

8.3 在线课程

  1. Udacity Android Developer Nanodegree
  2. Coursera Android App Development Specialization
  3. Pluralsight Android Development Path
  4. Google官方Android开发课程

8.4 社区与论坛

  1. Stack Overflowhttps://stackoverflow.com/questions/tagged/android
  2. Android开发者社区https://developer.android.com/community
  3. Kotlin Slack社区https://kotlinlang.slack.com/
  4. Reddit r/androiddevhttps://www.reddit.com/r/androiddev/

8.5 进阶学习路径

阶段1:基础巩固(1-2个月)

  • 深入理解Activity和Fragment生命周期
  • 掌握Jetpack组件(ViewModel、LiveData、Room)
  • 学习Kotlin协程和Flow

阶段2:UI开发(1个月)

  • 精通Jetpack Compose
  • 学习自定义View和动画
  • 掌握Material Design规范

阶段3:网络与数据(1个月)

  • 深入Retrofit和OkHttp
  • 学习数据缓存策略
  • 掌握JSON解析和序列化

阶段4:架构设计(1个月)

  • 学习MVVM、MVI等架构模式
  • 掌握依赖注入(Hilt/Dagger)
  • 学习测试驱动开发(TDD)

阶段5:性能优化与发布(1个月)

  • 内存泄漏检测和优化
  • 网络优化和缓存策略
  • 应用发布和版本管理

阶段6:专项领域(持续学习)

  • 音视频开发
  • 跨平台开发(Flutter/React Native)
  • 机器学习集成
  • 物联网开发

结语

Android开发是一个持续学习的过程。从零基础到实战开发,需要系统的学习和大量的实践。本指南提供了从环境搭建到实战项目的完整路径,涵盖了Android开发的各个方面。

记住以下几点建议:

  1. 动手实践:理论知识必须通过实践来巩固
  2. 阅读源码:学习优秀开源项目的代码结构
  3. 参与社区:与其他开发者交流学习
  4. 持续学习:Android技术栈更新迅速,保持学习热情
  5. 构建作品:通过实际项目展示你的能力

祝你在Android开发的道路上取得成功!