引言

Android开发作为移动应用开发的主流技术之一,涵盖了从基础UI构建到复杂架构设计的广泛领域。本文将通过一系列实例分析,从基础到进阶,详细讲解Android开发的核心技术,并结合实战案例和常见问题解决方案,帮助开发者系统性地掌握Android编程。

一、Android开发基础

1.1 Android开发环境搭建

Android开发主要依赖Android Studio,这是Google官方推荐的集成开发环境(IDE)。以下是搭建步骤:

  1. 下载并安装Android Studio:访问Android开发者官网下载最新版本。
  2. 配置SDK:安装完成后,打开Android Studio,通过SDK Manager安装所需的Android SDK版本和工具。
  3. 创建第一个项目:选择”Start a new Android Studio project”,选择”Empty Activity”模板,填写项目名称、包名和保存路径。
// 示例:MainActivity.kt
package com.example.myfirstapp

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

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

1.2 Android应用基本结构

Android应用由以下核心组件构成:

  • Activity:用户交互界面
  • Service:后台服务
  • Broadcast Receiver:广播接收器
  • Content Provider:数据共享
<!-- 示例:AndroidManifest.xml -->
<?xml version="1.1" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myfirstapp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyFirstApp">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

二、基础UI开发实例

2.1 布局系统详解

Android提供多种布局方式,包括LinearLayout、RelativeLayout、ConstraintLayout等。

示例:使用ConstraintLayout创建复杂布局

<!-- activity_main.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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/textView" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.2 常用UI组件使用

示例:实现一个登录界面

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    private lateinit var etUsername: EditText
    private lateinit var etPassword: EditText
    private lateinit var btnLogin: Button
    private lateinit var tvResult: TextView

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

        // 初始化视图
        etUsername = findViewById(R.id.et_username)
        etPassword = findViewById(R.id.et_password)
        btnLogin = findViewById(R.id.btn_login)
        tvResult = findViewById(R.id.tv_result)

        // 设置点击事件
        btnLogin.setOnClickListener {
            val username = etUsername.text.toString()
            val password = etPassword.text.toString()

            if (username.isNotEmpty() && password.isNotEmpty()) {
                // 模拟登录验证
                if (username == "admin" && password == "123456") {
                    tvResult.text = "登录成功!"
                    tvResult.setTextColor(Color.GREEN)
                } else {
                    tvResult.text = "用户名或密码错误!"
                    tvResult.setTextColor(Color.RED)
                }
            } else {
                tvResult.text = "请输入用户名和密码!"
                tvResult.setTextColor(Color.RED)
            }
        }
    }
}
<!-- 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:padding="16dp">

    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="用户名"
        android:inputType="text" />

    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="密码"
        android:inputType="textPassword" />

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="登录"
        android:layout_marginTop="16dp" />

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:gravity="center"
        android:textSize="16sp" />

</LinearLayout>

三、数据存储与管理

3.1 SharedPreferences存储

SharedPreferences适用于存储少量简单的数据,如用户设置。

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

// SharedPreferencesHelper.kt
class SharedPreferencesHelper(context: Context) {
    private val prefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)

    companion object {
        private const val KEY_USERNAME = "username"
        private const val KEY_THEME = "theme"
    }

    // 保存用户名
    fun saveUsername(username: String) {
        prefs.edit().putString(KEY_USERNAME, username).apply()
    }

    // 读取用户名
    fun getUsername(): String {
        return prefs.getString(KEY_USERNAME, "") ?: ""
    }

    // 保存主题设置
    fun saveTheme(theme: String) {
        prefs.edit().putString(KEY_THEME, theme).apply()
    }

    // 读取主题设置
    fun getTheme(): String {
        return prefs.getString(KEY_THEME, "light") ?: "light"
    }
}

// 在Activity中使用
class SettingsActivity : AppCompatActivity() {
    private lateinit var helper: SharedPreferencesHelper

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

        helper = SharedPreferencesHelper(this)

        // 保存设置
        helper.saveUsername("JohnDoe")
        helper.saveTheme("dark")

        // 读取设置
        val username = helper.getUsername()
        val theme = helper.getTheme()

        // 显示设置
        val tvSettings = findViewById<TextView>(R.id.tv_settings)
        tvSettings.text = "用户名: $username\n主题: $theme"
    }
}

3.2 SQLite数据库操作

对于结构化数据,SQLite是Android内置的轻量级数据库。

示例:创建和操作SQLite数据库

