引言

Android开发是一个充满挑战和机遇的领域。从简单的“Hello World”应用到复杂的社交网络或游戏应用,开发者需要掌握一系列技能。本文将通过实例分析,从基础到进阶,提供一份实战指南,并针对常见问题提供解决方案。我们将涵盖Android开发的核心概念、UI设计、数据存储、网络通信、性能优化以及现代架构(如MVVM)的实践。

第一部分:Android开发基础

1.1 Android开发环境搭建

在开始编码之前,你需要搭建一个完整的开发环境。推荐使用Android Studio,它是Google官方的集成开发环境(IDE)。

步骤:

  1. 下载并安装Android Studio(从官网获取)。
  2. 安装过程中,确保勾选了Android SDK、Android SDK Platform和Android Virtual Device(AVD)。
  3. 配置环境变量(可选,但推荐):将ANDROID_HOME指向你的SDK路径,并将%ANDROID_HOME%\platform-tools%ANDROID_HOME%\tools添加到PATH中。

验证安装: 打开终端或命令提示符,输入:

adb version

如果显示版本信息,说明环境配置成功。

1.2 第一个Android应用:Hello World

让我们创建一个简单的应用来理解Android项目结构。

项目结构:

  • app/src/main/java/:存放Java/Kotlin源代码。
  • app/src/main/res/:存放资源文件(布局、图片、字符串等)。
  • app/src/main/AndroidManifest.xml:应用的配置文件,声明权限、组件等。

示例:修改MainActivity.kt(Kotlin)

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)

        val textView = findViewById<TextView>(R.id.textView)
        textView.text = "Hello, Android Developer!"
    }
}

布局文件: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!" />

</LinearLayout>

运行应用: 点击Android Studio的运行按钮(绿色三角形),选择模拟器或连接的真机。应用启动后,你会看到屏幕中央显示“Hello, Android Developer!”。

1.3 Activity生命周期

Activity是Android应用的基本组件,负责管理用户界面。理解其生命周期至关重要。

生命周期回调方法:

  • onCreate():创建Activity时调用,用于初始化UI和数据。
  • onStart():Activity变为可见时调用。
  • onResume():Activity获得用户焦点时调用。
  • onPause():Activity失去焦点时调用(如来电)。
  • onStop():Activity不再可见时调用。
  • onDestroy():Activity被销毁时调用。
  • onRestart():从停止状态恢复时调用。

示例:生命周期日志

class MainActivity : AppCompatActivity() {
    private val TAG = "Lifecycle"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d(TAG, "onCreate")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(TAG, "onRestart")
    }
}

通过Logcat查看日志,你可以观察到Activity在不同状态下的变化。

1.4 常见问题:应用崩溃(NullPointerException)

问题描述: 应用在运行时突然崩溃,Logcat显示NullPointerException

原因分析:

  • 尝试访问一个空对象的属性或方法。
  • 布局文件中的控件ID在代码中未正确引用。

解决方案:

  1. 检查空值: 使用Kotlin的空安全特性(?.?:)。

    
    val textView = findViewById<TextView>(R.id.textView)
    textView?.text = "Hello" // 如果textView为null,不会执行
    

  2. 确保控件ID正确: 检查activity_main.xml中的控件ID是否与findViewById中的ID一致。

  3. 使用View Binding(推荐): View Binding可以避免findViewById的空指针问题。

    • build.gradle中启用:

      
      android {
          ...
          buildFeatures {
              viewBinding true
          }
      }
      

    • 在Activity中使用:

      private lateinit var binding: ActivityMainBinding
      
      
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          binding = ActivityMainBinding.inflate(layoutInflater)
          setContentView(binding.root)
          binding.textView.text = "Hello, View Binding!"
      }
      

第二部分:UI设计与交互

2.1 布局管理器

Android提供了多种布局管理器来组织UI元素。

常用布局:

  • LinearLayout:线性排列,支持水平或垂直方向。
  • RelativeLayout:相对定位,可以指定控件之间的相对位置。
  • ConstraintLayout:最灵活的布局,支持复杂的约束关系,推荐使用。
  • RecyclerView:用于显示大量数据的列表,性能优于ListView。

