Fragment中传感器触发TextView.setText()致APP崩溃,疑似TextView为空
兄弟,我太懂这种空指针崩溃的糟心了!你说在传感器状态变化时调用TextView.setText()就崩,感觉像TextView变量是空的,虽然你已经在onCreateView里初始化了,那咱们来捋捋几个最容易踩的坑:
1. 确认TextView的初始化方式是否正确
你在onCreateView里初始化page1的时候,必须通过Fragment自己的根View去findViewById,不能直接用getActivity().findViewById()!如果你的TextView是属于Fragment布局的,直接用Activity去查找就会拿到null,后续setText必然崩溃。正确的写法应该是这样:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mView = inflater.inflate(R.layout.fragment1_layout, container, false); // 用Fragment的根视图mView来查找TextView page1 = mView.findViewById(R.id.your_textview_id); return mView; }
2. 传感器回调时,Fragment可能已经不活跃,TextView被回收
传感器的回调是持续触发的,哪怕你的Fragment已经被销毁、隐藏或者脱离了Activity,SensorManager依然可能给你发回调,这时候page1早就变成null了。所以在回调里一定要先做双重检查:
@Override public void onSensorChanged(SensorEvent event) { // 先判断Fragment是否还附着在Activity上,以及TextView是否不为空 if (!isAdded() || page1 == null) { return; } // 注意:传感器回调可能在非UI线程,必须切回主线程更新UI getActivity().runOnUiThread(() -> { page1.setText("foo"); }); }
这里额外提一句:传感器回调大概率不在主线程,直接在非UI线程调用setText()不仅可能空指针,还会因为跨线程更新UI崩溃,所以一定要用runOnUiThread切换到主线程操作。
3. 务必在Fragment生命周期中及时注销传感器
你肯定在onResume里注册了传感器监听,但一定要在onPause或者onDestroyView里注销!不然Fragment销毁后,SensorManager还持有Fragment的引用,既会导致内存泄漏,还会继续触发回调,这时候page1早就被回收成null了:
@Override public void onResume() { super.onResume(); // 初始化SensorManager和传感器(如果还没初始化的话) if (mSrMgr == null) { mSrMgr = (SensorManager) getActivity().getSystemService(Context.SENSOR_SERVICE); } if (mLight == null) { mLight = mSrMgr.getDefaultSensor(Sensor.TYPE_LIGHT); } // 注册监听 if (mSrMgr != null && mLight != null) { mSrMgr.registerListener(this, mLight, SensorManager.SENSOR_DELAY_NORMAL); } } @Override public void onPause() { super.onPause(); // 注销监听,避免内存泄漏和无效回调 if (mSrMgr != null) { mSrMgr.unregisterListener(this); } }
4. 别在太早的生命周期里操作TextView
比如你如果在onCreate方法里就去调用page1.setText(),这时候onCreateView还没执行,page1肯定是null,直接就崩了。所有UI相关的操作,都要等到onCreateView执行完成之后再做。
内容的提问来源于stack exchange,提问作者hdw3