// DatabaseHelper.kt
class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {

    companion object {
        private const val DATABASE_NAME = "user_database.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"
    }

    override fun onCreate(db: SQLiteDatabase) {
        val createTableQuery = """
            CREATE TABLE $TABLE_USERS (
                $COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT,
                $COLUMN_NAME TEXT NOT NULL,
                $COLUMN_EMAIL TEXT NOT NULL
            )
        """.trimIndent()
        db.execSQL(createTableQuery)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        db.execSQL("DROP TABLE IF EXISTS $TABLE_USERS")
        onCreate(db)
    }

    // 添加用户
    fun addUser(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,
            null
        )

        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))
            }
        }
        cursor.close()
        return users
    }

    // 删除用户
    fun deleteUser(id: Long): Int {
        val db = writableDatabase
        return db.delete(TABLE_USERS, "$COLUMN_ID = ?", arrayOf(id.toString()))
    }
}

// 数据模型类
data class User(val id: Long, val name: String, val email: String)

// 在Activity中使用
class DatabaseActivity : AppCompatActivity() {
    private lateinit var dbHelper: DatabaseHelper

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

        dbHelper = DatabaseHelper(this)

        // 添加用户
        dbHelper.addUser("Alice", "alice@example.com")
        dbHelper.addUser("Bob", "bob@example.com")

        // 显示所有用户
        val users = dbHelper.getAllUsers()
        val tvUsers = findViewById<TextView>(R.id.tv_users)
        tvUsers.text = users.joinToString("\n") { "${it.id}: ${it.name} - ${it.email}" }

        // 删除用户
        val btnDelete = findViewById<Button>(R.id.btn_delete)
        btnDelete.setOnClickListener {
            if (users.isNotEmpty()) {
                val deleted = dbHelper.deleteUser(users[0].id)
                if (deleted > 0) {
                    // 刷新显示
                    val newUsers = dbHelper.getAllUsers()
                    tvUsers.text = newUsers.joinToString("\n") { "${it.id}: ${it.name} - ${it.email}" }
                }
            }
        }
    }
}

四、网络通信与数据处理

4.1 Retrofit + OkHttp实现网络请求

Retrofit是目前最流行的Android网络请求库,基于OkHttp。

示例:使用Retrofit获取GitHub用户信息

  1. 添加依赖(build.gradle):
dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:okhttp:4.9.3'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
}
  1. 创建数据模型
// GitHubUser.kt
data class GitHubUser(
    val login: String,
    val id: Long,
    val avatar_url: String,
    val name: String?,
    val bio: String?,
    val public_repos: Int,
    val followers: Int,
    val following: Int
)
  1. 创建API接口
// GitHubApi.kt
interface GitHubApi {
    @GET("users/{username}")
    suspend fun getUser(@Path("username") username: String): Response<GitHubUser>

    @GET("users/{username}/repos")
    suspend fun getUserRepos(@Path("username") username: String): List<GitHubRepo>
}

// GitHubRepo.kt
data class GitHubRepo(
    val id: Long,
    val name: String,
    val full_name: String,
    val description: String?,
    val stargazers_count: Int,
    val forks_count: Int
)
  1. 创建Retrofit实例
// RetrofitClient.kt
object RetrofitClient {
    private const val BASE_URL = "https://api.github.com/"

    private val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        })
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build()

    val instance: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}
  1. 在Activity中使用
// GitHubActivity.kt
class GitHubActivity : AppCompatActivity() {
    private lateinit var api: GitHubApi
    private lateinit var tvResult: TextView
    private lateinit var progressBar: ProgressBar

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

        api = RetrofitClient.instance.create(GitHubApi::class.java)
        tvResult = findViewById(R.id.tv_result)
        progressBar = findViewById(R.id.progressBar)

        val btnFetch = findViewById<Button>(R.id.btn_fetch)
        btnFetch.setOnClickListener {
            fetchGitHubUser("octocat")
        }
    }

    private fun fetchGitHubUser(username: String) {
        progressBar.visibility = View.VISIBLE
        tvResult.text = ""

        lifecycleScope.launch {
            try {
                val response = api.getUser(username)
                if (response.isSuccessful) {
                    val user = response.body()
                    user?.let {
                        val userInfo = """
                            用户名: ${it.login}
                            姓名: ${it.name ?: "未知"}
                            简介: ${it.bio ?: "无"}
                            粉丝数: ${it.followers}
                            关注数: ${it.following}
                            仓库数: ${it.public_repos}
                        """.trimIndent()
                        tvResult.text = userInfo
                    }
                } else {
                    tvResult.text = "请求失败: ${response.code()}"
                }
            } catch (e: Exception) {
                tvResult.text = "网络错误: ${e.message}"
            } finally {
                progressBar.visibility = View.GONE
            }
        }
    }
}

