2026-01-15 15:34:23 +08:00
|
|
|
|
# 性能优化最佳实践
|
|
|
|
|
|
|
2026-01-16 14:54:07 +08:00
|
|
|
|
性能优化是Android应用开发的重要环节,直接影响用户体验和应用质量。本文档介绍Android应用性能优化的最佳实践。
|
|
|
|
|
|
|
|
|
|
|
|
## 目录
|
|
|
|
|
|
|
|
|
|
|
|
- [性能优化策略](#性能优化策略)
|
|
|
|
|
|
- [启动优化实践](#启动优化实践)
|
|
|
|
|
|
- [内存优化实践](#内存优化实践)
|
|
|
|
|
|
- [布局优化实践](#布局优化实践)
|
|
|
|
|
|
- [网络优化实践](#网络优化实践)
|
|
|
|
|
|
- [性能监控](#性能监控)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 性能优化策略
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 性能优化原则
|
|
|
|
|
|
|
|
|
|
|
|
#### 测量优先
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// ✅ 好的做法:先测量,再优化
|
|
|
|
|
|
class PerformanceMonitor {
|
|
|
|
|
|
fun measureTime(block: () -> Unit): Long {
|
|
|
|
|
|
val startTime = System.currentTimeMillis()
|
|
|
|
|
|
block()
|
|
|
|
|
|
return System.currentTimeMillis() - startTime
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun measureMemory(block: () -> Unit): Long {
|
|
|
|
|
|
val runtime = Runtime.getRuntime()
|
|
|
|
|
|
val beforeMemory = runtime.totalMemory() - runtime.freeMemory()
|
|
|
|
|
|
block()
|
|
|
|
|
|
val afterMemory = runtime.totalMemory() - runtime.freeMemory()
|
|
|
|
|
|
return afterMemory - beforeMemory
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用
|
|
|
|
|
|
val monitor = PerformanceMonitor()
|
|
|
|
|
|
val time = monitor.measureTime {
|
|
|
|
|
|
// 执行代码
|
|
|
|
|
|
}
|
|
|
|
|
|
Log.d("Performance", "执行时间: ${time}ms")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 优化关键路径
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 识别关键路径并优化
|
|
|
|
|
|
class AppInitializer {
|
|
|
|
|
|
fun initialize() {
|
|
|
|
|
|
// 关键路径:必须立即初始化
|
|
|
|
|
|
initCriticalComponents()
|
|
|
|
|
|
|
|
|
|
|
|
// 非关键路径:可以延迟初始化
|
|
|
|
|
|
Handler(Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
initNonCriticalComponents()
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 性能指标
|
|
|
|
|
|
|
|
|
|
|
|
#### 关键指标
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 启动时间
|
|
|
|
|
|
class StartupTimeTracker {
|
|
|
|
|
|
private var appStartTime: Long = 0
|
|
|
|
|
|
|
|
|
|
|
|
fun onAppStart() {
|
|
|
|
|
|
appStartTime = System.currentTimeMillis()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun onFirstFrame() {
|
|
|
|
|
|
val startupTime = System.currentTimeMillis() - appStartTime
|
|
|
|
|
|
Log.d("Performance", "启动时间: ${startupTime}ms")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 内存使用
|
|
|
|
|
|
class MemoryTracker {
|
|
|
|
|
|
fun getMemoryUsage(): MemoryInfo {
|
|
|
|
|
|
val runtime = Runtime.getRuntime()
|
|
|
|
|
|
val totalMemory = runtime.totalMemory()
|
|
|
|
|
|
val freeMemory = runtime.freeMemory()
|
|
|
|
|
|
val usedMemory = totalMemory - freeMemory
|
|
|
|
|
|
|
|
|
|
|
|
return MemoryInfo(
|
|
|
|
|
|
total = totalMemory,
|
|
|
|
|
|
used = usedMemory,
|
|
|
|
|
|
free = freeMemory
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 启动优化实践
|
|
|
|
|
|
|
|
|
|
|
|
### 1. Application优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 延迟初始化
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// ✅ 好的做法:延迟初始化非关键组件
|
|
|
|
|
|
class MyApplication : Application() {
|
|
|
|
|
|
override fun onCreate() {
|
|
|
|
|
|
super.onCreate()
|
|
|
|
|
|
|
|
|
|
|
|
// 关键初始化:必须立即执行
|
|
|
|
|
|
initCriticalComponents()
|
|
|
|
|
|
|
|
|
|
|
|
// 非关键初始化:延迟执行
|
|
|
|
|
|
initNonCriticalComponentsAsync()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun initCriticalComponents() {
|
|
|
|
|
|
// 初始化关键组件
|
|
|
|
|
|
CrashReporting.init(this)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun initNonCriticalComponentsAsync() {
|
|
|
|
|
|
// 使用后台线程初始化
|
|
|
|
|
|
Thread {
|
|
|
|
|
|
// 初始化非关键组件
|
|
|
|
|
|
Analytics.init(this)
|
|
|
|
|
|
ImageLoader.init(this)
|
|
|
|
|
|
}.start()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用App Startup
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 使用App Startup统一管理初始化
|
|
|
|
|
|
class MyInitializer : Initializer<Unit> {
|
|
|
|
|
|
override fun create(context: Context) {
|
|
|
|
|
|
// 初始化组件
|
|
|
|
|
|
Analytics.init(context)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun dependencies(): List<Class<out Initializer<*>>> {
|
|
|
|
|
|
// 定义依赖关系
|
|
|
|
|
|
return listOf(OtherInitializer::class.java)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在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.MyInitializer"
|
|
|
|
|
|
// android:value="androidx.startup" />
|
|
|
|
|
|
// </provider>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 首屏优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 减少首屏布局复杂度
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- ✅ 好的做法:简化首屏布局 -->
|
|
|
|
|
|
<androidx.constraintlayout.widget.ConstraintLayout
|
|
|
|
|
|
android:layout_width="match_parent"
|
|
|
|
|
|
android:layout_height="match_parent">
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 只显示关键内容 -->
|
|
|
|
|
|
<TextView
|
|
|
|
|
|
android:id="@+id/title"
|
|
|
|
|
|
android:layout_width="wrap_content"
|
|
|
|
|
|
android:layout_height="wrap_content"
|
|
|
|
|
|
android:text="欢迎"
|
|
|
|
|
|
app:layout_constraintTop_toTopOf="parent"
|
|
|
|
|
|
app:layout_constraintStart_toStartOf="parent"
|
|
|
|
|
|
app:layout_constraintEnd_toEndOf="parent"/>
|
|
|
|
|
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ❌ 不好的做法:首屏包含过多内容 -->
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用ViewStub延迟加载
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- 使用ViewStub延迟加载非关键视图 -->
|
|
|
|
|
|
<ViewStub
|
|
|
|
|
|
android:id="@+id/viewStub"
|
|
|
|
|
|
android:layout_width="match_parent"
|
|
|
|
|
|
android:layout_height="wrap_content"
|
|
|
|
|
|
android:layout="@layout/expensive_view"/>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 在需要时加载
|
|
|
|
|
|
class MainActivity : AppCompatActivity() {
|
|
|
|
|
|
private lateinit var viewStub: ViewStub
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
|
setContentView(R.layout.activity_main)
|
|
|
|
|
|
|
|
|
|
|
|
viewStub = findViewById(R.id.viewStub)
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟加载
|
|
|
|
|
|
button.setOnClickListener {
|
|
|
|
|
|
if (viewStub.parent != null) {
|
|
|
|
|
|
viewStub.inflate()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 启动时间测量
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用Trace API
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 使用Trace API标记启动流程
|
|
|
|
|
|
class MainActivity : AppCompatActivity() {
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
|
Trace.beginSection("MainActivity.onCreate")
|
|
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
|
setContentView(R.layout.activity_main)
|
|
|
|
|
|
|
|
|
|
|
|
Trace.beginSection("initViews")
|
|
|
|
|
|
initViews()
|
|
|
|
|
|
Trace.endSection()
|
|
|
|
|
|
|
|
|
|
|
|
Trace.beginSection("loadData")
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
Trace.endSection()
|
|
|
|
|
|
|
|
|
|
|
|
Trace.endSection()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用Systrace分析
|
|
|
|
|
|
// python systrace.py -t 10 -o trace.html sched freq idle am wm gfx view binder_driver hal dalvik camera input res
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 内存优化实践
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 内存泄漏预防
|
|
|
|
|
|
|
|
|
|
|
|
#### 避免静态引用
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// ❌ 不好的做法:静态引用Context
|
|
|
|
|
|
object AppContext {
|
|
|
|
|
|
var context: Context? = null // 可能导致内存泄漏
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 好的做法:使用Application Context
|
|
|
|
|
|
object AppContext {
|
|
|
|
|
|
fun getContext(): Context {
|
|
|
|
|
|
return MyApplication.instance
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 正确处理生命周期
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// ✅ 好的做法:正确处理生命周期
|
|
|
|
|
|
class MainActivity : AppCompatActivity() {
|
|
|
|
|
|
private var handler: Handler? = null
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
|
|
|
|
|
|
|
// 使用静态内部类+WeakReference
|
|
|
|
|
|
handler = MyHandler(this)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
|
|
|
|
super.onDestroy()
|
|
|
|
|
|
handler?.removeCallbacksAndMessages(null)
|
|
|
|
|
|
handler = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class MyHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {
|
|
|
|
|
|
private val activityRef = WeakReference(activity)
|
|
|
|
|
|
|
|
|
|
|
|
override fun handleMessage(msg: Message) {
|
|
|
|
|
|
activityRef.get()?.let {
|
|
|
|
|
|
// 处理消息
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 内存使用优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用对象池
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 对象池:复用对象,减少GC
|
|
|
|
|
|
class ObjectPool<T>(private val factory: () -> T, private val maxSize: Int = 10) {
|
|
|
|
|
|
private val pool = mutableListOf<T>()
|
|
|
|
|
|
|
|
|
|
|
|
fun acquire(): T {
|
|
|
|
|
|
return if (pool.isNotEmpty()) {
|
|
|
|
|
|
pool.removeAt(pool.size - 1)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
factory()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun release(obj: T) {
|
|
|
|
|
|
if (pool.size < maxSize) {
|
|
|
|
|
|
pool.add(obj)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用
|
|
|
|
|
|
val viewPool = ObjectPool({ View(context) })
|
|
|
|
|
|
val view = viewPool.acquire()
|
|
|
|
|
|
// 使用view
|
|
|
|
|
|
viewPool.release(view)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 优化图片加载
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 使用Glide等图片加载库
|
|
|
|
|
|
Glide.with(context)
|
|
|
|
|
|
.load(imageUrl)
|
|
|
|
|
|
.placeholder(R.drawable.placeholder)
|
|
|
|
|
|
.error(R.drawable.error)
|
|
|
|
|
|
.override(800, 600) // 指定尺寸
|
|
|
|
|
|
.into(imageView)
|
|
|
|
|
|
|
|
|
|
|
|
// 使用WebP格式
|
|
|
|
|
|
// WebP格式比PNG/JPG更小,加载更快
|
|
|
|
|
|
|
|
|
|
|
|
// 使用图片压缩
|
|
|
|
|
|
fun compressBitmap(bitmap: Bitmap, quality: Int = 80): ByteArray {
|
|
|
|
|
|
val stream = ByteArrayOutputStream()
|
|
|
|
|
|
bitmap.compress(Bitmap.CompressFormat.WEBP, quality, stream)
|
|
|
|
|
|
return stream.toByteArray()
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 内存监控
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用LeakCanary
|
|
|
|
|
|
|
|
|
|
|
|
```gradle
|
|
|
|
|
|
// build.gradle
|
|
|
|
|
|
dependencies {
|
|
|
|
|
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 自动检测内存泄漏
|
|
|
|
|
|
// LeakCanary会自动检测并报告内存泄漏
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 手动内存分析
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 获取内存信息
|
|
|
|
|
|
class MemoryAnalyzer {
|
|
|
|
|
|
fun getMemoryInfo(): String {
|
|
|
|
|
|
val runtime = Runtime.getRuntime()
|
|
|
|
|
|
val maxMemory = runtime.maxMemory()
|
|
|
|
|
|
val totalMemory = runtime.totalMemory()
|
|
|
|
|
|
val freeMemory = runtime.freeMemory()
|
|
|
|
|
|
val usedMemory = totalMemory - freeMemory
|
|
|
|
|
|
|
|
|
|
|
|
return """
|
|
|
|
|
|
Max Memory: ${maxMemory / 1024 / 1024}MB
|
|
|
|
|
|
Total Memory: ${totalMemory / 1024 / 1024}MB
|
|
|
|
|
|
Used Memory: ${usedMemory / 1024 / 1024}MB
|
|
|
|
|
|
Free Memory: ${freeMemory / 1024 / 1024}MB
|
|
|
|
|
|
""".trimIndent()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun dumpHeap(): File {
|
|
|
|
|
|
val heapDumpFile = File(cacheDir, "heap_dump.hprof")
|
|
|
|
|
|
Debug.dumpHprofData(heapDumpFile.absolutePath)
|
|
|
|
|
|
return heapDumpFile
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 布局优化实践
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 减少布局层级
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用ConstraintLayout
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- ✅ 好的做法:使用ConstraintLayout减少层级 -->
|
|
|
|
|
|
<androidx.constraintlayout.widget.ConstraintLayout
|
|
|
|
|
|
android:layout_width="match_parent"
|
|
|
|
|
|
android:layout_height="match_parent">
|
|
|
|
|
|
|
|
|
|
|
|
<TextView
|
|
|
|
|
|
android:id="@+id/title"
|
|
|
|
|
|
android:layout_width="wrap_content"
|
|
|
|
|
|
android:layout_height="wrap_content"
|
|
|
|
|
|
android:text="标题"
|
|
|
|
|
|
app:layout_constraintStart_toStartOf="parent"
|
|
|
|
|
|
app:layout_constraintTop_toTopOf="parent"/>
|
|
|
|
|
|
|
|
|
|
|
|
<TextView
|
|
|
|
|
|
android:id="@+id/subtitle"
|
|
|
|
|
|
android:layout_width="wrap_content"
|
|
|
|
|
|
android:layout_height="wrap_content"
|
|
|
|
|
|
android:text="副标题"
|
|
|
|
|
|
app:layout_constraintStart_toStartOf="parent"
|
|
|
|
|
|
app:layout_constraintTop_toBottomOf="@id/title"/>
|
|
|
|
|
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ❌ 不好的做法:嵌套过多 -->
|
|
|
|
|
|
<LinearLayout>
|
|
|
|
|
|
<LinearLayout>
|
|
|
|
|
|
<TextView/>
|
|
|
|
|
|
</LinearLayout>
|
|
|
|
|
|
</LinearLayout>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 避免过度绘制
|
|
|
|
|
|
|
|
|
|
|
|
#### 减少背景绘制
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- ✅ 好的做法:移除不必要的背景 -->
|
|
|
|
|
|
<LinearLayout
|
|
|
|
|
|
android:layout_width="match_parent"
|
|
|
|
|
|
android:layout_height="wrap_content"
|
|
|
|
|
|
android:background="@android:color/white">
|
|
|
|
|
|
<!-- 子视图不需要再设置背景 -->
|
|
|
|
|
|
</LinearLayout>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ❌ 不好的做法:多层背景 -->
|
|
|
|
|
|
<LinearLayout android:background="@color/background1">
|
|
|
|
|
|
<LinearLayout android:background="@color/background2">
|
|
|
|
|
|
<!-- 导致过度绘制 -->
|
|
|
|
|
|
</LinearLayout>
|
|
|
|
|
|
</LinearLayout>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用clipToPadding
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- 使用clipToPadding减少绘制区域 -->
|
|
|
|
|
|
<RecyclerView
|
|
|
|
|
|
android:layout_width="match_parent"
|
|
|
|
|
|
android:layout_height="match_parent"
|
|
|
|
|
|
android:clipToPadding="false"
|
|
|
|
|
|
android:padding="16dp"/>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. RecyclerView优化
|
|
|
|
|
|
|
|
|
|
|
|
#### ViewHolder复用
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// ✅ 好的做法:正确实现ViewHolder
|
|
|
|
|
|
class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
|
|
|
|
|
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
|
|
|
|
val nameTextView: TextView = itemView.findViewById(R.id.name)
|
|
|
|
|
|
val emailTextView: TextView = itemView.findViewById(R.id.email)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
|
|
|
|
val view = LayoutInflater.from(parent.context)
|
|
|
|
|
|
.inflate(R.layout.item_user, parent, false)
|
|
|
|
|
|
return ViewHolder(view)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
|
|
|
|
val user = users[position]
|
|
|
|
|
|
holder.nameTextView.text = user.name
|
|
|
|
|
|
holder.emailTextView.text = user.email
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用DiffUtil
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 使用DiffUtil优化列表更新
|
|
|
|
|
|
class UserDiffCallback(
|
|
|
|
|
|
private val oldList: List<User>,
|
|
|
|
|
|
private val newList: List<User>
|
|
|
|
|
|
) : DiffUtil.Callback() {
|
|
|
|
|
|
override fun getOldListSize() = oldList.size
|
|
|
|
|
|
override fun getNewListSize() = newList.size
|
|
|
|
|
|
|
|
|
|
|
|
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
|
|
|
|
|
return oldList[oldItemPosition].id == newList[newItemPosition].id
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
|
|
|
|
|
return oldList[oldItemPosition] == newList[newItemPosition]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用
|
|
|
|
|
|
val diffResult = DiffUtil.calculateDiff(UserDiffCallback(oldList, newList))
|
|
|
|
|
|
adapter.users = newList
|
|
|
|
|
|
diffResult.dispatchUpdatesTo(adapter)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 网络优化实践
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 请求优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 请求合并
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// ✅ 好的做法:合并请求
|
|
|
|
|
|
class ApiService {
|
|
|
|
|
|
@GET("users")
|
|
|
|
|
|
suspend fun getUsers(@Query("ids") ids: String): List<User>
|
|
|
|
|
|
// 一次请求获取多个用户,而不是多次请求
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ❌ 不好的做法:多次请求
|
|
|
|
|
|
// for (id in userIds) {
|
|
|
|
|
|
// apiService.getUser(id)
|
|
|
|
|
|
// }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用缓存
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 使用OkHttp缓存
|
|
|
|
|
|
val client = OkHttpClient.Builder()
|
|
|
|
|
|
.cache(Cache(cacheDir, 10 * 1024 * 1024)) // 10MB缓存
|
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
|
|
|
|
// 使用Retrofit缓存
|
|
|
|
|
|
@Headers("Cache-Control: max-age=3600")
|
|
|
|
|
|
@GET("users/{id}")
|
|
|
|
|
|
suspend fun getUser(@Path("id") id: String): User
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 数据压缩
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用Gzip
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 服务器端启用Gzip压缩
|
|
|
|
|
|
// 客户端自动处理Gzip响应
|
|
|
|
|
|
// OkHttp自动支持Gzip解压
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 数据格式优化
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 使用Protobuf替代JSON
|
|
|
|
|
|
// Protobuf更小、更快
|
|
|
|
|
|
data class User(
|
|
|
|
|
|
val id: String,
|
|
|
|
|
|
val name: String,
|
|
|
|
|
|
val email: String
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为Protobuf
|
|
|
|
|
|
val userProto = UserProto.newBuilder()
|
|
|
|
|
|
.setId(user.id)
|
|
|
|
|
|
.setName(user.name)
|
|
|
|
|
|
.setEmail(user.email)
|
|
|
|
|
|
.build()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 网络监控
|
|
|
|
|
|
|
|
|
|
|
|
#### 监控网络请求
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 使用OkHttp Interceptor监控
|
|
|
|
|
|
class NetworkMonitorInterceptor : Interceptor {
|
|
|
|
|
|
override fun intercept(chain: Interceptor.Chain): Response {
|
|
|
|
|
|
val request = chain.request()
|
|
|
|
|
|
val startTime = System.currentTimeMillis()
|
|
|
|
|
|
|
|
|
|
|
|
val response = chain.proceed(request)
|
|
|
|
|
|
|
|
|
|
|
|
val duration = System.currentTimeMillis() - startTime
|
|
|
|
|
|
Log.d("Network", "${request.url} - ${duration}ms")
|
|
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val client = OkHttpClient.Builder()
|
|
|
|
|
|
.addInterceptor(NetworkMonitorInterceptor())
|
|
|
|
|
|
.build()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 性能监控
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 性能指标收集
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用Firebase Performance
|
|
|
|
|
|
|
|
|
|
|
|
```gradle
|
|
|
|
|
|
// build.gradle
|
|
|
|
|
|
dependencies {
|
|
|
|
|
|
implementation 'com.google.firebase:firebase-perf-ktx:20.4.1'
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// 监控自定义性能指标
|
|
|
|
|
|
val trace = FirebasePerformance.getInstance().newTrace("load_user_data")
|
|
|
|
|
|
trace.start()
|
|
|
|
|
|
|
|
|
|
|
|
// 执行操作
|
|
|
|
|
|
loadUserData()
|
|
|
|
|
|
|
|
|
|
|
|
trace.stop()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 自定义性能监控
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
class PerformanceTracker {
|
|
|
|
|
|
private val metrics = mutableMapOf<String, Long>()
|
|
|
|
|
|
|
|
|
|
|
|
fun startTrace(name: String) {
|
|
|
|
|
|
metrics[name] = System.currentTimeMillis()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun endTrace(name: String) {
|
|
|
|
|
|
val startTime = metrics[name]
|
|
|
|
|
|
if (startTime != null) {
|
|
|
|
|
|
val duration = System.currentTimeMillis() - startTime
|
|
|
|
|
|
Log.d("Performance", "$name: ${duration}ms")
|
|
|
|
|
|
metrics.remove(name)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用
|
|
|
|
|
|
val tracker = PerformanceTracker()
|
|
|
|
|
|
tracker.startTrace("load_data")
|
|
|
|
|
|
// 执行操作
|
|
|
|
|
|
tracker.endTrace("load_data")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 性能分析工具
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用Android Profiler
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// Android Studio Profiler可以监控:
|
|
|
|
|
|
// - CPU使用率
|
|
|
|
|
|
// - 内存使用情况
|
|
|
|
|
|
// - 网络请求
|
|
|
|
|
|
// - 电量消耗
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用Systrace
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 使用Systrace分析性能
|
|
|
|
|
|
python systrace.py -t 10 -o trace.html sched freq idle am wm gfx view binder_driver hal dalvik camera input res
|
|
|
|
|
|
|
|
|
|
|
|
# 在代码中标记
|
|
|
|
|
|
Trace.beginSection("my_section")
|
|
|
|
|
|
// 执行代码
|
|
|
|
|
|
Trace.endSection()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用Perfetto
|
|
|
|
|
|
|
|
|
|
|
|
```kotlin
|
|
|
|
|
|
// Perfetto是更强大的性能分析工具
|
|
|
|
|
|
// 可以分析:
|
|
|
|
|
|
// - CPU调度
|
|
|
|
|
|
// - 内存分配
|
|
|
|
|
|
// - 文件I/O
|
|
|
|
|
|
// - 网络活动
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 总结
|
|
|
|
|
|
|
|
|
|
|
|
性能优化是一个持续的过程,需要:
|
|
|
|
|
|
|
|
|
|
|
|
1. **测量优先**:先测量性能,找出瓶颈
|
|
|
|
|
|
2. **优化关键路径**:优先优化影响用户体验的关键路径
|
|
|
|
|
|
3. **启动优化**:减少启动时间,提升用户体验
|
|
|
|
|
|
4. **内存优化**:避免内存泄漏,优化内存使用
|
|
|
|
|
|
5. **布局优化**:减少布局层级,避免过度绘制
|
|
|
|
|
|
6. **网络优化**:优化网络请求,使用缓存
|
|
|
|
|
|
7. **性能监控**:持续监控性能指标,及时发现问题
|
|
|
|
|
|
|
|
|
|
|
|
通过遵循这些最佳实践,可以显著提升应用的性能和用户体验。
|