说到 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
}

避坑指南:

  1. 永远不要依赖 onPause 或 onStop 来保存数据,这两个方法调用时机不确定。
  2. 小数据用 Bundle,大数据用 ViewModel。Bundle 有大小限制,而且序列化反序列化有开销。
  3. 考虑配置更改(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;
    }
}

避坑指南:

  1. 永远使用 RecyclerView 的 ViewHolder 模式,不要手动创建视图。
  2. 图片加载务必使用库(Glide、Coil、Picasso),它们自动处理缓存、线程、内存管理。
  3. 列表数据量大时,考虑分页加载,不要一次性加载所有数据。
  4. 使用 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)
}

避坑指南:

  1. 永远不要在主线程执行耗时操作(网络、数据库、文件 I/O)。
  2. 使用现代异步工具:Kotlin 协程(推荐)、RxJava、或 Java 的 ExecutorService。
  3. 注意生命周期:使用 viewLifecycleOwner 观察 LiveData,避免内存泄漏和空指针。
  4. 异常处理:协程中捕获异常,避免静默失败。

痛点四:内存泄漏

场景还原: 应用用久了越来越卡,甚至崩溃。检查发现内存泄漏。

核心问题: 对象生命周期过长,无法被垃圾回收。

常见错误写法:

// ❌ 错误示范:静态引用 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);
        });
    }
}

避坑指南:

  1. 优先使用 ApplicationContext,除非你明确需要 Activity Context(比如显示 Dialog)。
  2. 避免在静态变量中持有 Activity/Fragment 引用。
  3. 使用 WeakReference 在长生命周期对象中持有短生命周期对象。
  4. 及时清理监听器、广播接收者、线程,在 onDestroy 中取消注册。
  5. 使用 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()
    }
}

避坑指南:

  1. 后台服务限制严格:Android 8.0+ 禁止后台启动服务,必须使用 startForegroundService 并立即显示通知。
  2. 推荐使用 WorkManager:它是官方推荐的后台任务解决方案,处理了各种设备状态和系统限制。
  3. 考虑使用 JobScheduler:对于需要精确调度的任务。
  4. 前台服务:如果任务需要立即执行且用户可见,使用 ForegroundService 并显示通知。

总结

Android 开发的痛点,说到底就是系统限制、生命周期管理、资源优化这三件事。记住:

  • 生命周期是上帝:尊重它,利用它,别和它作对。
  • 性能是用户体验:列表、图片、异步,处处有优化空间。
  • 内存泄漏是隐形杀手:定期检测,及时清理。
  • 后台任务是定时炸弹:了解系统限制,使用正确 API。

希望这些实战经验和代码示例能帮到你。Android 开发虽然坑多,但跨过去,就是高手。加油!