4.2 JSON数据解析

示例:使用Gson解析JSON数据

// 使用Gson解析JSON字符串
val jsonString = """
    {
        "name": "John Doe",
        "age": 30,
        "email": "john@example.com",
        "skills": ["Java", "Kotlin", "Android"],
        "address": {
            "city": "New York",
            "country": "USA"
        }
    }
""".trimIndent()

val gson = Gson()

// 解析为对象
data class Person(
    val name: String,
    val age: Int,
    val email: String,
    val skills: List<String>,
    val address: Address
)

data class Address(
    val city: String,
    val country: String
)

val person = gson.fromJson(jsonString, Person::class.java)
println("姓名: ${person.name}, 年龄: ${person.age}")
println("技能: ${person.skills.joinToString()}")
println("地址: ${person.address.city}, ${person.address.country}")

// 序列化为JSON字符串
val personJson = gson.toJson(person)
println(personJson)

五、进阶实战案例

5.1 实现MVVM架构的新闻应用

项目结构

app/
├── data/
│   ├── api/          # 网络API
│   ├── db/           # 数据库
│   └── repository/   # 数据仓库
├── di/               # 依赖注入
├── ui/
│   ├── news/         # 新闻相关UI
│   └── common/       # 通用组件
├── viewmodel/        # ViewModel
└── utils/            # 工具类

1. 数据模型

// NewsArticle.kt
data class NewsArticle(
    val id: String,
    val title: String,
    val description: String,
    val url: String,
    val imageUrl: String,
    val publishedAt: String,
    val source: String
)

2. 网络API

// NewsApi.kt
interface NewsApi {
    @GET("v2/top-headlines")
    suspend fun getTopHeadlines(
        @Query("country") country: String = "us",
        @Query("apiKey") apiKey: String = BuildConfig.API_KEY
    ): NewsResponse
}

// NewsResponse.kt
data class NewsResponse(
    val status: String,
    val totalResults: Int,
    val articles: List<NewsArticle>
)

3. Repository模式

// NewsRepository.kt
class NewsRepository(
    private val newsApi: NewsApi,
    private val newsDao: NewsDao
) {
    suspend fun getTopHeadlines(): Flow<List<NewsArticle>> {
        return flow {
            try {
                // 先从网络获取
                val response = newsApi.getTopHeadlines()
                if (response.status == "ok") {
                    // 保存到本地数据库
                    newsDao.insertAll(response.articles)
                    emit(response.articles)
                } else {
                    // 网络失败,尝试从数据库获取
                    emit(newsDao.getAllNews())
                }
            } catch (e: Exception) {
                // 网络异常,从数据库获取
                emit(newsDao.getAllNews())
            }
        }
    }
}

4. ViewModel

// NewsViewModel.kt
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
    private val _newsState = MutableStateFlow<NewsUiState>(NewsUiState.Loading)
    val newsState: StateFlow<NewsUiState> = _newsState

    fun loadNews() {
        viewModelScope.launch {
            _newsState.value = NewsUiState.Loading
            repository.getTopHeadlines().collect { articles ->
                if (articles.isNotEmpty()) {
                    _newsState.value = NewsUiState.Success(articles)
                } else {
                    _newsState.value = NewsUiState.Error("No news found")
                }
            }
        }
    }
}

sealed class NewsUiState {
    object Loading : NewsUiState()
    data class Success(val articles: List<NewsArticle>) : NewsUiState()
    data class Error(val message: String) : NewsUiState()
}

5. UI层(Compose实现)

// NewsScreen.kt
@Composable
fun NewsScreen(viewModel: NewsViewModel = viewModel()) {
    val newsState by viewModel.newsState.collectAsState()

    LaunchedEffect(Unit) {
        viewModel.loadNews()
    }

    when (val state = newsState) {
        is NewsUiState.Loading -> {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                CircularProgressIndicator()
            }
        }
        is NewsUiState.Success -> {
            NewsList(articles = state.articles)
        }
        is NewsUiState.Error -> {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text(text = "Error: ${state.message}")
            }
        }
    }
}

