说到 Android 开发,很多人第一反应是“坑多”。这还真不是空穴来风。我刚入行那会儿,遇到个屏幕旋转导致数据丢失的 bug,查了三天三夜,最后发现是因为 onSaveInstanceState 没写对。这种痛,每个 Android 开发者都经历过。今天,咱们不聊虚的,就实打实地把几个最常见的痛点扒开来看看,配上代码,告诉你怎么避坑。
痛点一:Activity 生命周期与数据保存
场景还原: 用户正在填写一个长表单,突然收到个电话,或者切到后台再回来,数据全没了!那种挫败感,懂的都懂。
核心问题: Activity 被系统回收时,如何保存和恢复状态?
常见错误写法:
// ❌ 错误示范:在 onCreate 里直接读取,但没保存
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 假设有一个 EditText 需要恢复
String savedText = savedInstanceState != null
? savedInstanceState.getString("my_key")
: "";
editText.setText(savedText);
}
正确做法:
// ✅ 正确示范:完整生命周期管理
public class MainActivity extends AppCompatActivity {
private static final String KEY_TEXT = "my_key";
private EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.edit_text);
// 恢复状态
if (savedInstanceState != null) {
String savedText = savedInstanceState.getString(KEY_TEXT);
editText.setText(savedText);
}
}
// ⚠️ 关键:重写 onSaveInstanceState
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
// 保存数据
outState.putString(KEY_TEXT, editText.getText().toString());
}
// 💡 额外建议:对于更复杂的数据,考虑使用 ViewModel + LiveData/StateFlow
}
避坑指南:
- 永远不要依赖
onPause或onStop来保存数据,这两个方法调用时机不确定。 - 小数据用
Bundle,大数据用ViewModel。Bundle有大小限制,而且序列化反序列化有开销。 - 考虑配置更改(Configuration Changes):如果屏幕旋转、键盘弹出等,Activity 会重建。可以在
AndroidManifest.xml中设置android:configChanges来避免重建,但这会绕过正常的生命周期,需谨慎使用。
痛点二:列表性能问题(RecyclerView)
场景还原: 列表滑动卡顿,甚至 ANR(应用无响应)。用户直骂“垃圾 App”。
核心问题:
RecyclerView 适配器的优化,以及图片加载的延迟问题。
常见错误写法:
// ❌ 错误示范:在 onBindViewHolder 中直接加载图片,且没有复用视图
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
// 每次都重新创建 ViewHolder,没有复用
holder.imageView.setImageResource(list.get(position).getImageUrl());
holder.textView.setText(list.get(position).getTitle());
}
正确做法:
// ✅ 正确示范:使用 ViewHolder 模式 + 图片懒加载/缓存
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<Item> itemList;
// ✅ 1. 定义静态 ViewHolder 内部类,提升性能
static class MyViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView textView;
MyViewHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image_view);
textView = itemView.findViewById(R.id.text_view);
}
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// ✅ 2. 使用 LayoutInflater 创建视图,并复用
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_layout, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Item item = itemList.get(position);
// ✅ 3. 使用 Glide/Picasso 加载图片,支持缓存和占位图
Glide.with(holder.imageView.getContext())
.load(item.getImageUrl())
.placeholder(R.drawable.placeholder) // 占位图
.error(R.drawable.error_image) // 错误图
.into(holder.imageView);
holder.textView.setText(item.getTitle());
}
// ✅ 4. 记得实现 getItemId,有助于优化
@Override
public long getItemId(int position) {
return position;
}
}
避坑指南:
- 永远使用
RecyclerView的ViewHolder模式,不要手动创建视图。 - 图片加载务必使用库(Glide、Coil、Picasso),它们自动处理缓存、线程、内存管理。
- 列表数据量大时,考虑分页加载,不要一次性加载所有数据。
- 使用
DiffUtil进行列表更新,避免全量刷新,只更新变化的部分。
痛点三:异步任务与线程管理
场景还原: 在主线程执行网络请求,导致 ANR;或者回调地狱,代码难以维护。
核心问题: 如何优雅地处理后台任务和 UI 更新?
常见错误写法:
// ❌ 错误示范:在主线程执行耗时操作
new Thread(new Runnable() {
@Override
public void run() {
// 耗时操作,比如网络请求
String data = fetchFromNetwork();
// ❌ 在子线程更新 UI,会崩溃!
textView.setText(data);
}
}).start();
正确做法:
// ✅ 正确示范:使用协程(Kotlin)或 LiveData/ViewModel
// 假设使用 Kotlin + ViewModel + LiveData
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun fetchData() {
viewModelScope.launch(Dispatchers.IO) {
// 在 IO 线程执行网络请求
val result = withContext(Dispatchers.IO) {
fetchFromNetwork()
}
// 自动切换到主线程更新 UI
_data.postValue(result)
}
}
}
// 在 Activity/Fragment 中观察
viewModel.data.observe(viewLifecycleOwner) { result ->
textView.setText(result)
}
避坑指南:
- 永远不要在主线程执行耗时操作(网络、数据库、文件 I/O)。
- 使用现代异步工具:Kotlin 协程(推荐)、RxJava、或 Java 的
ExecutorService。 - 注意生命周期:使用
viewLifecycleOwner观察LiveData,避免内存泄漏和空指针。 - 异常处理:协程中捕获异常,避免静默失败。
痛点四:内存泄漏
场景还原: 应用用久了越来越卡,甚至崩溃。检查发现内存泄漏。
核心问题: 对象生命周期过长,无法被垃圾回收。
常见错误写法:
// ❌ 错误示范:静态引用 Context,导致 Activity 无法被回收
public class MySingleton {
private static MySingleton instance;
private Context context;
private MySingleton(Context context) {
this.context = context.getApplicationContext(); // ✅ 应该用 Application Context
}
public static MySingleton getInstance(Context context) {
if (instance == null) {
instance = new MySingleton(context);
}
return instance;
}
}
// ❌ 错误示范:匿名内部类/回调持有 Activity 引用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 创建一个长生命周期对象,却持有了 Activity 引用
new Thread(new Runnable() {
@Override
public void run() {
// 耗时操作...
// 操作完成后,可能尝试更新 UI
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText("Done"); // 如果 Activity 已销毁,这里会出问题
}
});
}
}).start();
}
}
正确做法:
// ✅ 正确示范:使用弱引用或 Lifecycle-aware 组件
public class MySingleton {
private static MySingleton instance;
private Context context;
// ✅ 使用 Application Context,避免持有 Activity 引用
private MySingleton(Context context) {
this.context = context.getApplicationContext();
}
public static MySingleton getInstance(Context context) {
if (instance == null) {
instance = new MySingleton(context);
}
return instance;
}
}
// ✅ 正确示范:使用 ViewModel 或 Lifecycle 组件管理 UI 更新
public class MainActivity extends AppCompatActivity {
private MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
// 观察 LiveData,ViewModel 会自动处理生命周期
viewModel.getData().observe(this, result -> {
textView.setText(result);
});
}
}
避坑指南:
- 优先使用
ApplicationContext,除非你明确需要 Activity Context(比如显示 Dialog)。 - 避免在静态变量中持有 Activity/Fragment 引用。
- 使用
WeakReference在长生命周期对象中持有短生命周期对象。 - 及时清理监听器、广播接收者、线程,在
onDestroy中取消注册。 - 使用 LeaksCanary 等工具检测内存泄漏。
痛点五:后台服务与 Doze 模式
场景还原: 应用在后台运行时,通知不准时,数据不同步。用户抱怨“为什么我的 App 不工作了?”
核心问题: Android 8.0 及以上版本的 Doze 模式和 App Standby 对后台任务的影响。
常见错误写法:
// ❌ 错误示范:在 Android 8.0+ 直接使用 startService 启动后台服务
Intent intent = new Intent(this, MyBackgroundService.class);
startService(intent); // 会抛出 IllegalStateException
正确做法:
// ✅ 正确示范:使用 WorkManager 处理后台任务
// WorkManager 会根据系统版本自动选择 JobScheduler、Fcm 或 BroadcastReceiver
public class MyWorkManagerExample {
public void schedulePeriodicWork() {
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(
MyWorker.class,
15, TimeUnit.MINUTES, // 最少间隔 15 分钟
10, TimeUnit.MINUTES // 允许弹性时间
).build();
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
"my_periodic_work",
ExistingPeriodicWorkPolicy.KEEP,
workRequest
);
}
}
public class MyWorker extends Worker {
public MyWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public Result doWork() {
// 执行后台任务,比如同步数据
syncData();
return Result.success(); // 或 Result.failure()
}
}
避坑指南:
- 后台服务限制严格:Android 8.0+ 禁止后台启动服务,必须使用
startForegroundService并立即显示通知。 - 推荐使用
WorkManager:它是官方推荐的后台任务解决方案,处理了各种设备状态和系统限制。 - 考虑使用
JobScheduler:对于需要精确调度的任务。 - 前台服务:如果任务需要立即执行且用户可见,使用
ForegroundService并显示通知。
总结
Android 开发的痛点,说到底就是系统限制、生命周期管理、资源优化这三件事。记住:
- 生命周期是上帝:尊重它,利用它,别和它作对。
- 性能是用户体验:列表、图片、异步,处处有优化空间。
- 内存泄漏是隐形杀手:定期检测,及时清理。
- 后台任务是定时炸弹:了解系统限制,使用正确 API。
希望这些实战经验和代码示例能帮到你。Android 开发虽然坑多,但跨过去,就是高手。加油!
