Android编程实例分析从Activity生命周期到MVVM架构实战解决内存泄漏和界面卡顿常见问题的完整代码教程
先聊聊Activity的”一生”
想象一下,Activity就像一个员工去上班。早上打卡(onCreate),开始干活(onStart),真正面向客户(onResume)。中途老板喊你离开一下(onPause),客户走了但你还在工位(onStop),最后打卡下班(onDestroy)。
但Android这个老板特别严格,有时候还没等你打完卡就把你辞了——这就是我们后面要讲的内存泄漏问题。
先看最基本的生命周期:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 这是Activity被创建的时候
// 通常在这里初始化View、绑定数据
Log.d("Lifecycle", "onCreate - 新员工入职")
// 保存界面状态,比如用户在输入框写的字
savedInstanceState?.let {
val userName = it.getString("user_name")
editTextUsername.setText(userName)
}
}
override fun onStart() {
super.onStart()
Log.d("Lifecycle", "onStart - 准备营业")
// Activity即将可见,但还没焦点
}
override fun onResume() {
super.onResume()
Log.d("Lifecycle", "onResume - 正式营业中")
// 此时用户可以看到并与界面交互
// 适合启动动画、音乐等需要前台运行的东西
startBackgroundMusic()
}
override fun onPause() {
super.onPause()
Log.d("Lifecycle", "onPause - 老板喊你开会")
// 切换到另一个Activity时首先调用
// 适合保存数据、暂停动画
stopBackgroundMusic()
saveCurrentState()
}
override fun onStop() {
super.onStop()
Log.d("Lifecycle", "onStop - 门店关门")
// Activity完全不可见
// 适合释放重量级资源
}
override fun onDestroy() {
super.onDestroy()
Log.d("Lifecycle", "onDestroy - 打烊下班")
// 清理所有资源
}
override fun onRestart() {
super.onRestart()
Log.d("Lifecycle", "onRestart - 重新开门营业")
// 从onStop状态恢复时调用
}
}
很多新手容易踩的坑: 在onResume里开启了相机预览,却忘了在onPause里释放它。结果就是App切到后台,相机还占着摄像头,用户打开系统相机就会报错。
内存泄漏——那些”赖着不走”的对象
内存泄漏的本质就是:该死的没死,不该占的地方占了。
经典案例一:静态变量引用
// ❌ 错误示范 - 典型的内存泄漏
class MainActivity : AppCompatActivity() {
companion object {
// 千万别这么写!静态引用会让Activity对象永远无法被GC回收
var instance: MainActivity? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 这把MainActivity的引用存到了静态变量里
// Activity销毁后,这个引用还在,内存就泄漏了
instance = this
}
}
// ✅ 正确做法 - 使用弱引用或者直接不用
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 通过ViewModel管理数据,不手动存引用
viewModel = ViewModelProvider(this)[MainViewModel::class.java]
}
override fun onDestroy() {
super.onDestroy()
// ViewModel会自动清理,不需要手动置空
}
}
经典案例二:Handler和匿名内部类
// ❌ 错误示范 - Handler持有Activity的隐式引用
class MainActivity : AppCompatActivity() {
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// 这个匿名内部类隐式持有了MainActivity的引用
// 即使Activity销毁了,Handler里的消息队列还有消息的话
// MainActivity就永远不会被回收
updateUI(msg.obj as String)
}
}
fun startLoading() {
handler.postDelayed({
fetchDataFromNetwork()
}, 1000 * 60) // 延迟一分钟
}
// 如果用户在一秒钟内就关闭了页面...
// 这个延迟任务还悬在空中,Activity就泄漏了
}
// ✅ 正确做法 - 使用静态内部类 + 弱引用
class MainActivity : AppCompatActivity() {
// 静态内部类不持有外部类的隐式引用
private inner class MyHandler : Handler(Looper.getMainLooper()) {
// 用WeakReference包装Activity引用
private val activityRef: WeakReference<MainActivity> by lazy {
WeakReference(this@MainActivity)
}
override fun handleMessage(msg: Message) {
// 取出来之前先检查Activity还在不在
val activity = activityRef.get()
if (activity != null) {
activity.updateUI(msg.obj as String)
}
// 如果activity已经销毁了,就什么都不做,也不会泄漏
}
}
private val handler = MyHandler()
override fun onDestroy() {
super.onDestroy()
// 主动清除所有 pending 的消息,防止泄漏
handler.removeCallbacksAndMessages(null)
}
}
// ✅ 更现代的写法 - 用Lifecycle-aware的方式
class MainActivity : AppCompatActivity() {
private val job = Job()
private val scope = LifecycleScope + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 协程自动跟随Lifecycle,页面销毁时自动取消
scope.launch {
delay(1000 * 60)
if (isActive) { // 检查协程是否还活着
fetchDataFromNetwork()
}
}
}
override fun onDestroy() {
super.onDestroy()
// LifecycleScope销毁时,所有协程自动取消
// 比Handler靠谱多了
}
}
经典案例三:匿名类监听器未移除
// ❌ 危险代码
class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
// 每次都new一个匿名类,而且从来没移除过!
locationProvider.addLocationListener(object : LocationListener {
override fun onLocationChanged(location: Location) {
// 这个匿名类持有MainActivity引用
updateLocationText(location.latitude.toString())
}
})
}
// 用户每切回这个页面一次,就多一个监听器
// 5个页面切换 = 5个监听器同时工作 = 内存泄漏
}
// ✅ 正确做法
class MainActivity : AppCompatActivity() {
private var locationListener: LocationListener? = null
override fun onResume() {
super.onResume()
if (locationListener == null) {
locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
updateLocationText(location.latitude.toString())
}
}
}
locationProvider.addLocationListener(locationListener!!)
}
override fun onPause() {
super.onPause()
locationListener?.let {
locationProvider.removeLocationListener(it)
}
// 下次onResume时可以复用,不用每次new新的
}
}
经典案例四:Bitmap不回收
// ❌ 加载大图不处理,OOM的直接原因
class MainActivity : AppCompatActivity() {
fun loadImageSimple(url: String) {
// 这样加载大图,内存直接爆炸
val bitmap = BitmapFactory.decodeStream(url.openStream())
imageView.setImageBitmap(bitmap)
}
}
// ✅ 正确做法 - 使用Glide或Picasso
// 这些库帮你处理了缓存、回收、尺寸缩放
Glide.with(this)
.load(url)
.override(800, 600) // 限制加载尺寸
.diskCacheStrategy(DiskCacheStrategy.ALL) // 启用磁盘缓存
.into(imageView)
// 或者手动解码大图时
fun loadImageResponsible(url: String) {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true // 先只解码边框信息,不加载像素数据
}
BitmapFactory.decodeStream(url.openStream(), null, options)
// 计算合适的采样率
val scale = calculateInSampleSize(options, 800, 600)
options.inSampleSize = scale
options.inJustDecodeBounds = false // 现在真正解码
val bitmap = BitmapFactory.decodeStream(url.openStream(), null, options)
imageView.setImageBitmap(bitmap)
}
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val (height, width) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while ((halfHeight / inSampleSize) >= reqHeight &&
(halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
MVVM架构——让代码干净得像刚拖过的地
为什么需要MVVM?
先看看不做MVVM时代码有多乱:
// 这是多少人的噩梦?一个Activity 2000行代码
class MessyActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var userList: MutableList<User> = mutableListOf()
private var isLoading = false
private var page = 1
private var layoutManager: LinearLayoutManager? = null
private var adapter: UserAdapter? = null
private var searchJob: Job? = null
// 各种View的引用满天飞
private lateinit var refreshLayout: SwipeRefreshLayout
private lateinit var recyclerView: RecyclerView
private lateinit var searchEditText: EditText
private lateinit var emptyView: TextView
private lateinit var progressBar: ProgressBar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 初始化各种View
refreshLayout = binding.refreshLayout
recyclerView = binding.recyclerView
searchEditText = binding.searchEditText
emptyView = binding.emptyView
progressBar = binding.progressBar
// 业务逻辑全部堆在这里...
setupRecyclerView()
setupRefreshLayout()
setupSearch()
loadData()
observeUiState()
}
private fun setupRecyclerView() {
layoutManager = LinearLayoutManager(this)
adapter = UserAdapter(userList) { user ->
// 点击事件
navigateToUserDetail(user)
}
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
}
private fun setupRefreshLayout() {
refreshLayout.setOnRefreshListener {
page = 1
userList.clear()
loadData()
}
}
private fun setupSearch() {
searchEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
// 防抖搜索
searchJob?.cancel()
searchJob = lifecycleScope.launch {
delay(300)
performSearch(s?.toString() ?: "")
}
}
})
}
private fun loadData() {
isLoading = true
showLoading()
viewModel.fetchUsers(page).observe(this) { result ->
isLoading = false
refreshLayout.isRefreshing = false
when (result) {
is Resource.Success -> {
hideLoading()
userList.addAll(result.data)
adapter?.notifyDataSetChanged()
}
is Resource.Error -> {
showError(result.message)
}
is Resource.Loading -> {
showLoading()
}
}
}
}
// 还有500行类似的代码...
}
这就是典型的”上帝Activity”,所有逻辑、UI、数据全混在一起。
用MVVM重构
// ===== ViewModel层:只关心数据,不关心UI =====
class MainViewModel : ViewModel() {
// 用StateFlow管理UI状态——线程安全且Lifecycle感知
private val _uiState = MutableStateFlow<MainUiState>(MainUiState.Idle)
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
// 业务事件(单向流动,不会被重复触发)
private val _events = Channel<MainEvent>()
val events: Channel<MainEvent> = _events
// 用户数据
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
// 分页相关
private var currentPage = 1
private var hasNextPage = true
private var isLoading = false
// 搜索词
private var currentSearch = ""
/**
* 加载用户列表
*/
fun loadUsers(refresh: Boolean = false) {
if (isLoading) return
if (!refresh && !hasNextPage) return
isLoading = true
_uiState.value = MainUiState.Loading
viewModelScope.launch {
try {
val result = repository.fetchUsers(
page = if (refresh) 1 else currentPage,
search = currentSearch
)
when (result) {
is Resource.Success -> {
val newUsers = result.data
if (refresh) {
_users.value = newUsers
currentPage = 1
} else {
_users.value = _users.value + newUsers
currentPage++
}
hasNextPage = newUsers.isNotEmpty()
_uiState.value = MainUiState.Success
}
is Resource.Error -> {
_uiState.value = MainUiState.Error(result.message)
_events.send(MainEvent.ShowError(result.message))
}
is Resource.Loading -> {
_uiState.value = MainUiState.Loading
}
}
} catch (e: Exception) {
_uiState.value = MainUiState.Error(e.message ?: "未知错误")
} finally {
isLoading = false
}
}
}
/**
* 搜索
*/
fun search(query: String) {
currentSearch = query
loadUsers(refresh = true)
}
/**
* 下拉刷新
*/
fun refresh() {
loadUsers(refresh = true)
}
/**
* 上拉加载更多
*/
fun loadMore() {
loadUsers(refresh = false)
}
/**
* 用户点击
*/
fun onUserClick(user: User) {
viewModelScope.launch {
_events.send(MainEvent.NavigateToUserDetail(user.id))
}
}
override fun onCleared() {
super.onCleared()
// ViewModel销毁时自动取消所有协程,不会有内存泄漏
// 这是ViewModel比Repository更高级的地方
}
}
// ===== UI状态数据类 =====
sealed class MainUiState {
object Idle : MainUiState()
object Loading : MainUiState()
data class Success(val users: List<User> = emptyList()) : MainUiState()
data class Error(val message: String) : MainUiState()
}
// ===== 业务事件(一次性事件,不用StateFlow) =====
sealed class MainEvent {
data class ShowError(val message: String) : MainEvent()
data class NavigateToUserDetail(val userId: String) : MainEvent()
}
// ===== Repository层 =====
class UserRepository @Inject constructor(
private val apiService: UserServiceApi
) {
suspend fun fetchUsers(page: Int, search: String): Resource<List<User>> {
return try {
val response = apiService.getUsers(page = page, search = search)
if (response.isSuccessful && response.body() != null) {
Resource.Success(response.body()!!)
} else {
Resource.Error("请求失败: ${response.code()}")
}
} catch (e: Exception) {
Resource.Error(e.message ?: "网络异常")
}
}
}
sealed class Resource<out T> {
data class Success<out T>(val data: T) : Resource<T>()
data class Error(val message: String) : Resource<*>()
object Loading : Resource<Nothing>()
}
// ===== Activity层:只做UI绑定 =====
class MainActivity : AppCompatActivity() {
// 依赖注入(Hilt)
@Inject
lateinit var viewModel: MainViewModel
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupRecyclerView()
observeViewModel()
setupSwipeRefresh()
setupSearch()
// 初始加载
viewModel.loadUsers(refresh = true)
}
private fun setupRecyclerView() {
val adapter = UserAdapter(
onItemClick = { viewModel.onUserClick(it) }
)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
this.adapter = adapter
// 上拉加载更多
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val lm = layoutManager as LinearLayoutManager
if (newState == RecyclerView.SCROLL_STATE_IDLE &&
!lm.findLastCompletelyVisibleItemPosition().let {
it == adapter.itemCount - 1 && it != RecyclerView.NO_POSITION
}
) {
viewModel.loadMore()
}
}
})
}
// 用StateFlow的collectAsLazyList让列表响应式更新
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.users.collect { users ->
adapter.submitList(users)
}
}
}
}
private fun observeViewModel() {
// 观察UI状态
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is MainUiState.Loading -> showLoading()
is MainUiState.Success -> {
hideLoading()
showEmptyView(state.users.isEmpty())
}
is MainUiState.Error -> {
hideLoading()
showError(state.message)
}
is MainUiState.Idle -> {}
}
}
}
}
// 观察一次性事件(用flow.first收集,避免重复触发)
lifecycleScope.launch {
viewModel.events Flow { event ->
when (event) {
is MainEvent.ShowError -> showErrorDialog(event.message)
is MainEvent.NavigateToUserDetail ->
navigateToUserDetail(event.userId)
}
}
}
}
private fun setupSwipeRefresh() {
binding.refreshLayout.setOnRefreshListener {
viewModel.refresh()
}
}
private fun setupSearch() {
var searchJob: Job? = null
binding.searchEditText.doOnTextChanged { text, _, _, _ ->
searchJob?.cancel()
searchJob = lifecycleScope.launch {
delay(300) // 防抖
viewModel.search(text?.toString() ?: "")
}
}
}
// 各种UI操作方法
private fun showLoading() { ... }
private fun hideLoading() { ... }
private fun showError(message: String) { ... }
private fun showEmptyView(isEmpty: Boolean) { ... }
private fun showErrorDialog(message: String) { ... }
private fun navigateToUserDetail(userId: String) { ... }
override fun onDestroy() {
super.onDestroy()
_binding = null // 防止Activity引用泄漏
}
}
界面卡顿——从入门到精通的排查指南
卡顿的原因就两个:主线程太忙 和 布局太复杂。
卡顿的根源分析
主线程(UI线程)在做什么?
├── 绘制界面(onDraw)
├── 处理用户输入(onClick、onTouch)
├── 执行动画
└── 其他业务逻辑(这是问题的根源!)
Android要求每16ms完成一帧(60fps)
如果某次操作超过16ms,就会掉帧 → 卡顿
常见卡顿场景及解决方案
场景一:列表滑动卡顿
// ❌ 常见的错误写法
class BadAdapter : RecyclerView.Adapter<BadAdapter.BadViewHolder>() {
inner class BadViewHolder(view: View) : ViewHolder(view) {
val imageView: ImageView = view.findViewById(R.id.item_image)
val textView: TextView = view.findViewById(R.id.item_text)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BadViewHolder {
// 每次创建ViewHolder都 inflate,浪费性能
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return BadViewHolder(view)
}
override fun onBindViewHolder(holder: BadViewHolder, position: Int) {
val user = users[position]
// 直接在主线程做耗时操作!
val data = complexCalculation(user) // 这可能很耗时
holder.textView.text = data
// 用 Picasso/Glide 但不指定大小
Glide.with(holder.imageView.context)
.load(user.avatarUrl)
.into(holder.imageView)
// 没有用 ViewHolder pattern,每次 findViewById
val extraView = holder.itemView.findViewById(R.id.extra_view)
extraView.visibility = if (user.isOnline) View.VISIBLE else View.GONE
}
// 复杂的计算在主线程执行
private fun complexCalculation(user: User): String {
var result = ""
for (i in 0 until 100000) {
result += user.name + i
}
return result
}
}
// ✅ 正确的写法
class GoodAdapter(
private val users: List<User>,
private val onItemClick: (User) -> Unit
) : RecyclerView.Adapter<GoodAdapter.GoodViewHolder>() {
inner class GoodViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val imageView: ImageView = view.findViewById(R.id.item_image)
val textView: TextView = view.findViewById(R.id.item_text)
val onlineIndicator: View = view.findViewById(R.id.online_indicator)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GoodViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return GoodViewHolder(view)
}
override fun onBindViewHolder(holder: GoodViewHolder, position: Int) {
val user = users[position]
// 简单赋值,不做复杂计算
holder.textView.text = user.name
holder.onlineIndicator.visibility =
if (user.isOnline) View.VISIBLE else View.GONE
// Glide加载图片,自动在后台线程处理
Glide.with(holder.imageView.context)
.load(user.avatarUrl)
.override(100, 100) // 指定尺寸,避免大图加载
.circleCrop()
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(holder.imageView)
holder.itemView.setOnClickListener {
onItemClick(user)
}
}
// 复杂计算放在后台线程
fun getFormattedUsers(): List<String> {
return users.map { user ->
// 用coroutineScope在合适的地方做计算
user.name
}
}
}
场景二:布局嵌套过深
❌ 错误的布局(5层嵌套):
<CoordinatorLayout>
└── <LinearLayout> (orientation="vertical")
└── <FrameLayout>
└── <ScrollView>
└── <LinearLayout> (orientation="vertical")
└── <TextView>
└── <ImageView>
└── <RecyclerView>
└── ...(还有10个子View)
ScrollView嵌套RecyclerView = 灾难!两个滚动容器互相干扰
<!-- ✅ 优化后的布局 -->
<!-- 使用ConstraintLayout替代多层嵌套 -->
<!-- 用include复用公共部分 -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 下拉刷新容器 -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- RecyclerView直接放在SwipeRefreshLayout里 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="8dp"
android:paddingBottom="16dp"
app:layoutManager="LinearLayoutManager" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- 空状态用ViewStub延迟加载 -->
<ViewStub
android:id="@+id/empty_view_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inflatedId="@+id/empty_view"
android:layout="@layout/view_empty_state"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
场景三:不必要的重绘
// ❌ 频繁改变View属性导致重绘
class BadExampleActivity : AppCompatActivity() {
private lateinit var progressBar: ProgressBar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bad_example)
progressBar = findViewById(R.id.progress_bar)
// 每16ms更新一次,不管用户是否在看
// 即使不可见也在运行!
val animation = object : ValueAnimator() {
init {
duration = 1000
repeatCount = ValueAnimator.INFINITE
addUpdateListener { animation ->
progressBar.progress = animation.animatedValue as Int
}
}
}
animation.start()
}
}
// ✅ 只在可见时动画
class GoodExampleActivity : AppCompatActivity() {
private lateinit var progressBar: ProgressBar
private var animator: ValueAnimator? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_good_example)
progressBar = findViewById(R.id.progress_bar)
}
override fun onResume() {
super.onResume()
// 页面可见时才启动动画
startProgressAnimation()
}
override fun onPause() {
super.onPause()
// 页面不可见时停止动画
stopProgressAnimation()
}
private fun startProgressAnimation() {
animator = ValueAnimator.ofInt(0, 100).apply {
duration = 2000
repeatCount = ValueAnimator.INFINITE
addUpdateListener { animation ->
progressBar.progress = animation.animatedValue as Int
}
}
animator?.start()
}
private fun stopProgressAnimation() {
animator?.cancel()
animator = null
}
}
场景四:在主线程做网络请求/数据库操作
// ❌ 致命错误 - 主线程网络请求
class BadNetworkActivity : AppCompatActivity() {
private lateinit var binding: ActivityNetworkBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityNetworkBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnLoad.setOnClickListener {
// 这会直接导致NetworkOnMainThreadException
// 即使不抛异常,也会卡死UI至少几百毫秒
val result = makeNetworkCall() // 主线程!
binding.tvResult.text = result
}
}
private fun makeNetworkCall(): String {
// 模拟网络请求
Thread.sleep(2000) // 卡死2秒!
return "Hello from server"
}
}
// ✅ 使用协程在后台线程请求
class GoodNetworkActivity : AppCompatActivity() {
private lateinit var binding: ActivityNetworkBinding
private lateinit var viewModel: NetworkViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityNetworkBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this)[NetworkViewModel::class.java]
binding.btnLoad.setOnClickListener {
viewModel.fetchData()
}
// 观察结果
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.result.collect { result ->
when (result) {
is Resource.Loading -> binding.progressBar.visibility = View.VISIBLE
is Resource.Success -> {
binding.progressBar.visibility = View.GONE
binding.tvResult.text = result.data
}
is Resource.Error -> {
binding.progressBar.visibility = View.GONE
binding.tvResult.text = "错误: ${result.message}"
}
}
}
}
}
}
}
// ViewModel里处理网络请求
class NetworkViewModel : ViewModel() {
private val _result = MutableStateFlow<Resource<String>>(Resource.Idle)
val result: StateFlow<Resource<String>> = _result
fun fetchData() {
viewModelScope.launch {
_result.value = Resource.Loading
try {
// 网络请求自动在IO线程执行
val data = withContext(Dispatchers.IO) {
apiService.getData()
}
_result.value = Resource.Success(data)
} catch (e: Exception) {
_result.value = Resource.Error(e.message ?: "未知错误")
}
}
}
}
完整的性能优化工具使用指南
用Android Studio Profiler诊断问题
// 在代码里埋点,辅助排查
class PerformanceTracker {
companion object {
private val tag = "PerfTracker"
/**
* 耗时方法检测
*/
fun <T> profile(methodName: String, block: () -> T): T {
val start = SystemClock.elapsedRealtime()
val result = block()
val duration = SystemClock.elapsedRealtime() - start
if (duration > 16) { // 超过一帧的时间
Log.w(tag, "$methodName 耗时 ${duration}ms")
}
return result
}
/**
* 检测内存分配热点
*/
fun allocateAndTrack(tag: String, size: Int) {
val bytes = ByteArray(size)
// 频繁分配大数组会导致GC频繁,引起卡顿
// 用Profiler的Memory监控可以看到分配频率
return bytes
}
}
}
// 使用示例
fun loadDataFromNetwork() {
PerformanceTracker.profile("fetchData") {
// 实际的网络请求
apiService.getData()
}
}
布局调试技巧
// 在Activity里开启布局调试信息
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 这个方法可以在代码里动态开启,也可以在adb里开启
// adb shell setprop debug.layout true
WindowInsetsControllerCompat(window, window.decorView)
.isAppearanceLightStatusBars = true
// 检查布局层级
binding.root.viewTreeObserver.addOnGlobalLayoutListener {
val depth = countViewDepth(binding.root)
if (depth > 10) {
Log.w("LayoutDepth", "布局层级过深: $depth 层")
}
}
}
private fun countViewDepth(view: View): Int {
var depth = 1
var current = view.parent
while (current != null) {
depth++
current = current.parent
}
return depth
}
使用ViewBinding替代findViewById
// ❌ 旧的findViewById方式,类型不安全且容易写错
val textView = findViewById<TextView>(R.id.tv_name)
textView.text = "Hello"
textView.setTextColor(Color.BLACK) // 编译期不会报错,即使这个id不存在
// ✅ ViewBinding,编译期检查,类型安全
class ProfileActivity : AppCompatActivity() {
private var _binding: ActivityProfileBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
// 类型安全,编译期检查
binding.tvName.text = "张三"
binding.tvName.setTextColor(Color.BLACK)
// 空View也能安全处理
binding.btnFollow.setOnClickListener {
followUser()
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null // 防止内存泄漏
}
}
总结——一份检查清单
写完这个教程,我总结了几个关键点,方便你自查:
内存泄漏自查:
- [ ] 有没有静态变量持有Activity/View的引用?
- [ ] Handler/Thread是否在主线程销毁时清理了回调?
- [ ] 监听器(Observer/Listener)是否在onPause/onDestroy时移除?
- [ ] Bitmap/Socket/数据库连接是否正确关闭?
- [ ] 有没有用LeakCanary检测?
界面卡顿自查:
- [ ] 有没有在主线程做网络请求/数据库/文件IO?
- [ ] 列表的ViewHolder是否复用了?
- [ ] 布局层级是否超过5层?能用ConstraintLayout吗?
- [ ] 图片加载是否用了Glide/Picasso并指定了尺寸?
- [ ] 动画是否在页面不可见时停止了?
- [ ] 有没有用Profiler检测过CPU和内存?
MVVM架构自查:
- [ ] ViewModel里有没有直接引用View?
- [ ] UI状态是否用StateFlow/LiveData管理?
- [ ] 一次性事件是否用Channel/SingleFlow管理?
- [ ] Repository是否独立于UI层?
- [ ] 是否用依赖注入管理对象生命周期?
记住,好代码不是写出来的,是改出来的。遇到卡顿先跑Profiler,遇到泄漏先上LeakCanary,架构乱就先分层。Android开发这条路,坑不少,但一步步来,你一定能走得很稳。