@Composable
fun NewsList(articles: List<NewsArticle>) {
    LazyColumn {
        items(articles) { article ->
            NewsItem(article = article)
        }
    }
}

@Composable
fun NewsItem(article: NewsArticle) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
            .clickable { /* 处理点击 */ }
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = article.title,
                style = MaterialTheme.typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis
            )
            Spacer(modifier = Modifier.height(8.dp))
            Text(
                text = article.description ?: "",
                style = MaterialTheme.typography.body2,
                maxLines = 3,
                overflow = TextOverflow.Ellipsis
            )
            Spacer(modifier = Modifier.height(8.dp))
            Text(
                text = article.source,
                style = MaterialTheme.typography.caption,
                color = Color.Gray
            )
        }
    }
}

5.2 实时位置追踪应用

1. 权限处理

// LocationPermissionHelper.kt
class LocationPermissionHelper(private val activity: Activity) {
    private val permissions = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )

    fun checkPermissions(): Boolean {
        return permissions.all {
            ContextCompat.checkSelfPermission(activity, it) == PackageManager.PERMISSION_GRANTED
        }
    }

    fun requestPermissions() {
        ActivityCompat.requestPermissions(
            activity,
            permissions,
            LOCATION_PERMISSION_REQUEST_CODE
        )
    }

    companion object {
        const val LOCATION_PERMISSION_REQUEST_CODE = 1001
    }
}

2. 位置服务

// LocationService.kt
class LocationService : Service() {
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private var locationCallback: LocationCallback? = null

    override fun onCreate() {
        super.onCreate()
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startLocationUpdates()
        return START_STICKY
    }

    private fun startLocationUpdates() {
        val locationRequest = LocationRequest.create().apply {
            interval = 10000 // 10秒
            fastestInterval = 5000 // 5秒
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        }

        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                locationResult.locations.forEach { location ->
                    // 处理位置更新
                    sendLocationUpdate(location)
                }
            }
        }

        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            fusedLocationClient.requestLocationUpdates(
                locationRequest,
                locationCallback as LocationCallback,
                Looper.getMainLooper()
            )
        }
    }

    private fun sendLocationUpdate(location: Location) {
        // 发送广播或通知
        val intent = Intent("LOCATION_UPDATE")
        intent.putExtra("latitude", location.latitude)
        intent.putExtra("longitude", location.longitude)
        intent.putExtra("accuracy", location.accuracy)
        sendBroadcast(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        locationCallback?.let {
            fusedLocationClient.removeLocationUpdates(it)
        }
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

3. 主界面集成

// LocationTrackerActivity.kt
class LocationTrackerActivity : AppCompatActivity() {
    private lateinit var tvLocation: TextView
    private lateinit var tvAccuracy: TextView
    private lateinit var locationReceiver: LocationReceiver

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

        tvLocation = findViewById(R.id.tv_location)
        tvAccuracy = findViewById(R.id.tv_accuracy)

        // 检查权限
        val permissionHelper = LocationPermissionHelper(this)
        if (!permissionHelper.checkPermissions()) {
            permissionHelper.requestPermissions()
        } else {
            startLocationService()
        }

        // 注册广播接收器
        locationReceiver = LocationReceiver()
        val filter = IntentFilter("LOCATION_UPDATE")
        registerReceiver(locationReceiver, filter)
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == LocationPermissionHelper.LOCATION_PERMISSION_REQUEST_CODE) {
            if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
                startLocationService()
            } else {
                Toast.makeText(this, "需要位置权限才能使用此功能", Toast.LENGTH_LONG).show()
            }
        }
    }

    private fun startLocationService() {
        val intent = Intent(this, LocationService::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(intent)
        } else {
            startService(intent)
        }
    }

    inner class LocationReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action == "LOCATION_UPDATE") {
                val latitude = intent.getDoubleExtra("latitude", 0.0)
                val longitude = intent.getDoubleExtra("longitude", 0.0)
                val accuracy = intent.getFloatExtra("accuracy", 0f)

                runOnUiThread {
                    tvLocation.text = "位置: $latitude, $longitude"
                    tvAccuracy.text = "精度: ${accuracy}米"
                }
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(locationReceiver)
    }
}

六、常见问题解决方案

6.1 内存泄漏问题

问题描述:Activity被静态引用导致无法被GC回收。

解决方案

  1. 避免静态引用Context
// 错误示例
class MySingleton {
    companion object {
        private var context: Context? = null // 危险!
    }
}

