切换Tab时Activity崩溃,报无文件名行号的NullPointerException
首先,这个NullPointerException虽然显示在系统的ViewRootImpl.getHostVisibility()方法里,但本质是咱们代码中Fragment的View生命周期管理不当,导致系统在处理页面可见性变化时,访问到了已经被销毁的View引用。下面一步步拆解问题和修复方案:
1. TabLayout与ViewPager的初始化顺序错误
你在Activity中先手动添加了Tab,然后调用tabLayout.setupWithViewPager(pager)——但setupWithViewPager会清空TabLayout中已有的Tab,然后根据PagerAdapter的getPageTitle()方法重新生成Tab。这会导致Tab和Fragment的对应关系混乱,间接引发页面切换时的生命周期异常。
修复方案:
- 删除Activity中手动添加Tab的两行代码:
// 删掉这两行 tabLayout.addTab(tabLayout.newTab().setText("FirstTab")); tabLayout.addTab(tabLayout.newTab().setText("SecondTab")); - 在你的
LockScreenPagerAdapter中实现getPageTitle()方法,让ViewPager自动生成Tab:@Override public CharSequence getPageTitle(int position) { switch (position) { case 0: return "FirstTab"; case 1: return "SecondTab"; default: return null; } }
2. ButterKnife未正确解绑,导致View引用泄漏
你的两个Fragment都使用了ButterKnife,但没有在onDestroyView()中调用unbinder.unbind()。当Fragment的View被销毁(比如ViewPager切换页面销毁不可见的Fragment),ButterKnife依然持有这些View的引用,系统后续处理View可见性时就会访问到null的View。
修复方案:
在两个Fragment中添加onDestroyView()方法,解绑ButterKnife:
// LsFirstFragment和LsSecondFragment都要加 @Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); }
3. SecondFragment存在无限递归风险
在LsSecondFragment的loadOfflineArticlesData()方法中,如果savedArticles为空,你会递归调用自己,这会导致栈溢出,间接引发页面崩溃或异常:
if (savedArticles != null && savedArticles.size() == 0) { loadOfflineArticlesData(); // 这里无限递归了! }
修复方案:
替换递归逻辑,比如显示空视图或终止方法:
if (savedArticles != null && savedArticles.isEmpty()) { // 这里可以显示空状态提示,比如: // emptyView.setVisibility(View.VISIBLE); return; // 终止方法,避免无限递归 }
4. Fragment中View与上下文的安全判断不足
在LsFirstFragment的loadOfflineArticlesData()中,你操作View时没有判断View是否已经被销毁(比如Fragment detached后,lsFirstIv等View可能已经为null),而且使用getActivity()作为Glide的上下文,当Activity或Fragment状态变化时容易出现问题。
修复方案:
- 操作View前先判断View是否非null;
- 使用Fragment实例作为Glide的上下文(比
getActivity()更安全,Fragment销毁时Glide会自动取消请求):
public void loadOfflineArticlesData() { if (getContext() != null && lsArticlesSharedPreference != null && view != null) { List<Articles> savedArticles = lsArticlesSharedPreference.getOfflineDataLs(getContext()); RequestOptions requestOptions = new RequestOptions(); if (savedArticles != null && !savedArticles.isEmpty()) { // 先判断所有要操作的View是否存在 if (lsFirstSwipeLeftTv != null && lsFirstArticleHeadOverlayTv != null && lsFirstIv != null) { lsFirstSwipeLeftTv.setVisibility(View.VISIBLE); lsFirstArticleHeadOverlayTv.setVisibility(View.VISIBLE); // 用Fragment实例作为Glide上下文 Glide.with(this) .setDefaultRequestOptions(requestOptions) .load(savedArticles.get(0).getArticleSecondImage()) .thumbnail(Glide.with(getContext()).load(R.drawable.logo_anim_full)) .into(lsFirstIv); lsFirstIv.setScaleType(ImageView.ScaleType.FIT_XY); lsFirstArticleHeadOverlayTv.setText(savedArticles.get(0).getArticleHeading()); } } } }
为什么会出现系统层面的NPE?
当Fragment的View被销毁后,若还有代码(比如异步的Glide加载完成回调)尝试操作这些View,或者ButterKnife未解绑导致View引用被非法持有,系统在处理页面可见性变化时,ViewRootImpl会尝试访问当前关联的View,此时View已经是null,就会抛出这个NullPointerException——虽然你没直接调用getVisibility(),但这是系统内部流程触发的。
内容的提问来源于stack exchange,提问作者Kunal