示例:使用ConstraintLayout创建登录界面

<?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">

    <EditText
        android:id="@+id/etUsername"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="用户名"
        android:inputType="text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="100dp"
        android:layout_marginHorizontal="20dp" />

    <EditText
        android:id="@+id/etPassword"
        android:layout_width="0dp"
       android:layout_height="wrap_content"
        android:hint="密码"
        android:inputType="textPassword"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/etUsername"
        android:layout_marginTop="20dp"
        android:layout_marginHorizontal="20dp" />

    <Button
        android:id="@+id/btnLogin"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="登录"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/etPassword"
        android:layout_marginTop="30dp"
        android:layout_marginHorizontal="20dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.2 RecyclerView的使用

RecyclerView是显示列表数据的高效组件。

步骤:

  1. 添加依赖:build.gradle中添加:

    
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
    

  2. 创建列表项布局: item_user.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="wrap_content"
        android:orientation="horizontal"
        android:padding="16dp">
    
    
        <TextView
            android:id="@+id/tvName"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="18sp" />
    
    
        <TextView
            android:id="@+id/tvAge"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp" />
    
    
    </LinearLayout>
    
  3. 创建适配器:

    class UserAdapter(private val userList: List<User>) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
    
    
        class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val tvName: TextView = itemView.findViewById(R.id.tvName)
            val tvAge: TextView = itemView.findViewById(R.id.tvAge)
        }
    
    
        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.tvName.text = user.name
            holder.tvAge.text = user.age.toString()
        }
    
    
        override fun getItemCount(): Int = userList.size
    }
    
    
    data class User(val name: String, val age: Int)
    
  4. 在Activity中使用:

    class RecyclerViewActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_recycler_view)
    
    
            val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
            recyclerView.layoutManager = LinearLayoutManager(this)
    
    
            val userList = listOf(
                User("Alice", 25),
                User("Bob", 30),
                User("Charlie", 22)
            )
    
    
            val adapter = UserAdapter(userList)
            recyclerView.adapter = adapter
        }
    }
    

2.3 常见问题:RecyclerView不显示数据

问题描述: RecyclerView已经添加到布局中,但运行后没有显示任何数据。

原因分析:

  1. 未设置布局管理器: RecyclerView必须设置一个LayoutManager,否则无法显示内容。
  2. 适配器数据为空: 传递给适配器的数据列表为空。
  3. 布局高度问题: RecyclerView的layout_height设置为wrap_content,但父布局可能没有正确计算高度。

解决方案:

  1. 确保设置LayoutManager:

    
    recyclerView.layoutManager = LinearLayoutManager(this)
    

  2. 检查数据源: 确保传递给适配器的数据列表不为空。

  3. 设置固定高度或使用match_parent

    
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    

  4. 使用View Binding: 确保正确引用RecyclerView。

    private lateinit var binding: ActivityRecyclerViewBinding
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityRecyclerViewBinding.inflate(layoutInflater)
        setContentView(binding.root)
    
    
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        // 设置适配器...
    }
    

第三部分:数据存储

3.1 SharedPreferences

SharedPreferences是Android中最简单的数据存储方式,适用于存储少量简单的键值对数据(如用户设置)。

示例:保存和读取用户偏好

class SharedPreferencesActivity : AppCompatActivity() {
    private val PREFS_NAME = "MyPrefs"
    private val KEY_NAME = "name"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_shared_preferences)

        val etName = findViewById<EditText>(R.id.etName)
        val btnSave = findViewById<Button>(R.id.btnSave)
        val btnLoad = findViewById<Button>(R.id.btnLoad)
        val tvResult = findViewById<TextView>(R.id.tvResult)

        btnSave.setOnClickListener {
            val name = etName.text.toString()
            val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
            val editor = prefs.edit()
            editor.putString(KEY_NAME, name)
            editor.apply() // 异步提交
            Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show()
        }

        btnLoad.setOnClickListener {
            val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
            val name = prefs.getString(KEY_NAME, "未找到")
            tvResult.text = "保存的名字是:$name"
        }
    }
}

