作者: shijiusui

  • Android Activity 生命周期完全指南

    为什么需要理解生命周期?

    Android 系统的资源管理机制会导致 Activity 在用户无感知的情况下被销毁和重建。如果不正确处理生命周期,轻则丢失用户数据,重则导致应用崩溃。

    生命周期回调方法

    Activity 类提供了六个核心回调方法:

    • onCreate() — 系统创建 Activity 时调用。必须在此方法中执行初始化逻辑(如 setContentView),且只会调用一次。
    • onStart() — Activity 即将对用户可见时调用。
    • onResume() — Activity 开始与用户交互时调用。此时 Activity 位于栈顶。
    • onPause() — 系统即将启动或恢复另一个 Activity 时调用。应在此释放 CPU 密集型资源。
    • onStop() — Activity 对用户不再可见时调用。
    • onDestroy() — Activity 被销毁前调用。这是最后一个回调。

    常见场景的生命周期流转

    启动 Activity:onCreate → onStart → onResume

    按下 Home 键:onPause → onStop

    从 Home 返回:onRestart → onStart → onResume

    旋转屏幕:onPause → onStop → onDestroy → onCreate → onStart → onResume

    启动另一个 Activity(覆盖但不完全遮挡):onPause(新 Activity 主题为透明或 Dialog)

    onSaveInstanceState 的作用

    当 Activity 可能被系统意外销毁时(如旋转屏幕),系统会调用 onSaveInstanceState() 方法。你应该在此方法中将需要保存的临时数据存入 Bundle,然后在 onCreate() 或 onRestoreInstanceState() 中恢复。

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt("scrollPosition", recyclerView.computeVerticalScrollOffset())
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        savedInstanceState?.getInt("scrollPosition")?.let {
            recyclerView.scrollTo(0, it)
        }
    }

    最佳实践

    • onCreate 中只做必要的初始化,耗时操作放到后台线程。
    • onPause 中释放独占资源(如相机、传感器)。
    • onStop 中执行较重的清理工作(如保存数据到数据库)。
    • 始终考虑”Android 可能随时杀死你的进程”这一点来设计应用。
  • Jetpack Compose 入门:从 View 到 Composable 的思维转变

    什么是 Jetpack Compose?

    Jetpack Compose 是 Google 推出的现代化 Android UI 工具包,采用声明式编程范式。与传统的 XML + View 不同,Compose 让你用 Kotlin 代码直接描述 UI 应该是什么样子。

    声明式 vs 命令式

    传统 View 系统是命令式的:你先创建一个 TextView,然后在某个时机调用 setText() 来修改它。这个过程中状态和 UI 是分离的,你需要手动保持同步。

    Compose 是声明式的:你只需描述 UI 在给定状态下应该是什么样子。当状态变化时,框架会自动重新渲染受影响的部分。

    // 传统 View 方式
    val textView = findViewById(R.id.greeting)
    textView.text = "Hello"
    // 之后某处...
    textView.text = "World"
    
    // Compose 方式
    @Composable
    fun Greeting(name: String) {
        Text(text = "Hello ")
    }

    核心概念

    Composable 函数

    带有 @Composable 注解的普通 Kotlin 函数。它接收参数,描述界面的一部分:

    @Composable
    fun UserCard(user: User) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(text = user.name, style = MaterialTheme.typography.h6)
            Text(text = user.email, style = MaterialTheme.typography.body2)
        }
    }

    状态管理

    Compose 使用 remember 和 mutableStateOf 来管理组件内部状态:

    @Composable
    fun Counter() {
        var count by remember { mutableStateOf(0) }
        Button(onClick = { count++ }) {
            Text("Clicked  times")
        }
    }

    Modifier

    Modifier 用于修饰 Composable 的外观和行为——大小、边距、点击事件等:

    Text(
        text = "Hello",
        modifier = Modifier
            .padding(8.dp)
            .clickable { /* do something */ }
            .background(Color.Blue)
    )

    从 View 迁移的要点

    1. 布局:LinearLayout → Column/Row,FrameLayout → Box,ConstraintLayout 仍可用
    2. 列表:RecyclerView → LazyColumn / LazyRow
    3. 导航:Fragment + NavGraph → Compose Navigation
    4. 数据绑定:不再需要,状态驱动 UI 自动更新

    建议

    不要一次性重写整个项目。从新功能或独立页面开始尝试 Compose,逐步迁移。Compose 与 View 可以共存于同一个项目中。

  • Android 性能优化实战:启动速度、内存与渲染

    为什么性能优化如此重要?

    根据 Google 的研究,如果应用启动时间超过 5 秒,用户放弃率会急剧上升。一个流畅的应用不仅提升用户体验,还直接影响应用商店的评分和留存率。

    一、应用启动优化

    冷启动 vs 温启动 vs 热启动

    • 冷启动:应用进程被杀死后重新启动,耗时最长。
    • 温启动:Activity 被销毁但进程还在,可能是 trimMemory 导致的。
    • 热启动:Activity 仍驻留在内存中,最快。

    优化手段

    1. 延迟初始化:将非首屏必须的第三方 SDK 放到 IdleHandler 或协程中延迟加载:
      Looper.myLooper()?.queue?.addIdleHandler {
          HeavySdk.init()
          false // 只执行一次
      }
    2. 启动主题优化:使用 windowBackground 设置一个占位背景,避免白屏。
    3. 减少 Application.onCreate 的工作:只做最必要的初始化。

    二、内存优化

    常见内存泄漏场景

    • 静态变量持有 Activity 引用
    • 非静态内部类 / 匿名类隐式持有外部类引用
    • Handler 消息未处理完
    • 未取消的协程 / RxJava 订阅
    • 单例持有 Context 引用(应该用 ApplicationContext)

    检测工具

    • Android Studio Memory Profiler
    • LeakCanary:自动检测内存泄漏并给出引用链
    • MAT (Memory Analyzer Tool):分析 hprof 文件
    // LeakCanary 集成(仅需一行)
    dependencies {
        debugImplementation "com.squareup.leakcanary:leakcanary-android:2.12"
    }

    三、渲染优化

    关键指标:16ms 帧

    60fps 意味着每帧只有 16ms 的时间。任何超过这个时间的操作都会导致丢帧(jank)。

    优化技巧

    • 减少布局层级:使用 ConstraintLayout 替代嵌套的 LinearLayout。
    • 使用 ViewStub:对不常显示的 View 延迟初始化。
    • 避免过度绘制:在开发者选项中开启”显示过度绘制区域”。
    • RecyclerView 优化:使用 setHasFixedSize(true)、DiffUtil、预取。
    • 图片加载:使用 Glide/Coil,避免直接在 UI 线程解码位图。

    总结

    性能优化不是一蹴而就的。建议先通过 Profiler 找到瓶颈,再针对性优化。务必在优化前后做好数据对比,验证效果。

  • Android MVVM 架构实战:从理论到代码

    为什么要用架构?

    没有良好架构的 Android 应用,业务逻辑往往散布在 Activity/Fragment 中,导致这些类变成数千行的”上帝类”,难以测试和维护。MVVM 是 Google 官方推荐的架构模式,配合 Architecture Components 可以大幅提升代码质量。

    MVVM 三层结构

    • Model:数据层,负责从网络、数据库等数据源获取数据。通常包含 Repository 模式。
    • View:UI 层,由 Activity/Fragment/Composable 组成,负责展示数据和接收用户输入。
    • ViewModel:持有 UI 状态,处理业务逻辑,作为 View 和 Model 之间的桥梁。

    实战代码

    Model 层

    data class User(val id: Int, val name: String, val avatar: String)
    
    class UserRepository {
        private val api = RetrofitClient.userApi
        private val dao = AppDatabase.instance.userDao()
    
        suspend fun getUser(id: Int): User {
            // 先返回缓存,再请求网络
            val cached = dao.getUser(id)
            return try {
                val remote = api.getUser(id)
                dao.insertUser(remote)
                remote
            } catch (e: Exception) {
                cached ?: throw e
            }
        }
    }

    ViewModel 层

    class UserViewModel(
        private val repository: UserRepository
    ) : ViewModel() {
    
        private val _uiState = MutableStateFlow(UserUiState())
        val uiState: StateFlow = _uiState.asStateFlow()
    
        fun loadUser(id: Int) {
            viewModelScope.launch {
                _uiState.update { it.copy(isLoading = true) }
                try {
                    val user = repository.getUser(id)
                    _uiState.update { it.copy(user = user, isLoading = false) }
                } catch (e: Exception) {
                    _uiState.update { it.copy(error = e.message, isLoading = false) }
                }
            }
        }
    }
    
    data class UserUiState(
        val user: User? = null,
        val isLoading: Boolean = false,
        val error: String? = null
    )

    View 层

    @AndroidEntryPoint
    class UserFragment : Fragment() {
        private val viewModel: UserViewModel by viewModels()
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            viewModel.uiState
                .flowWithLifecycle(viewLifecycleOwner.lifecycle)
                .onEach { state -> render(state) }
                .launchIn(viewLifecycleOwner.lifecycleScope)
    
            viewModel.loadUser(1)
        }
    
        private fun render(state: UserUiState) {
            binding.progressBar.isVisible = state.isLoading
            state.user?.let { binding.nameText.text = it.name }
            state.error?.let { showToast(it) }
        }
    }

    核心原则

    1. 单一数据源:每种数据类型只有一个可信来源(通常是 Repository)。
    2. 单向数据流 (UDF):数据从 ViewModel 流向 View,事件从 View 流向 ViewModel。
    3. ViewModel 不持有 View 引用:避免内存泄漏,使用 StateFlow/LiveData 通信。
    4. 依赖注入:使用 Hilt/Koin 管理依赖,方便测试。

    推荐技术栈

    • 网络:Retrofit + OkHttp + Kotlin Coroutines
    • 数据库:Room
    • DI:Hilt(基于 Dagger)
    • 状态:StateFlow / LiveData
    • 测试:JUnit + MockK + Turbine
  • 深入理解 Android 的 Context:你用对了吗?

    Context 是什么?

    Context 是 Android 中最核心的抽象之一,它代表了一个应用环境的全局信息。几乎所有 Android 组件都需要 Context——启动 Activity、创建 View、访问资源、打开数据库,都离不开它。

    Context 的类型

    整个应用进程中存在两种 Context:

    • Application Context:与应用生命周期绑定。getApplicationContext() 返回。全局唯一。
    • Activity Context:与 Activity 生命周期绑定。this 或 getContext()(在 Activity 中)。
    // Application Context
    val appCtx = context.applicationContext
    // Activity Context
    val activityCtx = this  // 在 Activity 内部

    什么时候用哪个?

    场景 推荐 Context
    启动 Activity Activity Context(需要任务栈信息)
    显示 Dialog Activity Context
    Layout Inflation Activity Context(会使用正确主题)
    Toast Application Context
    初始化单例 Application Context(避免内存泄漏!)
    启动 Service Activity Context(Android 8.0+ 有限制)
    获取资源(string/color/drawable) 两者均可
    注册 BroadcastReceiver 两者均可,取决于生命周期需求

    常见错误

    1. 单例持有 Activity Context

    // ❌ 危险!Activity 无法被 GC
    object UserManager {
        lateinit var context: Context
    }
    
    // ✅ 正确做法
    object UserManager {
        lateinit var context: Application
        fun init(app: Application) {
            context = app
        }
    }

    2. 使用 Application Context 显示 Dialog

    // ❌ 会崩溃:Unable to add window — token null
    AlertDialog.Builder(applicationContext).show()
    
    // ✅ 使用 Activity Context
    AlertDialog.Builder(this).show()  // this 是 Activity

    3. 静态变量持有 View 引用

    // ❌ View 隐式持有 Activity Context
    companion object {
        var staticView: View? = null
    }

    Context 的继承关系

    Context 是一个抽象类,有两个直接子类:ContextWrapper 和 ContextImpl。Activity、Application、Service 都继承自 ContextWrapper,内部持有一个 ContextImpl 实例作为真正的实现。

    总结

    记住一条黄金法则:如果引用的生命周期比 Activity 长,就用 ApplicationContext,否则用 Activity Context。这样你就能避免大部分 Context 相关的内存泄漏问题。

  • 世界,您好!

    欢迎使用 WordPress。这是您的第一篇文章。编辑或删除它,然后开始写作吧!

沪ICP备2026038898号-1