// 正确示例
class MySingleton {
    companion object {
        private var context: Context? = null
        fun init(context: Context) {
            this.context = context.applicationContext // 使用Application Context
        }
    }
}
  1. 使用WeakReference
class MyActivity : AppCompatActivity() {
    private var weakActivity: WeakReference<MyActivity>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        weakActivity = WeakReference(this)
        
        // 在后台线程中使用
        Thread {
            val activity = weakActivity?.get()
            activity?.runOnUiThread {
                // 更新UI
            }
        }.start()
    }
}
  1. 使用LeakCanary检测
// build.gradle
dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}

6.2 网络请求超时或失败

问题描述:网络请求经常超时或失败。

解决方案

  1. 设置合理的超时时间
val okHttpClient = OkHttpClient.Builder()
    .connectTimeout(30, TimeUnit.SECONDS) // 连接超时
    .readTimeout(30, TimeUnit.SECONDS)    // 读取超时
    .writeTimeout(30, TimeUnit.SECONDS)   // 写入超时
    .retryOnConnectionFailure(true)       // 连接失败重试
    .build()
  1. 添加重试机制
class RetryInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        var response: Response? = null
        var exception: IOException? = null
        
        for (i in 0 until 3) { // 最多重试3次
            try {
                response = chain.proceed(request)
                if (response.isSuccessful) {
                    return response
                }
            } catch (e: IOException) {
                exception = e
                if (i == 2) throw e // 最后一次抛出异常
                Thread.sleep(1000 * (i + 1)) // 指数退避
            }
        }
        
        throw exception ?: IOException("Unknown error")
    }
}

// 添加到OkHttpClient
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(RetryInterceptor())
    .build()
  1. 添加网络状态检查
// NetworkUtils.kt
object NetworkUtils {
    fun isNetworkAvailable(context: Context): Boolean {
        val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) 
            as ConnectivityManager
        val network = connectivityManager.activeNetwork ?: return false
        val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
        return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    }
}

// 在请求前检查
if (NetworkUtils.isNetworkAvailable(this)) {
    // 发起网络请求
} else {
    Toast.makeText(this, "网络不可用", Toast.LENGTH_SHORT).show()
}

6.3 UI线程阻塞问题

问题描述:在主线程执行耗时操作导致ANR(Application Not Responding)。

解决方案

  1. 使用协程处理异步操作
// 在ViewModel中
viewModelScope.launch(Dispatchers.IO) {
    // 耗时操作
    val result = repository.getData()
    withContext(Dispatchers.Main) {
        // 更新UI
        updateUI(result)
    }
}
  1. 使用RxJava
// 使用RxJava处理异步
Single.fromCallable {
    // 耗时操作
    heavyOperation()
}
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({ result ->
        // 更新UI
        updateUI(result)
    }, { error ->
        // 处理错误
        handleError(error)
    })
  1. 使用AsyncTask(已废弃,但了解原理)
// AsyncTask示例(已废弃,仅作参考)
class MyAsyncTask : AsyncTask<Void, Void, String>() {
    override fun doInBackground(vararg params: Void): String {
        // 在后台线程执行
        return heavyOperation()
    }

    override fun onPostExecute(result: String) {
        // 在主线程执行
        updateUI(result)
    }
}

6.4 权限处理问题

问题描述:Android 6.0+需要动态请求权限。

解决方案

  1. 使用Activity Result API(推荐):
// 在Activity中
class MainActivity : AppCompatActivity() {
    private val locationPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        val fineLocationGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
        val coarseLocationGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: false
        
        if (fineLocationGranted || coarseLocationGranted) {
            // 权限已授予
            startLocationUpdates()
        } else {
            // 权限被拒绝
            showPermissionDeniedDialog()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val btnRequestPermission = findViewById<Button>(R.id.btn_request_permission)
        btnRequestPermission.setOnClickListener {
            locationPermissionLauncher.launch(
                arrayOf(
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION
                )
            )
        }
    }
}
  1. 使用第三方库简化权限处理
// build.gradle
dependencies {
    implementation 'com.github.fondesa:permissions-handler:3.0.0'
}
// 使用PermissionsHandler
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val btnRequestPermission = findViewById<Button>(R.id.btn_request_permission)
        btnRequestPermission.setOnClickListener {
            PermissionsHandler.requestPermissions(
                this,
                arrayOf(
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION
                ),
                object : PermissionsHandler.Callback {
                    override fun onPermissionsGranted() {
                        // 权限已授予
                        startLocationUpdates()
                    }

                    override fun onPermissionsDenied(deniedPermissions: Array<String>) {
                        // 权限被拒绝
                        showPermissionDeniedDialog()
                    }
                }
            )
        }
    }
}