3.2 Room数据库

Room是Google推荐的SQLite数据库封装库,提供了编译时检查,简化了数据库操作。

步骤:

  1. 添加依赖:build.gradle中添加:

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

  2. 定义实体(Entity):

    
    @Entity(tableName = "users")
    data class User(
        @PrimaryKey(autoGenerate = true) val id: Int = 0,
        @ColumnInfo(name = "name") val name: String,
        @ColumnInfo(name = "age") val age: Int
    )
    

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

    @Dao
    interface UserDao {
        @Query("SELECT * FROM users")
        fun getAll(): List<User>
    
    
        @Insert
        suspend fun insert(user: User)
    
    
        @Delete
        suspend fun delete(user: User)
    }
    
  4. 定义数据库类:

    @Database(entities = [User::class], version = 1)
    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
                }
            }
        }
    }
    
  5. 在ViewModel中使用:

    class UserViewModel(application: Application) : AndroidViewModel(application) {
        private val userDao = AppDatabase.getDatabase(application).userDao()
    
    
        fun insertUser(name: String, age: Int) {
            viewModelScope.launch(Dispatchers.IO) {
                userDao.insert(User(name = name, age = age))
            }
        }
    
    
        fun getAllUsers(): LiveData<List<User>> {
            return userDao.getAll().asLiveData()
        }
    }
    

3.3 常见问题:Room数据库迁移问题

问题描述: 当数据库结构发生变化(如添加新表或修改列)时,应用崩溃,提示数据库版本不匹配。

原因分析: Room要求在数据库版本增加时提供迁移策略,否则会抛出异常。

解决方案:

  1. 增加数据库版本号:@Database注解中增加version

    
    @Database(entities = [User::class], version = 2) // 从1增加到2
    

  2. 提供迁移策略:

    val MIGRATION_1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("ALTER TABLE users ADD COLUMN email TEXT")
        }
    }
    
    
    val instance = Room.databaseBuilder(
        context.applicationContext,
        AppDatabase::class.java,
        "app_database"
    ).addMigrations(MIGRATION_1_2)
    .build()
    
  3. 测试迁移: 使用Room.inMemoryDatabaseBuilder进行单元测试。

第四部分:网络通信

4.1 Retrofit + OkHttp

Retrofit是基于OkHttp的HTTP客户端,用于与RESTful API通信。

步骤:

  1. 添加依赖:

    
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    

  2. 定义API接口:

    
    interface ApiService {
        @GET("users/{id}")
        suspend fun getUser(@Path("id") id: Int): Response<User>
    }
    

  3. 创建Retrofit实例:

    object RetrofitClient {
        private const val BASE_URL = "https://api.example.com/"
    
    
        val instance: ApiService by lazy {
            val okHttpClient = OkHttpClient.Builder()
                .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                .build()
    
    
            Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(ApiService::class.java)
        }
    }
    
  4. 在ViewModel中使用:

    class UserViewModel : ViewModel() {
        private val apiService = RetrofitClient.instance
    
    
        suspend fun fetchUser(id: Int): User? {
            return try {
                val response = apiService.getUser(id)
                if (response.isSuccessful) {
                    response.body()
                } else {
                    null
                }
            } catch (e: Exception) {
                null
            }
        }
    }
    

4.2 协程与LiveData

使用协程处理异步操作,结合LiveData更新UI。

示例:在ViewModel中使用协程和LiveData

class UserViewModel : ViewModel() {
    private val _userLiveData = MutableLiveData<User>()
    val userLiveData: LiveData<User> = _userLiveData

    private val _errorLiveData = MutableLiveData<String>()
    val errorLiveData: LiveData<String> = _errorLiveData

