引言
Android开发作为移动应用开发的主流技术之一,涵盖了从基础UI构建到复杂架构设计的广泛领域。本文将通过一系列实例分析,从基础到进阶,详细讲解Android开发的核心技术,并结合实战案例和常见问题解决方案,帮助开发者系统性地掌握Android编程。
一、Android开发基础
1.1 Android开发环境搭建
Android开发主要依赖Android Studio,这是Google官方推荐的集成开发环境(IDE)。以下是搭建步骤:
- 下载并安装Android Studio:访问Android开发者官网下载最新版本。
- 配置SDK:安装完成后,打开Android Studio,通过SDK Manager安装所需的Android SDK版本和工具。
- 创建第一个项目:选择”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用户信息
- 添加依赖(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'
}
- 创建数据模型:
// 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
)
- 创建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
)
- 创建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()
}
}
- 在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回收。
解决方案:
- 避免静态引用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
}
}
}
- 使用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()
}
}
- 使用LeakCanary检测:
// build.gradle
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}
6.2 网络请求超时或失败
问题描述:网络请求经常超时或失败。
解决方案:
- 设置合理的超时时间:
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) // 连接超时
.readTimeout(30, TimeUnit.SECONDS) // 读取超时
.writeTimeout(30, TimeUnit.SECONDS) // 写入超时
.retryOnConnectionFailure(true) // 连接失败重试
.build()
- 添加重试机制:
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()
- 添加网络状态检查:
// 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)。
解决方案:
- 使用协程处理异步操作:
// 在ViewModel中
viewModelScope.launch(Dispatchers.IO) {
// 耗时操作
val result = repository.getData()
withContext(Dispatchers.Main) {
// 更新UI
updateUI(result)
}
}
- 使用RxJava:
// 使用RxJava处理异步
Single.fromCallable {
// 耗时操作
heavyOperation()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result ->
// 更新UI
updateUI(result)
}, { error ->
// 处理错误
handleError(error)
})
- 使用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+需要动态请求权限。
解决方案:
- 使用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
)
)
}
}
}
- 使用第三方库简化权限处理:
// 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)。
解决方案:
- 图片加载优化:
// 使用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)
}
- 避免内存泄漏:
// 使用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)
}
}
}
}
- 使用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构建到复杂的架构设计,再到性能优化和新特性适配,每个阶段都有其独特的挑战和解决方案。通过本文的实例分析,我们涵盖了:
- 基础开发:环境搭建、UI组件、数据存储
- 网络通信:Retrofit、JSON解析
- 进阶架构:MVVM、Repository模式
- 实战案例:新闻应用、位置追踪
- 常见问题:内存泄漏、网络问题、权限处理
- 性能优化:启动优化、渲染优化、电池优化
- 测试调试:单元测试、UI测试、调试技巧
- 新特性适配:Android 13+权限变更
掌握这些知识和技能,将帮助你成为一名优秀的Android开发者。记住,实践是最好的老师,不断编写代码、解决问题、优化应用,才能在Android开发的道路上走得更远。