6.5 内存优化技巧

问题描述:应用内存占用过高,容易导致OOM(Out of Memory)。

解决方案

  1. 图片加载优化
// 使用Glide加载图片(推荐)
Glide.with(context)
    .load(imageUrl)
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .into(imageView)

// 使用Coil(Kotlin友好)
imageView.load(imageUrl) {
    crossfade(true)
    placeholder(R.drawable.placeholder)
    error(R.drawable.error)
}
  1. 避免内存泄漏
// 使用ViewModel避免Activity泄漏
class MyViewModel : ViewModel() {
    private val _data = MutableStateFlow<List<String>>(emptyList())
    val data: StateFlow<List<String>> = _data

    fun loadData() {
        viewModelScope.launch {
            // 加载数据
            val result = repository.getData()
            _data.value = result
        }
    }
}

// 在Activity中使用
class MainActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

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

        // 观察数据变化
        lifecycleScope.launch {
            viewModel.data.collect { data ->
                // 更新UI
                updateUI(data)
            }
        }
    }
}
  1. 使用ProGuard/R8优化
// build.gradle
android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

// proguard-rules.pro
-keep class com.example.myapp.** { *; }
-keepclassmembers class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

七、性能优化最佳实践

7.1 启动优化

1. 冷启动优化

// Application类中
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // 延迟初始化非必要组件
        val startTime = System.currentTimeMillis()
        
        // 使用异步初始化
        val executor = Executors.newFixedThreadPool(4)
        executor.execute {
            // 初始化耗时组件
            initThirdPartyLibraries()
        }
        
        // 记录启动时间
        Log.d("Startup", "Application onCreate took ${System.currentTimeMillis() - startTime}ms")
    }
    
    private fun initThirdPartyLibraries() {
        // 初始化第三方库
        // 例如:Firebase, LeakCanary等
    }
}

2. 使用App Startup库

// build.gradle
dependencies {
    implementation "androidx.startup:startup-runtime:1.1.1"
}
// Initializer.kt
class MyInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        // 初始化逻辑
        initThirdPartyLibraries()
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}

// AndroidManifest.xml
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="com.example.myapp.MyInitializer"
        android:value="androidx.startup" />
</provider>

7.2 渲染优化

1. 减少过度绘制

<!-- 在主题中设置 -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
    <item name="android:windowBackground">@null</item>
    <item name="android:colorBackground">@color/background</item>
</style>

2. 使用ConstraintLayout减少层级

<!-- 优化前:多层嵌套 -->
<LinearLayout>
    <LinearLayout>
        <TextView />
        <TextView />
    </LinearLayout>
</LinearLayout>

<!-- 优化后:ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView
        android:id="@+id/text1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
    <TextView
        android:id="@+id/text2"
        app:layout_constraintTop_toBottomOf="@id/text1"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

7.3 电池优化

1. 使用JobScheduler处理后台任务

// JobScheduler示例
class MyJobScheduler {
    fun scheduleJob(context: Context) {
        val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
        
        val jobInfo = JobInfo.Builder(1, ComponentName(context, MyJobService::class.java))
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
            .setRequiresCharging(true)
            .setPeriodic(3600000) // 每小时执行一次
            .build()
        
        jobScheduler.schedule(jobInfo)
    }
}

// JobService
class MyJobService : JobService() {
    override fun onStartJob(params: JobParameters?): Boolean {
        // 执行任务
        Thread {
            // 耗时操作
            performTask()
            jobFinished(params, false)
        }.start()
        return true
    }

    override fun onStopJob(params: JobParameters?): Boolean {
        return false
    }
}

2. 使用WorkManager

// build.gradle
dependencies {
    implementation "androidx.work:work-runtime-ktx:2.7.1"
}
// MyWorker.kt
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        return try {
            // 执行任务
            performTask()
            Result.success()
        } catch (e: Exception) {
            Result.failure()
        }
    }
}

// 调度任务
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresCharging(true)
            .build()
    )
    .setInitialDelay(1, TimeUnit.HOURS)
    .build()

WorkManager.getInstance(context).enqueue(workRequest)

八、测试与调试