    fun fetchUser(id: Int) {
        viewModelScope.launch {
            try {
                val user = RetrofitClient.instance.getUser(id).body()
                if (user != null) {
                    _userLiveData.postValue(user)
                } else {
                    _errorLiveData.postValue("User not found")
                }
            } catch (e: Exception) {
                _errorLiveData.postValue("Network error: ${e.message}")
            }
        }
    }
}

4.3 常见问题:网络请求在主线程执行

问题描述: 应用在进行网络请求时崩溃,Logcat显示NetworkOnMainThreadException

原因分析: Android不允许在主线程(UI线程)执行网络操作,因为这会阻塞UI,导致应用无响应。

解决方案:

  1. 使用协程: 如上例所示,使用viewModelScope.launch在后台线程执行网络请求。
  2. 使用AsyncTask(已弃用): 不推荐,但了解历史。
  3. 使用RxJava: 通过subscribeOn(Schedulers.io())切换线程。
  4. 使用线程池: 例如ExecutorService,但协程更简单。

示例:使用线程池(不推荐,仅作对比)

private val executor = Executors.newSingleThreadExecutor()

fun fetchUser(id: Int) {
    executor.execute {
        // 网络请求
        val user = RetrofitClient.instance.getUser(id).body()
        // 更新UI需要切回主线程
        runOnUiThread {
            // 更新UI
        }
    }
}

第五部分:性能优化

5.1 内存泄漏检测

内存泄漏是Android应用常见的性能问题,会导致应用占用内存不断增加,最终被系统杀死。

常见原因:

  • 非静态内部类持有外部类的引用(如Handler、Runnable)。
  • 未正确注销监听器(如广播接收器、事件总线)。
  • 长时间持有Context(如静态变量)。

检测工具:

  • LeakCanary: 一个开源的内存泄漏检测库。
    • 添加依赖:implementation 'com.squareup.leakcanary:leakcanary-android:2.12'
    • 运行应用,当发生内存泄漏时,会弹出通知。

示例:修复非静态内部类导致的内存泄漏

class MainActivity : AppCompatActivity() {
    // 错误示例:非静态内部类持有外部类的引用
    private inner class MyHandler : Handler() {
        override fun handleMessage(msg: Message) {
            // 访问MainActivity的成员变量,导致内存泄漏
        }
    }

    // 正确示例:使用静态内部类 + 弱引用
    private class MyHandler(activity: MainActivity) : Handler() {
        private val weakActivity = WeakReference(activity)

        override fun handleMessage(msg: Message) {
            weakActivity.get()?.let { activity ->
                // 安全地访问activity
            }
        }
    }
}

5.2 布局优化

复杂的布局会导致渲染性能下降,尤其是列表滚动时。

优化技巧:

  1. 减少布局层级: 使用ConstraintLayout代替嵌套的LinearLayout
  2. 使用<include>标签: 复用布局。
  3. 使用<merge>标签: 减少不必要的ViewGroup。
  4. 使用ViewStub 延迟加载不常用的布局。

示例:使用ConstraintLayout减少嵌套

<!-- 优化前:嵌套的LinearLayout -->
<LinearLayout ...>
    <LinearLayout ...>
        <TextView ... />
        <TextView ... />
    </LinearLayout>
</LinearLayout>

<!-- 优化后:使用ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout ...>
    <TextView
        android:id="@+id/tv1"
        ... />
    <TextView
        android:id="@+id/tv2"
        app:layout_constraintTop_toBottomOf="@id/tv1"
        ... />
</androidx.constraintlayout.widget.ConstraintLayout>

5.3 常见问题:应用卡顿(ANR)

问题描述: 应用无响应(ANR),系统弹出“应用无响应”对话框。

原因分析:

  • 主线程执行耗时操作(如网络请求、数据库查询、复杂计算)。
  • 主线程被阻塞(如等待锁、死锁)。

解决方案:

  1. 将耗时操作移到后台线程: 使用协程、RxJava或线程池。
  2. 优化数据库查询: 使用索引、分页查询。
  3. 避免在主线程进行大量UI操作: 如一次性添加大量View到布局中。

