首页 Android 正文
  • 本文约4548字,阅读需23分钟
  • 120
  • 0

超过90%的Android开发都回答不全的性能优化面试题

摘要

如何优化RecyclerView的性能? 问题分析 RecyclerView卡顿通常由以下原因导致: 频繁创建/销毁ViewHolder onBindViewHolder执行耗时操作 图片加载未优化 嵌套滑动冲突 优化方案与代码示例 1. 使用DiffUtil进行增量更新原理:DiffUtil通过比较新旧数据集差异,仅更新变化的Item,避免全局刷新...

如何优化RecyclerView的性能?

问题分析

RecyclerView卡顿通常由以下原因导致:

  • 频繁创建/销毁ViewHolder
  • onBindViewHolder执行耗时操作
  • 图片加载未优化
  • 嵌套滑动冲突

优化方案与代码示例

1. 使用DiffUtil进行增量更新**原理**:DiffUtil通过比较新旧数据集差异,仅更新变化的Item,避免全局刷新。


// 定义DiffCallback
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(oldPos: Int, newPos: Int) = 
        oldList[oldPos].id == newList[newPos].id
    override fun areContentsTheSame(oldPos: Int, newPos: Int) = 
        oldList[oldPos] == newList[newPos]
}

// 使用DiffResult更新Adapter
val diffResult = DiffUtil.calculateDiff(UserDiffCallback(oldUsers, newUsers))
diffResult.dispatchUpdatesTo(adapter)
adapter.submitList(newUsers) // 更新数据

2. 优化图片加载(以Glide为例)

  • 限制图片尺寸:避免加载原图
  • 启用内存/磁盘缓存
Glide.with(context)
    .load(imageUrl)
    .override(300, 300) // 根据Item大小调整
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .into(imageView)

3. 解决嵌套滑动冲突

  • 自定义NestedScrollView:重写onInterceptTouchEvent
  • 使用RecyclerView.setRecycledViewPool():共享子RecyclerView的缓存池
// 父RecyclerView中设置共享池
val sharedPool = RecyclerView.RecycledViewPool()
childRecyclerView1.setRecycledViewPool(sharedPool)
childRecyclerView2.setRecycledViewPool(sharedPool)

如何定位和解决内存泄漏?

问题分析

内存泄漏常见场景:

  • Activity被静态引用(如单例、匿名内部类)
  • 未取消的RxJava订阅、Handler消息
  • 监听器未反注册

解决方案与工具

1. 使用LeakCanary自动检测

集成步骤

dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}

输出结果:LeakCanary会自动生成泄漏链,显示泄漏对象引用路径。

2. Handler内存泄漏解决方案

静态内部类 + WeakReference


class SafeHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {
    private val weakActivity = WeakReference(activity)

    override fun handleMessage(msg: Message) {
        weakActivity.get()?.run {
            // 操作UI
            textView.text = "Updated"
        }
    }
}

// 在Activity中清理消息
override fun onDestroy() {
    handler.removeCallbacksAndMessages(null)
    super.onDestroy()
}

3. 匿名内部类泄漏案例

// 错误示例:匿名Runnable持有Activity引用
object : Runnable {
    override fun run() {
        // 直接访问Activity方法导致泄漏
        updateUI() 
    }
}

// 正确做法:使用弱引用或分离生命周期
class SafeRunnable(activity: WeakReference<MainActivity>) : Runnable {
    override fun run() {
        activity.get()?.updateUI()
    }
}

如何优化冷启动时间?

优化阶段分析

冷启动耗时主要分布在:

  • Application初始化:第三方库初始化
  • 首帧渲染:主题背景绘制、布局复杂度

优化代码实践

1. 异步初始化(使用协程)


class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        // 主线程只初始化必要组件
        initCoreComponents()

        // 异步初始化非关键组件
        CoroutineScope(Dispatchers.IO).launch {
            initAnalytics() // 如Firebase
            initCrashReporting() // 如Sentry
        }
    }
}

2. 启动主题优化

styles.xml

<style name="AppTheme.Launcher">
    <item name="android:windowBackground">@drawable/launch_background</item>
</style>

AndroidManifest.xml

<activity 
    android:name=".MainActivity"
    android:theme="@style/AppTheme.Launcher"> <!-- 启动时使用轻量主题 -->