8.1 单元测试

1. JUnit测试

// Calculator.kt
class Calculator {
    fun add(a: Int, b: Int): Int = a + b
    fun subtract(a: Int, b: Int): Int = a - b
    fun multiply(a: Int, b: Int): Int = a * b
    fun divide(a: Int, b: Int): Int = a / b
}

// CalculatorTest.kt
import org.junit.Test
import org.junit.Assert.*

class CalculatorTest {
    @Test
    fun testAdd() {
        val calculator = Calculator()
        assertEquals(5, calculator.add(2, 3))
        assertEquals(0, calculator.add(-1, 1))
    }

    @Test
    fun testSubtract() {
        val calculator = Calculator()
        assertEquals(1, calculator.subtract(3, 2))
        assertEquals(-2, calculator.subtract(1, 3))
    }

    @Test
    fun testMultiply() {
        val calculator = Calculator()
        assertEquals(6, calculator.multiply(2, 3))
        assertEquals(0, calculator.multiply(0, 5))
    }

    @Test
    fun testDivide() {
        val calculator = Calculator()
        assertEquals(2, calculator.divide(6, 3))
        assertEquals(0, calculator.divide(0, 5))
    }
}

2. Mockito测试

// build.gradle
dependencies {
    testImplementation 'junit:junit:4.13.2'
    testImplementation 'org.mockito:mockito-core:4.0.0'
    testImplementation 'androidx.arch.core:core-testing:2.1.0'
}
// UserRepository.kt
interface UserRepository {
    suspend fun getUser(id: String): User?
}

// UserViewModel.kt
class UserViewModel(private val repository: UserRepository) : ViewModel() {
    private val _user = MutableStateFlow<User?>(null)
    val user: StateFlow<User?> = _user

    fun loadUser(id: String) {
        viewModelScope.launch {
            _user.value = repository.getUser(id)
        }
    }
}

// UserViewModelTest.kt
import org.junit.Test
import org.mockito.Mockito.*
import kotlinx.coroutines.test.runTest

class UserViewModelTest {
    @Test
    fun testLoadUser() = runTest {
        // 创建Mock
        val mockRepository = mock(UserRepository::class.java)
        val mockUser = User("1", "John Doe", "john@example.com")
        
        // 设置Mock行为
        `when`(mockRepository.getUser("1")).thenReturn(mockUser)
        
        // 创建ViewModel
        val viewModel = UserViewModel(mockRepository)
        
        // 调用方法
        viewModel.loadUser("1")
        
        // 验证结果
        assertEquals(mockUser, viewModel.user.value)
        
        // 验证方法被调用
        verify(mockRepository).getUser("1")
    }
}

8.2 UI测试

1. Espresso测试

// build.gradle
dependencies {
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    androidTestImplementation 'androidx.test:runner:1.4.0'
    androidTestImplementation 'androidx.test:rules:1.4.0'
}
// MainActivityTest.kt
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun testLoginSuccess() {
        // 输入用户名
        onView(withId(R.id.et_username))
            .perform(typeText("admin"), closeSoftKeyboard())

        // 输入密码
        onView(withId(R.id.et_password))
            .perform(typeText("123456"), closeSoftKeyboard())

        // 点击登录按钮
        onView(withId(R.id.btn_login))
            .perform(click())

        // 验证结果
        onView(withId(R.id.tv_result))
            .check(matches(withText("登录成功!")))
    }

    @Test
    fun testLoginFailure() {
        // 输入错误的用户名
        onView(withId(R.id.et_username))
            .perform(typeText("wrong"), closeSoftKeyboard())

        // 输入错误的密码
        onView(withId(R.id.et_password))
            .perform(typeText("wrong"), closeSoftKeyboard())

        // 点击登录按钮
        onView(withId(R.id.btn_login))
            .perform(click())

        // 验证结果
        onView(withId(R.id.tv_result))
            .check(matches(withText("用户名或密码错误!")))
    }
}

2. Compose UI测试

// build.gradle
dependencies {
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.2.1'
    debugImplementation 'androidx.compose.ui:ui-test-manifest:1.2.1'
}
// NewsScreenTest.kt
@RunWith(AndroidJUnit4::class)
class NewsScreenTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun testNewsListDisplayed() {
        val articles = listOf(
            NewsArticle("1", "Title 1", "Description 1", "url1", "image1", "2023-01-01", "Source1"),
            NewsArticle("2", "Title 2", "Description 2", "url2", "image2", "2023-01-02", "Source2")
        )