示例:使用协程避免主线程阻塞

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 错误:在主线程执行耗时操作
        // val result = heavyCalculation()

        // 正确:使用协程在后台线程执行
        lifecycleScope.launch {
            val result = withContext(Dispatchers.IO) {
                heavyCalculation()
            }
            // 更新UI
            textView.text = result
        }
    }

    private fun heavyCalculation(): String {
        // 模拟耗时操作
        Thread.sleep(5000)
        return "Calculation done"
    }
}

第六部分:现代架构(MVVM)

6.1 MVVM架构概述

MVVM(Model-View-ViewModel)是Google推荐的Android架构模式,将UI逻辑与业务逻辑分离。

组件:

  • Model: 数据层,负责数据获取和存储(如数据库、网络)。
  • View: UI层,负责显示数据和用户交互(如Activity、Fragment)。
  • ViewModel: 业务逻辑层,持有UI状态,与Model交互,不持有View的引用。

优势:

  • 分离关注点,易于测试和维护。
  • 支持配置更改(如屏幕旋转)时保留数据。
  • 与LiveData或StateFlow结合,实现响应式UI。

6.2 实战:构建一个MVVM应用

项目结构:

app/
├── data/
│   ├── model/          # 数据模型
│   ├── repository/     # 数据仓库
│   └── api/            # 网络接口
├── ui/
│   ├── view/           # View层(Activity/Fragment)
│   └── viewmodel/      # ViewModel层
└── di/                 # 依赖注入(可选)

步骤:

  1. 定义Model:

    
    data class User(val id: Int, val name: String, val age: Int)
    

  2. 定义Repository:

    
    class UserRepository(private val apiService: ApiService) {
        suspend fun getUser(id: Int): User? {
            return try {
                apiService.getUser(id).body()
            } catch (e: Exception) {
                null
            }
        }
    }
    

  3. 定义ViewModel:

    class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
        private val _userLiveData = MutableLiveData<User>()
        val userLiveData: LiveData<User> = _userLiveData
    
    
        fun fetchUser(id: Int) {
            viewModelScope.launch {
                val user = userRepository.getUser(id)
                _userLiveData.postValue(user)
            }
        }
    }
    
  4. 定义View(Activity):

    class UserActivity : AppCompatActivity() {
        private lateinit var viewModel: UserViewModel
    
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_user)
    
    
            // 依赖注入(简化示例)
            val apiService = RetrofitClient.instance
            val repository = UserRepository(apiService)
            viewModel = ViewModelProvider(this, ViewModelFactory(repository)).get(UserViewModel::class.java)
    
    
            // 观察LiveData
            viewModel.userLiveData.observe(this) { user ->
                user?.let {
                    findViewById<TextView>(R.id.tvName).text = it.name
                    findViewById<TextView>(R.id.tvAge).text = it.age.toString()
                }
            }
    
    
            // 触发数据获取
            viewModel.fetchUser(1)
        }
    }
    

6.3 常见问题:ViewModel生命周期与Activity不同步

问题描述: 在Activity中创建ViewModel,但当Activity被销毁并重新创建时,ViewModel中的数据丢失。

原因分析: ViewModel的生命周期与Activity不同,它会在Activity配置更改(如屏幕旋转)时存活,但当Activity被完全销毁(如用户按返回键)时,ViewModel也会被销毁。

解决方案:

  1. 使用ViewModelProvider: 确保使用ViewModelProvider创建ViewModel,这样在配置更改时可以保留实例。

    
    val viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
    

  2. 使用SavedStateHandle: 如果需要在ViewModel中保存状态(如搜索查询),可以使用SavedStateHandle

    class UserViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
        private val _searchQuery = savedStateHandle.getLiveData<String>("searchQuery")
        val searchQuery: LiveData<String> = _searchQuery
    
    
        fun setSearchQuery(query: String) {
            savedStateHandle.set("searchQuery", query)
        }
    }
    
  3. 避免在ViewModel中持有View的引用: 这会导致内存泄漏。

第七部分:常见问题解决方案汇总

7.1 布局问题

