前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >超过90%的Android开发都回答不全的性能优化面试题

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

作者头像
AntDream
发布2025-02-04 14:38:56
发布2025-02-04 14:38:56
10700
代码可运行
举报
运行总次数:0
代码可运行

如何优化RecyclerView的性能?

问题分析

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

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

优化方案与代码示例

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

代码语言:javascript
代码运行次数:0
运行
复制
// 定义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为例)

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

3. 解决嵌套滑动冲突

  • 自定义NestedScrollView:重写onInterceptTouchEvent
  • 使用RecyclerView.setRecycledViewPool():共享子RecyclerView的缓存池
代码语言:javascript
代码运行次数:0
运行
复制
// 父RecyclerView中设置共享池
val sharedPool = RecyclerView.RecycledViewPool()
childRecyclerView1.setRecycledViewPool(sharedPool)
childRecyclerView2.setRecycledViewPool(sharedPool)

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

问题分析

内存泄漏常见场景:

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

解决方案与工具

1. 使用LeakCanary自动检测

集成步骤

代码语言:javascript
代码运行次数:0
运行
复制
dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}
  • 输出结果:LeakCanary会自动生成泄漏链,显示泄漏对象引用路径。

2. Handler内存泄漏解决方案

  • 静态内部类 + WeakReference
代码语言:javascript
代码运行次数:0
运行
复制
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. 匿名内部类泄漏案例

代码语言:javascript
代码运行次数:0
运行
复制
// 错误示例:匿名Runnable持有Activity引用
object : Runnable {
    override fun run() {
        // 直接访问Activity方法导致泄漏
        updateUI() 
    }
}

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

如何优化冷启动时间?

优化阶段分析

冷启动耗时主要分布在:

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

优化代码实践

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

代码语言:javascript
代码运行次数:0
运行
复制
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        // 主线程只初始化必要组件
        initCoreComponents()

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

2. 启动主题优化

  • styles.xml
代码语言:javascript
代码运行次数:0
运行
复制
<style name="AppTheme.Launcher">
    <item name="android:windowBackground">@drawable/launch_background</item>
</style>
  • AndroidManifest.xml
代码语言:javascript
代码运行次数:0
运行
复制
<activity 
    android:name=".MainActivity"
    android:theme="@style/AppTheme.Launcher"> <!-- 启动时使用轻量主题 -->
</activity>
  • Activity中恢复主题
代码语言:javascript
代码运行次数:0
运行
复制
override fun onCreate(savedInstanceState: Bundle?) {
    setTheme(R.style.AppTheme) // 恢复正常主题
    super.onCreate(savedInstanceState)
}

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

代码语言:javascript
代码运行次数:0
运行
复制
// 定义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. 主线程耗时操作迁移到协程

代码语言:javascript
代码运行次数:0
运行
复制
// 错误示例:在主线程执行数据库查询
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替代多层嵌套)

  • 原始布局(低效)
代码语言:javascript
代码运行次数:0
运行
复制
<LinearLayout>
    <LinearLayout>
        <ImageView />
        <TextView />
    </LinearLayout>
    <LinearLayout>...</LinearLayout>
</LinearLayout>
  • 优化后布局
代码语言:javascript
代码运行次数:0
运行
复制
<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)

代码语言:javascript
代码运行次数:0
运行
复制
android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

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

代码语言:javascript
代码运行次数:0
运行
复制
plugins {
    id 'com.tencent.mm.andresguard'
}

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

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

  • build.gradle配置
代码语言:javascript
代码运行次数:0
运行
复制
splits {
    abi {
        enable true
        reset()
        include 'armeabi-v7a', 'arm64-v8a'
        universalApk false
    }
}
  • 运行时加载
代码语言:javascript
代码运行次数:0
运行
复制
// 从服务器下载对应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调度任务

代码语言:javascript
代码运行次数:0
运行
复制
// 定义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)

代码语言:javascript
代码运行次数:0
运行
复制
// 使用RxJava的zip操作符合并多个请求
Observable.zip(
    apiService.getUserInfo(),
    apiService.getNotifications(),
    BiFunction { user, notifications -> 
        CombinedData(user, notifications) 
    }
).subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe { data -> updateUI(data) }
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-02-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AntDream 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何优化RecyclerView的性能?
    • 问题分析
    • 优化方案与代码示例
  • 如何定位和解决内存泄漏?
    • 问题分析
    • 解决方案与工具
  • 如何优化冷启动时间?
    • 优化阶段分析
    • 优化代码实践
  • 如何解决UI卡顿(掉帧)问题?
    • 性能分析工具链
    • 优化实践
  • 如何优化APK体积?
    • APK组成分析
    • 分阶段优化方案
  • 如何优化电池续航?
    • 耗电场景分析
    • 优化策略与代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档