        composeTestRule.setContent {
            NewsList(articles = articles)
        }

        // 验证文章数量
        composeTestRule.onAllNodesWithText("Title 1").assertCountEquals(1)
        composeTestRule.onAllNodesWithText("Title 2").assertCountEquals(1)
    }

    @Test
    fun testNewsItemClick() {
        var clicked = false
        val article = NewsArticle("1", "Title", "Description", "url", "image", "2023-01-01", "Source")

        composeTestRule.setContent {
            NewsItem(article = article)
        }

        // 点击新闻项
        composeTestRule.onNodeWithText("Title").performClick()

        // 验证点击事件
        // 注意:在实际测试中需要设置点击回调并验证
    }
}

8.3 调试技巧

1. 使用Android Studio调试器

  • 设置断点
  • 查看变量值
  • 条件断点
  • 日志过滤

2. 使用Logcat

// 自定义日志工具类
object LogUtils {
    private const val TAG = "MyApp"

    fun d(message: String) {
        Log.d(TAG, message)
    }

    fun i(message: String) {
        Log.i(TAG, message)
    }

    fun w(message: String) {
        Log.w(TAG, message)
    }

    fun e(message: String, throwable: Throwable? = null) {
        if (throwable != null) {
            Log.e(TAG, message, throwable)
        } else {
            Log.e(TAG, message)
        }
    }
}

// 使用示例
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        LogUtils.d("MainActivity onCreate started")
        // ... 其他代码
        LogUtils.d("MainActivity onCreate finished")
    }
}

3. 使用Profiler分析性能

  • CPU Profiler:分析CPU使用情况
  • Memory Profiler:分析内存使用情况
  • Network Profiler:分析网络请求
  • Energy Profiler:分析电池消耗

九、Android 13+新特性适配

9.1 权限变更

1. 通知权限

// 检查通知权限
fun checkNotificationPermission(context: Context): Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        return ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.POST_NOTIFICATIONS
        ) == PackageManager.PERMISSION_GRANTED
    }
    return true
}

// 请求通知权限
fun requestNotificationPermission(activity: Activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        ActivityCompat.requestPermissions(
            activity,
            arrayOf(Manifest.permission.POST_NOTIFICATIONS),
            NOTIFICATION_PERMISSION_REQUEST_CODE
        )
    }
}

2. 媒体权限

// Android 13+媒体权限
val mediaPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    arrayOf(
        Manifest.permission.READ_MEDIA_IMAGES,
        Manifest.permission.READ_MEDIA_VIDEO,
        Manifest.permission.READ_MEDIA_AUDIO
    )
} else {
    arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
}

9.2 新功能适配

1. 语义意图(Intent Filters)

<!-- AndroidManifest.xml -->
<activity android:name=".DeepLinkActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" android:host="example.com" />
    </intent-filter>
</activity>

2. 电池优化

// 检查电池优化设置
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
    val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        powerManager.isIgnoringBatteryOptimizations(context.packageName)
    } else {
        true
    }
}

// 请求忽略电池优化
fun requestIgnoreBatteryOptimizations(activity: Activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val intent = Intent().apply {
            action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
            data = Uri.parse("package:${activity.packageName}")
        }
        activity.startActivity(intent)
    }
}

十、总结

Android开发是一个不断演进的领域,从基础的UI构建到复杂的架构设计,再到性能优化和新特性适配,每个阶段都有其独特的挑战和解决方案。通过本文的实例分析,我们涵盖了:

  1. 基础开发:环境搭建、UI组件、数据存储
  2. 网络通信:Retrofit、JSON解析
  3. 进阶架构:MVVM、Repository模式
  4. 实战案例:新闻应用、位置追踪
  5. 常见问题:内存泄漏、网络问题、权限处理
  6. 性能优化:启动优化、渲染优化、电池优化
  7. 测试调试:单元测试、UI测试、调试技巧
  8. 新特性适配:Android 13+权限变更

掌握这些知识和技能,将帮助你成为一名优秀的Android开发者。记住,实践是最好的老师,不断编写代码、解决问题、优化应用,才能在Android开发的道路上走得更远。

参考资料

  1. Android开发者官方文档
  2. Jetpack Compose官方文档
  3. Kotlin官方文档
  4. Retrofit官方文档
  5. LeakCanary官方文档
  6. WorkManager官方文档
  7. Android 13开发者文档