问题: 布局在不同设备上显示不一致。 解决方案:

  • 使用ConstraintLayout和百分比布局。
  • 为不同屏幕尺寸创建不同的布局文件(如layout-sw600dp)。
  • 使用dimens.xml定义尺寸,避免硬编码。

7.2 权限问题

问题: 应用需要访问相机、位置等敏感权限,但请求失败。 解决方案:

  • AndroidManifest.xml中声明权限。
  • 运行时请求权限(针对Android 6.0+)。
  • 处理用户拒绝权限的情况。

示例:请求相机权限

class CameraActivity : AppCompatActivity() {
    private val CAMERA_PERMISSION = Manifest.permission.CAMERA
    private val CAMERA_REQUEST_CODE = 100

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera)

        if (ContextCompat.checkSelfPermission(this, CAMERA_PERMISSION) == PackageManager.PERMISSION_GRANTED) {
            // 权限已授予,执行相机操作
            openCamera()
        } else {
            // 请求权限
            ActivityCompat.requestPermissions(this, arrayOf(CAMERA_PERMISSION), CAMERA_REQUEST_CODE)
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == CAMERA_REQUEST_CODE) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                openCamera()
            } else {
                // 权限被拒绝,可以显示解释或引导用户到设置
                Toast.makeText(this, "相机权限被拒绝", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun openCamera() {
        // 打开相机的逻辑
    }
}

7.3 后台任务

问题: 应用在后台运行时,如何执行任务(如下载文件)? 解决方案:

  • WorkManager: 用于调度可延迟的、保证执行的任务(即使应用关闭或设备重启)。
  • Foreground Service: 用于需要用户可见的任务(如音乐播放)。
  • JobScheduler: 用于Android 5.0+,但WorkManager是更现代的替代方案。

示例:使用WorkManager下载文件

class DownloadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // 执行下载逻辑
        return Result.success()
    }
}

// 调度下载任务
val downloadWorkRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
    .setConstraints(Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build())
    .build()

WorkManager.getInstance(context).enqueue(downloadWorkRequest)

7.4 兼容性问题

问题: 应用在不同Android版本上行为不一致。 解决方案:

  • 使用AndroidX库,它们提供了向后兼容的API。
  • 检查API级别,使用Build.VERSION.SDK_INT进行条件判断。
  • 使用ContextCompatActivityCompat等兼容类。

示例:检查API级别

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    // Android 8.0及以上代码
    val notificationManager = getSystemService(NotificationManager::class.java)
    // ...
} else {
    // 旧版本代码
    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    // ...
}

第八部分:进阶主题

8.1 Jetpack Compose

Jetpack Compose是Google推出的现代UI工具包,使用Kotlin声明式UI,简化了UI开发。

示例:创建一个简单的Compose界面

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        Greeting("Android")
    }
}

8.2 Kotlin协程深入

协程是Kotlin的轻量级线程,用于简化异步编程。

示例:使用asyncawait并发执行任务

suspend fun fetchData(): String {
    val result1 = async { networkCall1() }
    val result2 = async { networkCall2() }
    return result1.await() + result2.await()
}

8.3 依赖注入(Dagger Hilt)

依赖注入(DI)可以管理类之间的依赖关系,提高代码的可测试性和可维护性。

示例:使用Hilt注入Repository

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    fun provideApiService(): ApiService {
        return RetrofitClient.instance
    }

    @Provides
    fun provideUserRepository(apiService: ApiService): UserRepository {
        return UserRepository(apiService)
    }
}

@HiltViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    // ...
}

结论

Android开发是一个不断发展的领域,从基础到进阶需要不断学习和实践。本文通过实例分析,涵盖了从环境搭建、UI设计、数据存储、网络通信到性能优化和现代架构的各个方面。常见问题解决方案部分旨在帮助开发者快速定位和解决实际开发中遇到的问题。

记住,最好的学习方式是动手实践。尝试构建自己的项目,遇到问题时查阅文档和社区资源。随着经验的积累,你将能够开发出高质量、高性能的Android应用。

推荐资源:

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