</activity>

Activity中恢复主题

override fun onCreate(savedInstanceState: Bundle?) {
    setTheme(R.style.AppTheme) // 恢复正常主题
    super.onCreate(savedInstanceState)
}

3. 使用App Startup库集中初始化

// 定义Initializer
class FirebaseInitializer : Initializer<FirebaseApp> {
    override fun create(context: Context): FirebaseApp {
        return Firebase.initialize(context)
    }
    override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}
// 在AndroidManifest中配置
<provider 
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup">
    <meta-data 
        android:name="com.example.FirebaseInitializer"
        android:value="androidx.startup" />
</provider>

如何解决UI卡顿(掉帧)问题?

性能分析工具链

  • Systrace:分析UI线程和RenderThread的耗时
  • Perfetto:跟踪CPU调度、锁竞争等系统级问题

优化实践

1. 主线程耗时操作迁移到协程

// 错误示例:在主线程执行数据库查询
fun loadData() {
    val data = database.query() // 阻塞主线程
    updateUI(data)
}

// 正确做法:使用协程
fun loadData() {
    CoroutineScope(Dispatchers.Main).launch {
        val data = withContext(Dispatchers.IO) { 
            database.query() 
        }
        updateUI(data)
    }
}

2. 布局优化(ConstraintLayout替代多层嵌套)

原始布局(低效)

<LinearLayout>
    <LinearLayout>
        <ImageView />
        <TextView />
    </LinearLayout>
    <LinearLayout>...</LinearLayout>
</LinearLayout>

优化后布局

<androidx.constraintlayout.widget.ConstraintLayout>
    <ImageView app:layout_constraintTop_toTopOf="parent" .../>
    <TextView app:layout_constraintStart_toEndOf="@id/image" .../>
</androidx.constraintlayout.widget.ConstraintLayout>

3. 避免过度绘制

  • 开启调试工具:开发者选项 → 显示过度绘制
  • 优化策略
    移除不必要的背景
    使用canvas.clipRect()限制绘制区域

如何优化APK体积?

APK组成分析

  • 代码:Java/Kotlin编译后的DEX
  • 资源:图片、XML、字体等
  • 本地库:armeabi-v7a/arm64-v8a等SO文件

分阶段优化方案

1. 代码混淆与优化(R8)

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

2. 资源混淆与压缩(AndResGuard)

plugins {
    id 'com.tencent.mm.andresguard'
}

andResGuard {
    mappingFile = null
    use7zip = true
    keepRoot = false
    whiteList = ["R.drawable.icon"] // 保留不混淆的资源
}

3. 动态下发SO库(ABI拆分 + 动态加载)

build.gradle配置

splits {
    abi {
        enable true
        reset()
        include 'armeabi-v7a', 'arm64-v8a'
        universalApk false
    }
}

运行时加载

// 从服务器下载对应ABI的SO文件
val abi = Build.SUPPORTED_ABIS[0]
downloadSoFile("https://example.com/${abi}/libnative.so")

// 加载自定义路径SO
System.load("/data/data/com.example/app_lib/libnative.so")

如何优化电池续航?

耗电场景分析

  • 频繁网络请求:移动无线电激活状态耗电
  • WakeLock滥用:阻止设备进入休眠
  • 后台定位:GPS持续工作

优化策略与代码

1. 使用WorkManager调度任务

// 定义Worker
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // 执行同步任务
        return Result.success()
    }
}

// 配置周期性任务(每6小时,仅在充电+网络连接时执行)
val constraints = Constraints.Builder()
    .setRequiresCharging(true)
    .setRequiredNetworkType(NetworkType.UNMETERED)
    .build()

val request = PeriodicWorkRequestBuilder<SyncWorker>(6, TimeUnit.HOURS)
    .setConstraints(constraints)
    .build()

WorkManager.getInstance(context).enqueue(request)

2. 合并网络请求(Retrofit + RxJava)

// 使用RxJava的zip操作符合并多个请求
Observable.zip(
    apiService.getUserInfo(),
    apiService.getNotifications(),
    BiFunction { user, notifications -> 
        CombinedData(user, notifications) 
    }
).subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe { data -> updateUI(data) }

扫描二维码,在手机上阅读
    评论