引言:为什么选择Android开发?
Android作为全球最大的移动操作系统,拥有超过30亿活跃设备,为开发者提供了广阔的市场空间。无论是个人开发者还是企业团队,掌握Android开发技能都能带来丰富的职业机会。本指南将从零基础开始,通过实际案例逐步深入,帮助你构建完整的Android开发知识体系。
第一部分:Android开发基础环境搭建
1.1 开发工具选择
Android Studio是Google官方推荐的集成开发环境(IDE),基于IntelliJ IDEA构建,提供了代码编辑、调试、性能分析等全方位工具。
安装步骤:
- 访问Android开发者官网下载最新版本
- 运行安装程序,选择标准安装模式
- 配置SDK路径和模拟器选项
- 首次启动时会下载必要的SDK组件
# 验证安装是否成功
adb version
# 应该显示类似:Android Debug Bridge version 1.0.41
1.2 项目结构解析
一个标准的Android项目包含以下关键目录:
MyApp/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/ # Java/Kotlin源代码
│ │ │ ├── res/ # 资源文件(布局、图片、字符串等)
│ │ │ └── AndroidManifest.xml # 应用配置文件
│ │ └── test/ # 单元测试
│ ├── build.gradle # 模块级构建配置
│ └── proguard-rules.pro # 代码混淆规则
├── gradle/
│ └── wrapper/ # Gradle包装器
├── build.gradle # 项目级构建配置
└── settings.gradle # 项目设置
1.3 第一个应用:Hello World
让我们创建一个简单的”Hello World”应用来熟悉基本流程:
步骤1:创建新项目
- 打开Android Studio → New Project
- 选择”Empty Activity”模板
- 配置项目名称、包名、语言(推荐Kotlin)和最低API级别
步骤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:运行应用
- 连接Android设备或启动模拟器
- 点击工具栏的”Run”按钮
- 应用将在设备上启动并显示欢迎信息
第二部分:核心组件与UI开发
2.1 Activity生命周期详解
Activity是Android应用的基本组件,管理用户界面和交互。理解其生命周期至关重要。
生命周期回调方法:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1. 创建时调用,适合初始化UI
setContentView(R.layout.activity_main)
}
override fun onStart() {
super.onStart()
// 2. 可见但不可交互
}
override fun onResume() {
super.onResume()
// 3. 可见且可交互,适合恢复数据
}
override fun onPause() {
super.onPause()
// 4. 失去焦点但可能部分可见,适合保存数据
}
override fun onStop() {
super.onStop()
// 5. 完全不可见
}
override fun onDestroy() {
super.onDestroy()
// 6. 销毁前调用,适合释放资源
}
}
生命周期状态图:
onCreate() → onStart() → onResume() → [运行中] → onPause() → onStop() → onDestroy()
2.2 布局系统实战
Android提供多种布局方式,最常用的是ConstraintLayout。
示例:用户登录界面
<!-- activity_login.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">
<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"
app:layout_constraintBottom_toTopOf="@+id/etPassword"
app:layout_constraintVertical_chainStyle="packed"
android:layout_marginTop="100dp"
android:layout_marginHorizontal="32dp" />
<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"
app:layout_constraintBottom_toTopOf="@+id/btnLogin"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="32dp" />
<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="32dp"
android:layout_marginHorizontal="32dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
对应的Activity代码:
class LoginActivity : AppCompatActivity() {
private lateinit var etUsername: EditText
private lateinit var etPassword: EditText
private lateinit var btnLogin: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
// 初始化视图
etUsername = findViewById(R.id.etUsername)
etPassword = findViewById(R.id.etPassword)
btnLogin = findViewById(R.id.btnLogin)
// 设置点击监听器
btnLogin.setOnClickListener {
val username = etUsername.text.toString()
val password = etPassword.text.toString()
if (username.isNotEmpty() && password.isNotEmpty()) {
// 模拟登录验证
if (username == "admin" && password == "123456") {
Toast.makeText(this, "登录成功!", Toast.LENGTH_SHORT).show()
// 跳转到主界面
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
} else {
Toast.makeText(this, "用户名或密码错误", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "请输入用户名和密码", Toast.LENGTH_SHORT).show()
}
}
}
}
2.3 RecyclerView列表展示
RecyclerView是展示大量数据的高效组件,支持滚动和复用。
示例:商品列表展示
// 数据模型
data class Product(
val id: Int,
val name: String,
val price: Double,
val imageUrl: String
)
// 适配器
class ProductAdapter(private val productList: List<Product>) :
RecyclerView.Adapter<ProductAdapter.ProductViewHolder>() {
class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvName: TextView = itemView.findViewById(R.id.tvProductName)
val tvPrice: TextView = itemView.findViewById(R.id.tvProductPrice)
val ivImage: ImageView = itemView.findViewById(R.id.ivProductImage)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_product, parent, false)
return ProductViewHolder(view)
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val product = productList[position]
holder.tvName.text = product.name
holder.tvPrice.text = "¥${product.price}"
// 使用Glide加载图片(需添加依赖)
Glide.with(holder.itemView.context)
.load(product.imageUrl)
.placeholder(R.drawable.placeholder)
.into(holder.ivImage)
// 点击事件
holder.itemView.setOnClickListener {
Toast.makeText(holder.itemView.context,
"点击了:${product.name}", Toast.LENGTH_SHORT).show()
}
}
override fun getItemCount(): Int = productList.size
}
// 布局文件 item_product.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:id="@+id/ivProductImage"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_launcher_background"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp">
<TextView
android:id="@+id/tvProductName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvProductPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#FF5722"
android:layout_marginTop="4dp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
使用适配器:
class ProductListActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_product_list)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
// 模拟数据
val products = listOf(
Product(1, "iPhone 15", 5999.0, "https://example.com/iphone15.jpg"),
Product(2, "MacBook Pro", 12999.0, "https://example.com/macbook.jpg"),
Product(3, "AirPods Pro", 1999.0, "https://example.com/airpods.jpg")
)
val adapter = ProductAdapter(products)
recyclerView.adapter = adapter
}
}
第三部分:数据存储与网络通信
3.1 SharedPreferences轻量级存储
SharedPreferences适合存储简单的键值对数据,如用户设置、登录状态等。
示例:记住登录状态
class SharedPreferencesHelper(context: Context) {
private val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
companion object {
private const val KEY_IS_LOGGED_IN = "is_logged_in"
private const val KEY_USERNAME = "username"
private const val KEY_TOKEN = "auth_token"
}
// 保存登录状态
fun saveLoginState(isLoggedIn: Boolean, username: String, token: String) {
prefs.edit().apply {
putBoolean(KEY_IS_LOGGED_IN, isLoggedIn)
putString(KEY_USERNAME, username)
putString(KEY_TOKEN, token)
apply() // 异步提交
}
}
// 获取登录状态
fun getLoginState(): LoginState {
val isLoggedIn = prefs.getBoolean(KEY_IS_LOGGED_IN, false)
val username = prefs.getString(KEY_USERNAME, "") ?: ""
val token = prefs.getString(KEY_TOKEN, "") ?: ""
return LoginState(isLoggedIn, username, token)
}
// 清除登录信息
fun clearLoginInfo() {
prefs.edit().apply {
remove(KEY_IS_LOGGED_IN)
remove(KEY_USERNAME)
remove(KEY_TOKEN)
apply()
}
}
}
data class LoginState(
val isLoggedIn: Boolean,
val username: String,
val token: String
)
// 在Activity中使用
class MainActivity : AppCompatActivity() {
private lateinit var prefsHelper: SharedPreferencesHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
prefsHelper = SharedPreferencesHelper(this)
// 检查登录状态
val loginState = prefsHelper.getLoginState()
if (!loginState.isLoggedIn) {
// 跳转到登录页面
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
}
}
3.2 Room数据库操作
Room是Google推荐的SQLite封装库,提供编译时验证和简化API。
步骤1:添加依赖
// app/build.gradle
dependencies {
implementation "androidx.room:room-runtime:2.6.1"
implementation "androidx.room:room-ktx:2.6.1"
kapt "androidx.room:room-compiler:2.6.1"
}
步骤2:定义实体和DAO
// User.kt - 实体类
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val name: String,
val email: String,
@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis()
)
// UserDao.kt - 数据访问对象
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User)
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
}
步骤3:创建数据库
// AppDatabase.kt
@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
}
}
}
}
步骤4:在ViewModel中使用
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val userDao = AppDatabase.getDatabase(application).userDao()
// 使用协程处理异步操作
fun insertUser(name: String, email: String) = viewModelScope.launch {
val user = User(name = name, email = email)
userDao.insert(user)
}
fun getAllUsers() = liveData {
emit(userDao.getAllUsers())
}
}
3.3 Retrofit网络请求
Retrofit是处理HTTP请求的流行库,结合OkHttp使用。
步骤1:添加依赖
// app/build.gradle
dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
}
步骤2:定义API接口
// ApiService.kt
interface ApiService {
@GET("users/{id}")
suspend fun getUserById(@Path("id") userId: Int): Response<User>
@POST("users")
suspend fun createUser(@Body user: User): Response<User>
@GET("posts")
suspend fun getPosts(): List<Post>
@Multipart
@POST("upload")
suspend fun uploadImage(@Part image: MultipartBody.Part): Response<UploadResponse>
}
// 数据模型
data class User(
val id: Int,
val name: String,
val email: String
)
data class Post(
val userId: Int,
val id: Int,
val title: String,
val body: String
)
data class UploadResponse(
val success: Boolean,
val url: String
)
步骤3:创建Retrofit实例
// RetrofitClient.kt
object RetrofitClient {
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService: ApiService by lazy {
retrofit.create(ApiService::class.java)
}
}
步骤4:在ViewModel中使用
class UserViewModel : ViewModel() {
private val apiService = RetrofitClient.apiService
fun fetchUser(userId: Int) = liveData {
try {
val response = apiService.getUserById(userId)
if (response.isSuccessful) {
response.body()?.let { user ->
emit(Resource.success(user))
}
} else {
emit(Resource.error("请求失败: ${response.code()}", null))
}
} catch (e: Exception) {
emit(Resource.error("网络错误: ${e.message}", null))
}
}
fun fetchPosts() = liveData {
try {
val posts = apiService.getPosts()
emit(Resource.success(posts))
} catch (e: Exception) {
emit(Resource.error("网络错误: ${e.message}", null))
}
}
}
// 资源包装类
sealed class Resource<T>(val data: T? = null, val message: String? = null) {
class Success<T>(data: T) : Resource<T>(data)
class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
class Loading<T> : Resource<T>()
companion object {
fun <T> success(data: T) = Success(data)
fun <T> error(message: String, data: T? = null) = Error(message, data)
fun <T> loading() = Loading<T>()
}
}
第四部分:高级主题与架构模式
4.1 MVVM架构模式
MVVM(Model-View-ViewModel)是现代Android开发推荐的架构模式。
架构组件:
- Model: 数据层,负责数据获取和存储
- View: UI层,负责显示数据和用户交互
- ViewModel: 业务逻辑层,连接View和Model
示例:用户列表MVVM实现
// Model层 - UserRepository
class UserRepository(private val apiService: ApiService) {
suspend fun getUsers(): List<User> {
return apiService.getUsers()
}
suspend fun getUserById(id: Int): User {
return apiService.getUserById(id)
}
}
// ViewModel层 - UserViewModel
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
private val _users = MutableLiveData<Resource<List<User>>>()
val users: LiveData<Resource<List<User>>> = _users
fun loadUsers() {
viewModelScope.launch {
_users.value = Resource.loading()
try {
val userList = userRepository.getUsers()
_users.value = Resource.success(userList)
} catch (e: Exception) {
_users.value = Resource.error("加载失败: ${e.message}")
}
}
}
}
// View层 - UserListActivity
class UserListActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
private lateinit var adapter: UserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_list)
// 初始化ViewModel
val repository = UserRepository(RetrofitClient.apiService)
viewModel = ViewModelProvider(this, UserViewModelFactory(repository))
.get(UserViewModel::class.java)
// 设置RecyclerView
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
adapter = UserAdapter()
recyclerView.adapter = adapter
// 观察数据变化
viewModel.users.observe(this) { resource ->
when (resource) {
is Resource.Loading -> showLoading()
is Resource.Success -> {
hideLoading()
resource.data?.let { users ->
adapter.submitList(users)
}
}
is Resource.Error -> {
hideLoading()
showError(resource.message)
}
}
}
// 加载数据
viewModel.loadUsers()
}
private fun showLoading() {
findViewById<ProgressBar>(R.id.progressBar).visibility = View.VISIBLE
}
private fun hideLoading() {
findViewById<ProgressBar>(R.id.progressBar).visibility = View.GONE
}
private fun showError(message: String?) {
Toast.makeText(this, message ?: "未知错误", Toast.LENGTH_SHORT).show()
}
}
4.2 Jetpack Compose现代化UI
Jetpack Compose是Google推出的声明式UI框架,简化了UI开发。
步骤1:添加依赖
// app/build.gradle
dependencies {
implementation "androidx.compose.ui:ui:1.6.7"
implementation "androidx.compose.material:material:1.6.7"
implementation "androidx.compose.ui:ui-tooling-preview:1.6.7"
implementation "androidx.activity:activity-compose:1.8.2"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0"
kapt "androidx.compose.compiler:compiler:1.5.10"
}
步骤2:创建Compose界面
// MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
UserListScreen()
}
}
}
}
}
// 主题定义
@Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
MaterialTheme(
colors = if (darkTheme) DarkColors else LightColors,
typography = Typography,
shapes = Shapes,
content = content
)
}
// 用户列表屏幕
@Composable
fun UserListScreen(viewModel: UserViewModel = viewModel()) {
val users by viewModel.users.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = { Text("用户列表") },
actions = {
IconButton(onClick = { /* 刷新 */ }) {
Icon(Icons.Filled.Refresh, "刷新")
}
}
)
}
) { padding ->
when (val state = users) {
is Resource.Loading -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
is Resource.Success -> {
LazyColumn(
modifier = Modifier.padding(padding),
contentPadding = PaddingValues(16.dp)
) {
items(state.data ?: emptyList()) { user ->
UserItem(user)
}
}
}
is Resource.Error -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = state.message ?: "未知错误",
color = MaterialTheme.colors.error
)
}
}
}
}
}
// 用户项组件
@Composable
fun UserItem(user: User) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
elevation = 4.dp
) {
Row(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.Person,
contentDescription = null,
modifier = Modifier.size(48.dp),
tint = MaterialTheme.colors.primary
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(
text = user.name,
style = MaterialTheme.typography.h6
)
Text(
text = user.email,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
)
}
}
}
}
4.3 依赖注入(Dagger Hilt)
Dagger Hilt简化了依赖注入的实现。
步骤1:添加依赖
// app/build.gradle
dependencies {
implementation "com.google.dagger:hilt-android:2.51"
kapt "com.google.dagger:hilt-compiler:2.51"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
kapt "androidx.hilt:hilt-compiler:1.0.0"
}
步骤2:配置Hilt
// MyApplication.kt
@HiltAndroidApp
class MyApplication : Application()
// AndroidManifest.xml
<application
android:name=".MyApplication"
... >
</application>
步骤3:创建模块
// NetworkModule.kt
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
// RepositoryModule.kt
@Module
@InstallIn(ViewModelComponent::class)
object RepositoryModule {
@Provides
fun provideUserRepository(apiService: ApiService): UserRepository {
return UserRepository(apiService)
}
}
步骤4:在ViewModel中使用
@HiltViewModel
class UserViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
private val _users = MutableLiveData<Resource<List<User>>>()
val users: LiveData<Resource<List<User>>> = _users
fun loadUsers() {
viewModelScope.launch {
_users.value = Resource.loading()
try {
val userList = userRepository.getUsers()
_users.value = Resource.success(userList)
} catch (e: Exception) {
_users.value = Resource.error("加载失败: ${e.message}")
}
}
}
}
第五部分:常见问题解析与解决方案
5.1 内存泄漏问题
问题描述: Activity被意外持有导致无法回收,引发内存泄漏。
常见场景:
- 静态变量持有Activity引用
- 未取消的异步任务
- 监听器未移除
解决方案:
// 错误示例:静态变量持有Activity
class MyActivity : AppCompatActivity() {
companion object {
private var activity: MyActivity? = null // 危险!
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity = this // 导致内存泄漏
}
}
// 正确做法:使用弱引用
class MyActivity : AppCompatActivity() {
companion object {
private var weakActivity: WeakReference<MyActivity>? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
weakActivity = WeakReference(this)
}
}
// 更好的做法:避免静态引用Activity
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用ViewModel处理数据,避免持有Activity引用
}
}
检测工具:
- Android Studio Profiler
- LeakCanary库(自动检测内存泄漏)
// 添加LeakCanary依赖
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
5.2 网络请求异常处理
问题描述: 网络请求失败或超时,应用崩溃。
解决方案:
// 使用协程的异常处理
class NetworkManager {
suspend fun fetchData(): Result<Data> {
return try {
val response = apiService.getData()
if (response.isSuccessful) {
Result.success(response.body()!!)
} else {
Result.failure(NetworkException("HTTP ${response.code()}: ${response.message()}"))
}
} catch (e: SocketTimeoutException) {
Result.failure(NetworkException("请求超时,请检查网络连接"))
} catch (e: ConnectException) {
Result.failure(NetworkException("无法连接到服务器"))
} catch (e: IOException) {
Result.failure(NetworkException("网络错误: ${e.message}"))
} catch (e: Exception) {
Result.failure(NetworkException("未知错误: ${e.message}"))
}
}
}
// 自定义异常类
class NetworkException(message: String) : Exception(message)
// 在ViewModel中使用
class DataViewModel : ViewModel() {
private val networkManager = NetworkManager()
fun loadData() = liveData {
emit(Resource.loading())
val result = networkManager.fetchData()
result.onSuccess { data ->
emit(Resource.success(data))
}.onFailure { exception ->
emit(Resource.error(exception.message ?: "网络错误"))
}
}
}
5.3 UI线程阻塞
问题描述: 在主线程执行耗时操作导致ANR(Application Not Responding)。
解决方案:
// 错误示例:在主线程执行数据库操作
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 危险!在主线程执行数据库操作
val users = database.userDao().getAllUsers() // 可能阻塞主线程
}
}
// 正确做法:使用协程
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在协程中执行耗时操作
lifecycleScope.launch {
viewModel.loadUsers()
}
}
}
// ViewModel中的协程使用
class UserViewModel : ViewModel() {
fun loadUsers() = liveData {
emit(Resource.loading())
// 在IO线程执行数据库操作
val users = withContext(Dispatchers.IO) {
database.userDao().getAllUsers()
}
emit(Resource.success(users))
}
}
5.4 权限管理问题
问题描述: Android 6.0+需要动态请求权限,处理不当会导致应用崩溃。
解决方案:
class PermissionManager(private val activity: AppCompatActivity) {
private val permissionCallback = mutableMapOf<String, (Boolean) -> Unit>()
// 请求单个权限
fun requestPermission(permission: String, callback: (Boolean) -> Unit) {
permissionCallback[permission] = callback
if (ContextCompat.checkSelfPermission(
activity,
permission
) == PackageManager.PERMISSION_GRANTED
) {
callback(true)
} else {
ActivityCompat.requestPermissions(
activity,
arrayOf(permission),
REQUEST_CODE_PERMISSION
)
}
}
// 请求多个权限
fun requestPermissions(permissions: Array<String>, callback: (Map<String, Boolean>) -> Unit) {
val grantedPermissions = mutableMapOf<String, Boolean>()
permissions.forEach { permission ->
if (ContextCompat.checkSelfPermission(
activity,
permission
) == PackageManager.PERMISSION_GRANTED
) {
grantedPermissions[permission] = true
}
}
if (grantedPermissions.size == permissions.size) {
callback(grantedPermissions)
} else {
ActivityCompat.requestPermissions(
activity,
permissions,
REQUEST_CODE_PERMISSION
)
}
}
// 处理权限请求结果
fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_PERMISSION) {
val results = mutableMapOf<String, Boolean>()
for (i in permissions.indices) {
results[permissions[i]] = grantResults[i] == PackageManager.PERMISSION_GRANTED
}
// 调用回调
permissions.forEach { permission ->
permissionCallback[permission]?.invoke(results[permission] ?: false)
}
permissionCallback.clear()
}
}
companion object {
const val REQUEST_CODE_PERMISSION = 1001
}
}
// 在Activity中使用
class CameraActivity : AppCompatActivity() {
private lateinit var permissionManager: PermissionManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
permissionManager = PermissionManager(this)
findViewById<Button>(R.id.btnTakePhoto).setOnClickListener {
permissionManager.requestPermission(
Manifest.permission.CAMERA
) { granted ->
if (granted) {
openCamera()
} else {
showPermissionDeniedDialog()
}
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissionManager.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
5.5 性能优化技巧
1. 图片加载优化:
// 使用Glide优化图片加载
Glide.with(context)
.load(imageUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存原始和转换后的图片
.placeholder(R.drawable.placeholder) // 占位符
.error(R.drawable.error) // 错误占位符
.override(200, 200) // 限制尺寸
.centerCrop() // 裁剪填充
.into(imageView)
2. 列表滚动优化:
// RecyclerView优化
class OptimizedAdapter : RecyclerView.Adapter<ViewHolder>() {
// 使用DiffUtil计算差异,避免全量刷新
fun updateList(newList: List<Item>) {
val diffCallback = object : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldPos: Int, newPos: Int) =
oldList[oldPos].id == newList[newPos].id
override fun areContentsTheSame(oldPos: Int, newPos: Int) =
oldList[oldPos] == newList[newPos]
}
val diffResult = DiffUtil.calculateDiff(diffCallback)
diffResult.dispatchUpdatesTo(this)
}
}
3. 布局优化:
<!-- 避免过度嵌套 -->
<!-- 错误示例:嵌套过多 -->
<LinearLayout>
<LinearLayout>
<LinearLayout>
<TextView />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- 正确示例:使用ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout>
<TextView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
第六部分:实战项目:天气预报应用
6.1 项目需求分析
功能需求:
- 显示当前城市天气
- 搜索城市天气
- 未来5天天气预报
- 保存最近搜索的城市
- 离线缓存
技术栈:
- MVVM架构
- Retrofit + Coroutines
- Room数据库
- Jetpack Compose(可选)
- Hilt依赖注入
6.2 项目结构
WeatherApp/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/example/weatherapp/
│ │ │ │ ├── data/
│ │ │ │ │ ├── model/ # 数据模型
│ │ │ │ │ ├── repository/ # 数据仓库
│ │ │ │ │ ├── api/ # 网络API
│ │ │ │ │ └── db/ # 数据库
│ │ │ │ ├── ui/
│ │ │ │ │ ├── view/ # UI组件
│ │ │ │ │ ├── viewmodel/ # ViewModel
│ │ │ │ │ └── theme/ # 主题
│ │ │ │ ├── di/ # 依赖注入
│ │ │ │ └── util/ # 工具类
│ │ │ └── res/
│ │ │ ├── layout/ # XML布局(如果使用传统UI)
│ │ │ ├── drawable/ # 图片资源
│ │ │ ├── values/ # 字符串、颜色等
│ │ │ └── navigation/ # 导航图
│ │ └── test/
│ └── build.gradle
6.3 核心代码实现
1. 数据模型:
// Weather.kt
@Entity(tableName = "weather")
data class Weather(
@PrimaryKey
val city: String,
val temperature: Double,
val condition: String,
val humidity: Int,
val windSpeed: Double,
val forecast: List<Forecast>,
val timestamp: Long = System.currentTimeMillis()
)
data class Forecast(
val date: String,
val highTemp: Double,
val lowTemp: Double,
val condition: String
)
2. 网络API:
// WeatherApi.kt
interface WeatherApi {
@GET("weather")
suspend fun getCurrentWeather(
@Query("q") city: String,
@Query("units") units: String = "metric"
): Response<WeatherResponse>
@GET("forecast")
suspend fun getForecast(
@Query("q") city: String,
@Query("units") units: String = "metric"
): Response<ForecastResponse>
}
// 响应模型
data class WeatherResponse(
val main: Main,
val weather: List<WeatherItem>,
val wind: Wind,
val name: String
)
data class Main(
val temp: Double,
val humidity: Int
)
data class WeatherItem(
val description: String
)
data class Wind(
val speed: Double
)
data class ForecastResponse(
val list: List<ForecastItem>
)
data class ForecastItem(
val dt_txt: String,
val main: Main,
val weather: List<WeatherItem>
)
3. Repository:
// WeatherRepository.kt
class WeatherRepository(
private val api: WeatherApi,
private val db: WeatherDatabase
) {
suspend fun getWeather(city: String): Resource<Weather> {
return try {
// 先尝试从数据库获取
val cachedWeather = db.weatherDao().getWeather(city)
if (cachedWeather != null &&
System.currentTimeMillis() - cachedWeather.timestamp < 3600000) {
return Resource.success(cachedWeather)
}
// 从网络获取
val response = api.getCurrentWeather(city)
if (response.isSuccessful) {
val weatherResponse = response.body()!!
val weather = Weather(
city = weatherResponse.name,
temperature = weatherResponse.main.temp,
condition = weatherResponse.weather.firstOrNull()?.description ?: "",
humidity = weatherResponse.main.humidity,
windSpeed = weatherResponse.wind.speed,
forecast = emptyList()
)
// 保存到数据库
db.weatherDao().insert(weather)
Resource.success(weather)
} else {
Resource.error("获取天气失败: ${response.code()}")
}
} catch (e: Exception) {
Resource.error("网络错误: ${e.message}")
}
}
}
4. ViewModel:
// WeatherViewModel.kt
@HiltViewModel
class WeatherViewModel @Inject constructor(
private val repository: WeatherRepository
) : ViewModel() {
private val _weatherState = MutableLiveData<Resource<Weather>>()
val weatherState: LiveData<Resource<Weather>> = _weatherState
private val _searchHistory = MutableLiveData<List<String>>()
val searchHistory: LiveData<List<String>> = _searchHistory
fun searchWeather(city: String) {
viewModelScope.launch {
_weatherState.value = Resource.loading()
val result = repository.getWeather(city)
_weatherState.value = result
// 更新搜索历史
if (result is Resource.Success) {
updateSearchHistory(city)
}
}
}
private fun updateSearchHistory(city: String) {
val currentHistory = _searchHistory.value ?: emptyList()
val newHistory = (listOf(city) + currentHistory).distinct().take(5)
_searchHistory.value = newHistory
}
}
5. UI(Jetpack Compose):
// WeatherScreen.kt
@Composable
fun WeatherScreen(viewModel: WeatherViewModel = hiltViewModel()) {
val weatherState by viewModel.weatherState.collectAsState()
val searchHistory by viewModel.searchHistory.collectAsState()
var searchText by remember { mutableStateOf("") }
Scaffold(
topBar = {
TopAppBar(
title = { Text("天气预报") },
actions = {
IconButton(onClick = { /* 刷新 */ }) {
Icon(Icons.Filled.Refresh, "刷新")
}
}
)
}
) { padding ->
Column(
modifier = Modifier
.padding(padding)
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
// 搜索框
OutlinedTextField(
value = searchText,
onValueChange = { searchText = it },
label = { Text("搜索城市") },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(
onSearch = {
if (searchText.isNotEmpty()) {
viewModel.searchWeather(searchText)
}
}
),
trailingIcon = {
IconButton(
onClick = {
if (searchText.isNotEmpty()) {
viewModel.searchWeather(searchText)
}
}
) {
Icon(Icons.Filled.Search, "搜索")
}
}
)
Spacer(modifier = Modifier.height(16.dp))
// 搜索历史
if (searchHistory.isNotEmpty()) {
Text("最近搜索", style = MaterialTheme.typography.h6)
Spacer(modifier = Modifier.height(8.dp))
LazyRow {
items(searchHistory) { city ->
Chip(
onClick = { viewModel.searchWeather(city) },
modifier = Modifier.padding(end = 8.dp)
) {
Text(city)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
}
// 天气内容
when (val state = weatherState) {
is Resource.Loading -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
is Resource.Success -> {
state.data?.let { weather ->
WeatherContent(weather)
}
}
is Resource.Error -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = state.message ?: "未知错误",
color = MaterialTheme.colors.error
)
}
}
}
}
}
}
@Composable
fun WeatherContent(weather: Weather) {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = 8.dp
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = weather.city,
style = MaterialTheme.typography.h4,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "${weather.temperature}°C",
style = MaterialTheme.typography.h2,
color = MaterialTheme.colors.primary
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = weather.condition,
style = MaterialTheme.typography.h6
)
}
Spacer(modifier = Modifier.height(16.dp))
// 详细信息
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
InfoCard(
icon = Icons.Filled.WaterDrop,
label = "湿度",
value = "${weather.humidity}%"
)
InfoCard(
icon = Icons.Filled.Air,
label = "风速",
value = "${weather.windSpeed} m/s"
)
}
}
}
}
@Composable
fun InfoCard(icon: ImageVector, label: String, value: String) {
Card(
modifier = Modifier.width(120.dp),
elevation = 4.dp
) {
Column(
modifier = Modifier.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(icon, contentDescription = null, tint = MaterialTheme.colors.primary)
Text(text = label, style = MaterialTheme.typography.caption)
Text(
text = value,
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold
)
}
}
}
第七部分:发布与优化
7.1 应用签名与发布
生成签名密钥:
# 使用keytool生成密钥库
keytool -genkey -v -keystore my-release-key.keystore \
-alias my-key-alias -keyalg RSA -keysize 2048 \
-validity 10000
配置签名:
// app/build.gradle
android {
signingConfigs {
release {
storeFile file("my-release-key.keystore")
storePassword "your_password"
keyAlias "my-key-alias"
keyPassword "your_password"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
7.2 性能优化策略
1. 减少APK大小:
// 启用资源压缩
android {
buildTypes {
release {
shrinkResources true
minifyEnabled true
}
}
}
// 使用WebP格式图片
android {
aaptOptions {
cruncherEnabled = true
cruncherProcesses = 4
}
}
2. 优化启动时间:
// 延迟初始化非必要组件
class MainActivity : AppCompatActivity() {
private lateinit var heavyComponent: HeavyComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 延迟初始化
lifecycleScope.launch {
delay(1000) // 等待UI渲染完成
heavyComponent = HeavyComponent()
}
}
}
3. 电池优化:
// 使用WorkManager处理后台任务
class SyncWorker(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
return try {
// 执行同步操作
syncData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
// 安排工作
val workRequest = PeriodicWorkRequestBuilder<SyncWorker>(
1, TimeUnit.HOURS
).setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
).build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"data_sync",
ExistingPeriodicWorkPolicy.KEEP,
workRequest
)
7.3 测试策略
1. 单元测试:
// UserRepositoryTest.kt
class UserRepositoryTest {
private lateinit var repository: UserRepository
private lateinit var mockApi: ApiService
@Before
fun setup() {
mockApi = mock()
repository = UserRepository(mockApi)
}
@Test
fun `getUsers should return success when api call succeeds`() = runTest {
// Given
val expectedUsers = listOf(User(1, "John", "john@example.com"))
whenever(mockApi.getUsers()).thenReturn(expectedUsers)
// When
val result = repository.getUsers()
// Then
assertEquals(expectedUsers, result)
}
@Test
fun `getUsers should throw exception when api call fails`() = runTest {
// Given
whenever(mockApi.getUsers()).thenThrow(NetworkException("Network error"))
// When & Then
assertThrows<NetworkException> {
repository.getUsers()
}
}
}
2. UI测试:
// UserListScreenTest.kt
@RunWith(AndroidJUnit4::class)
class UserListScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testUserListScreen() {
// Given
val users = listOf(
User(1, "Alice", "alice@example.com"),
User(2, "Bob", "bob@example.com")
)
// When
composeTestRule.setContent {
MyAppTheme {
UserListScreen()
}
}
// Then
composeTestRule.onNodeWithText("Alice").assertIsDisplayed()
composeTestRule.onNodeWithText("Bob").assertIsDisplayed()
}
}
第八部分:学习资源与进阶路径
8.1 官方资源
8.2 推荐书籍
- 《Android编程权威指南》(第4版)
- 《Kotlin实战》
- 《Jetpack Compose实战》
8.3 在线课程
- Google官方Android开发课程
- Udacity Android开发纳米学位
- Coursera Android开发专项课程
8.4 社区与论坛
- Stack Overflow(Android标签)
- Reddit r/androiddev
- GitHub Android开源项目
- Android开发者中文社区
结语
Android开发是一个持续学习的过程。从基础组件到高级架构,从传统UI到Jetpack Compose,每一步都需要扎实的实践。建议初学者按照以下路径学习:
- 基础阶段:掌握Activity、Fragment、布局系统、数据存储
- 进阶阶段:学习MVVM、网络请求、数据库操作
- 高级阶段:掌握Jetpack组件、依赖注入、性能优化
- 实战阶段:参与开源项目或开发个人应用
记住,最好的学习方式是动手实践。遇到问题时,善用官方文档、Stack Overflow和开发者社区。保持好奇心,持续学习新技术,你一定能成为一名优秀的Android开发者!
最后提醒:Android生态系统更新迅速,建议定期关注Google I/O大会和Android开发者博客,及时了解最新技术和最佳实践。
