说实话,Android 开发这潭水挺深的。我记得刚入行那会儿,总觉得会写个 Hello World 就算入门了,结果真到了生产环境,各种闪退、卡顿、内存泄漏搞得人头秃。今天咱不整那些虚头巴脑的理论,就聊聊我在实际开发中踩过的坑,以及怎么把它们填平。这篇文章就像是一个老大哥在咖啡桌旁跟你掏心窝子,咱们边聊边看代码,保证让你看完能回去直接改 Bug。
第一关:Activity 生命周期,别让它成为你的噩梦
很多人觉得 Activity 生命周期背下来就完事了,onCreate、onStart、onResume… 嘿,真到用的时候全乱套。我先给你讲个真实的崩溃故事。
场景重现:旋转屏幕导致的崩溃
想象一下,你在做一个视频播放器,播放状态存在 Activity 里。用户旋转屏幕,Activity 重建,你忘了恢复状态,直接 NPE(空指针异常)走起。
public class VideoPlayerActivity extends AppCompatActivity {
private VideoView videoView;
private String videoUrl; // 假设这是从网络获取的
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_player);
videoView = findViewById(R.id.video_view);
// 经典错误:没有在 savedInstanceState 中保存和恢复数据
if (savedInstanceState == null) {
videoUrl = "https://example.com/video.mp4";
}
// 如果旋转屏幕后 savedInstanceState 不为 null,但这里没有恢复逻辑
// 可能导致后续使用时 videoUrl 为 null 或者其他状态不一致
startVideo();
}
private void startVideo() {
if (videoUrl != null) { // 这里的判断是不够的
videoView.setVideoPath(videoUrl);
videoView.start();
}
}
// 正确的做法:保存和恢复状态
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("video_url", videoUrl);
outState.putLong("current_position", videoView.getCurrentPosition());
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
videoUrl = savedInstanceState.getString("video_url");
long currentPosition = savedInstanceState.getLong("current_position");
// 恢复视频播放状态...
}
}
你看,这只是最基础的。更复杂的是,很多开发者在 onPause 或 onStop 里做了耗时操作,或者在 onDestroy 里还持有外部对象的引用,导致内存泄漏。
内存泄漏的典型陷阱
public class LeakyActivity extends AppCompatActivity {
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 这个匿名内部类隐式持有外部 Activity 的引用
// 如果消息延迟处理,Activity 就可能无法被回收
updateUI();
}
};
@Override
protected void onResume() {
super.onResume();
// 发送一个延迟消息
handler.sendEmptyMessageDelayed(0, 60 * 60 * 1000); // 一小时后执行
}
@Override
protected void onPause() {
super.onPause();
// 很多开发者忘记取消消息!
// handler.removeMessages(0);
}
private void updateUI() {
// 更新 UI...
}
}
修复这个泄漏很简单,只要在 onPause 里取消消息:
@Override
protected void onPause() {
super.onPause();
handler.removeMessages(0); // 关键!
}
或者更优雅地使用静态内部类 + WeakReference:
public class BetterActivity extends AppCompatActivity {
private static class MyHandler extends Handler {
private final WeakReference<BetterActivity> activityRef;
MyHandler(BetterActivity activity) {
activityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
BetterActivity activity = activityRef.get();
if (activity != null) {
activity.updateUI();
}
}
}
private MyHandler handler = new MyHandler(this);
// ... 其他代码
}
第二关:RecyclerView 性能优化,从入门到精通
RecyclerView 是 Android 列表控件的王者,但用不好也能把你坑死。我见过太多项目,列表一滑就卡,内存飙升,原因多半是 RecyclerView 没用好。
ViewHolder 复用机制 misunderstood
很多开发者以为 ViewHolder 就是简单的视图缓存,其实它是性能优化的核心。让我给你拆解一下。
public class EfficientAdapter extends RecyclerView.Adapter<EfficientAdapter.ViewHolder> {
private static final String TAG = "EfficientAdapter";
// 1. 定义 ViewHolder,只持有需要操作的 View 引用
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView titleView;
TextView subtitleView;
ImageView imageView;
ProgressBar progressBar; // 注意:不是所有 item 都需要 progressBar
public ViewHolder(View itemView) {
super(itemView);
titleView = itemView.findViewById(R.id.title);
subtitleView = itemView.findViewById(R.id.subtitle);
imageView = itemView.findViewById(R.id.image);
// 不要在这里初始化 progressBar,除非你需要
}
}
// 2. onCreateViewHolder 应该尽量轻量
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 避免在这里做耗时操作
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_article, parent, false);
return new ViewHolder(view);
}
// 3. onBindViewHolder 是性能关键,要尽可能快
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Article article = getItem(position);
// 基础数据绑定
holder.titleView.setText(article.getTitle());
holder.subtitleView.setText(article.getSubtitle());
// 图片加载要异步,不要阻塞主线程
// 使用 Glide/Picasso 等库,它们内部有缓存机制
Glide.with(holder.imageView.getContext())
.load(article.getImageUrl())
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(holder.imageView);
// 4. 避免在 onBindViewHolder 里做复杂计算
// 错误示例:每次都重新计算
// String formattedDate = formatDate(article.getCreateDate());
// 正确做法:预处理数据,或者缓存计算结果
}
@Override
public int getItemCount() {
return items.size();
}
private Article getItem(int position) {
return items.get(position);
}
}
预加载和视图类型优化
当你的列表有不同种类的物品时,合理使用 viewType 能大幅提升性能。
public class MultiTypeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private static final int TYPE_FOOTER = 2;
private List<Object> data;
// 不同的 ViewHolder
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView headerTitle;
public HeaderViewHolder(View itemView) {
super(itemView);
headerTitle = itemView.findViewById(R.id.header_title);
}
}
public static class ItemViewHolder extends RecyclerView.ViewHolder {
TextView title;
TextView content;
public ItemViewHolder(View itemView) {
super(itemView);
title = itemView.findViewById(R.id.title);
content = itemView.findViewById(R.id.content);
}
}
public static class FooterViewHolder extends RecyclerView.ViewHolder {
ProgressBar progressBar;
public FooterViewHolder(View itemView) {
super(itemView);
progressBar = itemView.findViewById(R.id.progress);
}
}
@Override
public int getItemViewType(int position) {
if (position == 0) return TYPE_HEADER;
if (position == data.size() - 1) return TYPE_FOOTER;
return TYPE_ITEM;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.header_item, parent, false);
return new HeaderViewHolder(view);
} else if (viewType == TYPE_FOOTER) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.footer_item, parent, false);
return new FooterViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_article, parent, false);
return new ItemViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeaderViewHolder) {
// 绑定 header 数据
((HeaderViewHolder) holder).headerTitle.setText("热门文章");
} else if (holder instanceof ItemViewHolder) {
// 绑定 item 数据
Article article = (Article) data.get(position);
((ItemViewHolder) holder).title.setText(article.getTitle());
((ItemViewHolder) holder).content.setText(article.getContent());
} else if (holder instanceof FooterViewHolder) {
// 绑定 footer 数据
((FooterViewHolder) holder).progressBar.setIndeterminate(true);
}
}
@Override
public int getItemCount() {
return data.size();
}
}
分页加载和预加载策略
对于长列表,一次性加载所有数据是不现实的。我们需要分页加载,甚至预加载。
public class PaginatedRecyclerViewActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private ArticleAdapter adapter;
private LinearLayoutManager layoutManager;
private static final int PAGE_SIZE = 20;
private int currentPage = 0;
private boolean isLoading = false;
private boolean hasMoreData = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_paginated_list);
recyclerView = findViewById(R.id.recycler_view);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter = new ArticleAdapter();
recyclerView.setAdapter(adapter);
// 添加滚动监听,实现分页加载
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
// 当接近列表末尾时加载更多
if (!isLoading && hasMoreData) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount - 5) {
loadMoreData();
}
}
}
});
// 初始加载
loadInitialData();
}
private void loadInitialData() {
isLoading = true;
// 模拟网络请求
new Handler(Looper.getMainLooper()).postDelayed(() -> {
List<Article> articles = fetchArticlesFromNetwork(0, PAGE_SIZE);
adapter.updateData(articles);
currentPage++;
isLoading = false;
if (articles.size() < PAGE_SIZE) {
hasMoreData = false;
}
}, 1000);
}
private void loadMoreData() {
if (isLoading || !hasMoreData) return;
isLoading = true;
// 显示加载更多指示器
adapter.showLoading();
new Handler(Looper.getMainLooper()).postDelayed(() -> {
List<Article> moreArticles = fetchArticlesFromNetwork(currentPage * PAGE_SIZE, PAGE_SIZE);
adapter.addMoreData(moreArticles);
currentPage++;
isLoading = false;
adapter.hideLoading();
if (moreArticles.size() < PAGE_SIZE) {
hasMoreData = false;
}
}, 1000);
}
private List<Article> fetchArticlesFromNetwork(int offset, int limit) {
// 实际项目中这里会是网络请求
// 返回模拟数据
List<Article> result = new ArrayList<>();
for (int i = offset; i < offset + limit; i++) {
result.add(new Article("Article " + i, "Content " + i));
}
return result;
}
}
DiffUtil:告别 notifyDatasetChanged
很多开发者为了方便,数据变化时直接调用 notifyDatasetChanged(),这会触发整个列表的重绘,性能极差。DiffUtil 才是正确的选择。
public class ArticleDiffCallback extends DiffUtil.ItemCallback<Article> {
@Override
public boolean areItemsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
// 业务逻辑:用 ID 判断是否同一物品
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
// 业务逻辑:比较内容是否相同
return oldItem.getTitle().equals(newItem.getTitle()) &&
oldItem.getContent().equals(newItem.getContent());
}
// 可选:如果内容相同但需要更新特定部分(比如动画)
@Nullable
@Override
public Object getChangePayload(@NonNull Article oldItem, @NonNull Article newItem) {
// 返回变化的部分,让 onBindViewHolder 只更新变化的部分
Bundle payload = new Bundle();
if (!oldItem.getTitle().equals(newItem.getTitle())) {
payload.putString("title", newItem.getTitle());
}
return payload.size() > 0 ? payload : null;
}
}
使用 DiffUtil:
public class OptimizedAdapter extends RecyclerView.Adapter<OptimizedAdapter.ViewHolder> {
private List<Article> articles = new ArrayList<>();
private final ArticleDiffCallback diffCallback = new ArticleDiffCallback();
public void updateData(List<Article> newArticles) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
new ArticleDiffCallback(),
() -> {
// 实现 ListDiffer 需要的接口
}
);
articles.clear();
articles.addAll(newArticles);
diffResult.dispatchUpdatesTo(this);
}
}
等等,上面的代码有点问题,让我给你写一个正确的 DiffUtil 用法:
public class ArticleAdapter extends RecyclerView.Adapter<ArticleAdapter.ViewHolder> {
private final List<Article> articles = new ArrayList<>();
private final ArticleDiffCallback diffCallback = new ArticleDiffCallback();
public void submitList(List<Article> newList) {
// 使用 ListAdapter 可以更简单,但这里展示原生 DiffUtil
List<Article> oldList = articles;
articles.clear();
articles.addAll(newList);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return articles.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return diffCallback.areItemsTheSame(oldList.get(oldItemPosition),
articles.get(newItemPosition));
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return diffCallback.areContentsTheSame(oldList.get(oldItemPosition),
articles.get(newItemPosition));
}
});
diffResult.dispatchUpdatesTo(this);
}
}
第三关:常见崩溃原因及解决方案
NullPointerException(NPE)- 永远的头号杀手
NPE 占 Android 崩溃的 50% 以上。原因千奇百怪,但核心就一个:空指针。
// 反模式:危险的链式调用
String result = user.getAddress().getCity().getName();
// 正解:逐步检查或使用 Optional(Java 8+)
String result = null;
if (user != null) {
Address address = user.getAddress();
if (address != null) {
City city = address.getCity();
if (city != null) {
result = city.getName();
}
}
}
// 或者使用 Kotlin 的安全调用(如果你用 Kotlin)
val result = user?.address?.city?.name
在 Java 中,你可以用 Objects.requireNonNull 来明确失败原因:
public void processUser(User user) {
// 明确抛出异常,便于调试
Objects.requireNonNull(user, "User cannot be null");
// 后续操作
String name = user.getName();
// ...
}
ClassCastException - 类型转换错误
这种崩溃通常发生在泛型集合或者 View 查找时。
// 错误示例:unchecked cast
List<String> strings = (List<String>) jsonObject.get("items"); // 可能 ClassCastException
// 正确示例:使用 TypeToken 或逐个转换
Type type = new TypeToken<List<String>>() {}.getType();
List<String> strings = gson.fromJson(jsonObject.get("items").toString(), type);
在 RecyclerView 中,这种错误常见于 getItemViewType 返回错误类型:
// 错误:viewType 和实际 ViewHolder 不匹配
@Override
public int getItemViewType(int position) {
return 0; // 所有 item 都返回 0
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 0) {
// 但如果实际有多个类型,这里会出错
return new SimpleViewHolder(itemView);
}
return null; // 返回 null 会导致崩溃
}
OutOfMemoryError - 内存溢出
图片处理是 OOM 的主要原因。
