记得刚开始学安卓的时候,大家都写过那个经典的”Hello World”。代码简单,运行起来满屏喜悦。那时候谁能想到,随着项目变大,界面越来越复杂,那些曾经看似无害的代码,会像定时炸弹一样在关键时刻引爆?今天咱们就聊聊安卓开发里最让人头疼的两个大坑:内存泄漏和线程调度。别担心,我会用大白话和实际例子带你把这些坑填平。
内存泄漏:那些”赖着不走”的对象
内存泄漏在安卓开发中简直是家常便饭。简单说,就是有些对象你明明不需要了,但系统还留着它们,导致内存不断增长,最后App卡顿甚至崩溃。
匿名内部类的坑
举个最常见的例子。很多开发者喜欢在Activity里直接写匿名内部类,比如处理点击事件:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理点击事件
}
});
// 启动一个定时任务
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// 执行一些操作
}
}, 60000); // 1分钟后执行
}
}
这里有个大问题!mHandler持有MainActivity的引用,而Runnable匿名内部类又隐式持有外部类MainActivity的引用。当1分钟倒计时还没结束时,你按返回键退出Activity,MainActivity本该被回收,但因为mHandler里还有一条引用链指向它,所以根本回收不了!
静态变量的陷阱
另一个常见错误是使用静态变量持有Context:
public class SingleTon {
private static SingleTon instance;
private Context context;
public static SingleTon getInstance(Context context) {
if (instance == null) {
instance = new SingleTon();
}
// 危险!静态变量持有Activity的Context
instance.context = context;
return instance;
}
}
当你调用SingleTon.getInstance(this)时,传入的是Activity的Context。由于instance是静态的,它的生命周期和App一样长,这就意味着Activity被销毁后,它的内存还一直被SingleTon占着。
解决内存泄漏的方法
方法一:使用弱引用
public class WeakReferenceExample {
private WeakReference<MainActivity> mActivityRef;
public WeakReferenceExample(MainActivity activity) {
mActivityRef = new WeakReference<>(activity);
}
public void doSomething() {
MainActivity activity = mActivityRef.get();
if (activity != null) {
// 安全使用
activity.doAction();
}
}
}
方法二:及时取消注册和回调
public class ProperLifecycleExample extends AppCompatActivity {
private BroadcastReceiver broadcastReceiver;
private Handler mHandler = new Handler();
@Override
protected void onResume() {
super.onResume();
// 注册广播
broadcastReceiver = new MyBroadcastReceiver();
registerReceiver(broadcastReceiver, new IntentFilter("my_action"));
// 发送消息
mHandler.postDelayed(runnable, 1000);
}
@Override
protected void onPause() {
super.onPause();
// 记得取消!
if (broadcastReceiver != null) {
unregisterReceiver(broadcastReceiver);
}
mHandler.removeCallbacksAndMessages(null);
}
}
方法三:使用LeakCanary检测
// build.gradle中添加
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
这个库会在检测到内存泄漏时自动提醒你,简直是开发者的救星。
线程调度:别让你的界面卡成PPT
安卓是单线程模型,UI操作必须在主线程完成。但很多开发者不知道如何正确处理多线程,导致界面卡顿或崩溃。
子线程更新UI的坑
// 错误示范:在子线程中更新UI
new Thread(new Runnable() {
@Override
public void run() {
// 这是在子线程中操作UI,会抛出异常
TextView textView = findViewById(R.id.text_view);
textView.setText("这是在子线程设置的文本");
}
}).start();
这段代码看似简单,但实际上会在运行时抛出CalledFromWrongThreadException异常。因为TextView的setText方法必须在主线程调用。
Handler的正确用法
public class ProperHandlerExample extends AppCompatActivity {
private TextView textView;
private Handler mainHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_example);
textView = findViewById(R.id.text_view);
// 在子线程执行耗时操作
new Thread(new Runnable() {
@Override
public void run() {
// 模拟耗时操作
simulateLongOperation();
// 通过Handler切回主线程更新UI
mainHandler.post(new Runnable() {
@Override
public void run() {
textView.setText("操作完成!");
}
});
}
}).start();
}
private void simulateLongOperation() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
AsyncTask的坑
虽然AsyncTask已经被标记为废弃,但很多老项目还在用。它有几个经典问题:
// 旧式AsyncTask的问题
private class DownloadTask extends AsyncTask<String, Void, String> {
private WeakReference<Activity> activityRef;
public DownloadTask(Activity activity) {
activityRef = new WeakReference<>(activity);
}
@Override
protected String doInBackground(String... urls) {
// 执行下载
return "下载完成";
}
@Override
protected void onPostExecute(String result) {
Activity activity = activityRef.get();
if (activity != null) {
// 安全更新UI
activity.findViewById(R.id.result_text).setText(result);
}
}
}
这里用WeakReference持有Activity,防止内存泄漏。但即便如此,如果Activity在任务执行期间被销毁,还是会有一些微妙的问题。
现代解决方案:协程
Kotlin协程是现在处理异步操作的最佳实践:
class ModernActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
private var job: Job = Job()
private val scope = CoroutineScope(Dispatchers.Main + job)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_modern)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// 使用协程发起网络请求
scope.launch {
try {
val result = viewModel.fetchData()
// 自动在主线程更新UI
resultTextView.text = result
} catch (e: Exception) {
resultTextView.text = "错误: ${e.message}"
}
}
}
override fun onDestroy() {
super.onDestroy()
// 取消所有协程,防止内存泄漏
job.cancel()
}
}
LiveData和ViewModel的结合
class MainViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun fetchData() {
viewModelScope.launch {
// 在IO线程执行网络请求
val result = withContext(Dispatchers.IO) {
networkRepository.getData()
}
// 自动回到主线程
_data.postValue(result)
}
}
}
class DataActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_data)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// 观察数据变化,自动在主线程回调
viewModel.data.observe(this) { result ->
resultTextView.text = result
}
viewModel.fetchData()
}
}
实际开发中的综合案例
让我给你一个完整的实战例子,展示如何避免这些坑:
class ProductActivity : AppCompatActivity() {
private lateinit var binding: ActivityProductBinding
private lateinit var viewModel: ProductViewModel
private var imageLoader: ImageLoader? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityProductBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this).get(ProductViewModel::class.java)
// 初始化图片加载器
imageLoader = ImageLoader(this)
// 观察数据
observeData()
// 设置点击事件,使用Lambda表达式
binding.btnBuy.setOnClickListener {
viewModel.purchaseProduct()
}
}
private fun observeData() {
viewModel.product.observe(this) { product ->
product?.let {
// 更新UI
binding.tvName.text = it.name
binding.tvPrice.text = it.price
// 使用图片加载器加载图片
imageLoader?.load(it.imageUrl, binding.ivProduct)
}
}
viewModel.error.observe(this) { errorMsg ->
errorMsg?.let {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}
}
}
override fun onDestroy() {
super.onDestroy()
// 释放图片加载器
imageLoader?.cancelAll()
imageLoader = null
}
}
class ProductViewModel : ViewModel() {
private val _product = MutableLiveData<Product>()
val product: LiveData<Product> = _product
private val _error = MutableLiveData<String?>()
val error: LiveData<String?> = _error
fun loadProduct(productId: String) {
viewModelScope.launch {
try {
val result = repository.getProduct(productId)
_product.postValue(result)
} catch (e: Exception) {
_error.postValue(e.message)
}
}
}
fun purchaseProduct() {
viewModelScope.launch {
// 执行购买逻辑
}
}
}
这个例子展示了现代安卓开发的最佳实践:使用ViewBinding代替findViewById,使用ViewModel和LiveData处理数据,使用协程处理异步操作,及时释放资源。
调试工具推荐
最后,给你推荐几个实用的调试工具:
- Android Studio Profiler:可以实时查看内存使用情况和线程状态
- LeakCanary:自动检测内存泄漏
- StrictMode:检测主线程上的不当操作
// 在Application中启用StrictMode
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
)
}
}
}
记住,好的代码不是一蹴而就的,而是在不断发现问题、解决问题的过程中逐步完善的。希望这些经验能帮你在安卓开发的道路上走得更稳更远。如果还有疑问,随时来找我讨论!
