一个不良编程习惯引起的怪异bug
来源:互联网 发布:皇图时装进阶数据 编辑:程序博客网 时间:2024/05/16 14:04
最近项目上反馈了一个异常,十分常见的异常,Caused by: java.lang.NullPointerException
一般出现这个异常其实可能会有两种可能,一种是变量还未进行初始化,程序就使用了该变量,另外一个就是,变量初始化完成,当程序调用时,有地方已经将该变量置为空。
经过对程序逻辑的查看,初始化已经完成,可以确定的说,肯定初始化了,变量置空的时候,只有在onDestory中进行了置空,而且调用的地方也不是在onDestory中,而是程序正常运行中。为了演示这个bug,写了一个简单的demo,代码如下:
Test.java
package com.android.test;import android.util.Log;public class Test { public void test(){ Log.i("Test", "Test"); }}
Activity布局xml文件
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <Button android:id="@+id/btn" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="finish" /></LinearLayout>
Activity.java
package com.android.test;import android.app.Activity;import android.os.Bundle;import android.telephony.PhoneStateListener;import android.telephony.TelephonyManager;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;public class TestActivity extends Activity { private TelephonyManager mTelephonyManager; private FlashLightPhoneStateListener mFlashLightPhoneStateListener; private Test mTest; private Button btn; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mTelephonyManager = (TelephonyManager) this.getSystemService(TELEPHONY_SERVICE); btn = (Button) this.findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { TestActivity.this.finish(); } }); mFlashLightPhoneStateListener = new FlashLightPhoneStateListener(); mTest = new Test(); registerPhoneStateListener(); } private void registerPhoneStateListener() { mTelephonyManager.listen(mFlashLightPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); } class FlashLightPhoneStateListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state) { case TelephonyManager.CALL_STATE_IDLE: break; case TelephonyManager.CALL_STATE_RINGING: mTest.test(); break; case TelephonyManager.CALL_STATE_OFFHOOK: break; default: break; } } } @Override protected void onDestroy() { super.onDestroy(); mTest = null; }}代码逻辑很简单,我相信大家都能看得懂,Activity监听电话状态,当电话响铃时,调用Test类的一个方法进行打印,点击按钮,程序退出
异常描述如下:
产生的问题:第一次进入程序,状态正常,电话监听也正常,程序无任何异常
点击按钮之后,退出程序,再次进入程序,来电话程序异常
W/System.err( 9859): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.android.test/com.android.test.TestActivity}: java.lang.NullPointerExceptionW/System.err( 9859): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2342)W/System.err( 9859): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2394)W/System.err( 9859): at android.app.ActivityThread.access$800(ActivityThread.java:155)W/System.err( 9859): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1325)W/System.err( 9859): at android.os.Handler.dispatchMessage(Handler.java:110)W/System.err( 9859): at android.os.Looper.loop(Looper.java:193)W/System.err( 9859): at android.app.ActivityThread.main(ActivityThread.java:5300)W/System.err( 9859): at java.lang.reflect.Method.invokeNative(Native Method)W/System.err( 9859): at java.lang.reflect.Method.invoke(Method.java:515)W/System.err( 9859): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:830)W/System.err( 9859): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:646)W/System.err( 9859): at dalvik.system.NativeStart.main(Native Method)W/System.err( 9859): Caused by: java.lang.NullPointerExceptionW/System.err( 9859): at com.android.test.TestActivity.registerPhoneStateListener(TestActivity.java:39)W/System.err( 9859): at com.android.test.TestActivity.onCreate(TestActivity.java:35)W/System.err( 9859): at android.app.Activity.performCreate(Activity.java:5264)W/System.err( 9859): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1088)W/System.err( 9859): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2306)W/System.err( 9859): ... 11 more
但是根据正常的逻辑,oncreate中已经对Test类进行了初始化,而且Test的对象肯定不应该为空,但是灵异的出现了onCreate中进行了初始化,但是在来电话使用的时候,变量奇迹般的为空
经过打印log信息,发现了一个问题
class FlashLightPhoneStateListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state) { case TelephonyManager.CALL_STATE_IDLE: break; case TelephonyManager.CALL_STATE_RINGING: Log.i("logTag", TestActivity.this.toString()); mTest.test(); break; case TelephonyManager.CALL_STATE_OFFHOOK: break; default: break; } }}
第一次TestActivity对象是@421138d8
// logTag(11350): com.android.test.TestActivity@421138d8
调用finish之后,再次进入来电话时,对象没有改变,依然是@421138d8
// logTag(11350):com.android.test.TestActivity@421138d8
说明前一次的Activity并没有销毁,调用了finish之后,Activity依然存在,调用finish之后,程序会执行onDestory,在onDestory中对Test对象进行了置空mTest= null;
而调用finish之后,Activity对象并没有销毁,由于程序设置了监听电话状态,所以该实例对象依然可以监听到电话状态,所以监听到电话状态后,未销毁的实例,会调用Test实例的test方法,由于该实例调用过了onDestory,Test变量已经置成了空,所以此处会发生空指针异常。这也证实了case TelephonyManager.CALL_STATE_RINGING:两次打印的Activity的地址是一样的。
Log.i("logTag",TestActivity.this.toString());这里说明了两个问题:
1,调用Activity的finish之后,程序会执行onDestory,执行完这些后,并不代表这个Activity已经完全不存在。
2,注册了电话监听,电话监听中依赖其他的变量,ondestory中,将变量置空,却没有取消对电话的监听,这是很危险的。
所以,一般传感器,监听器,注册了监听之后,一定要注意取消注册,上处程序最好的修改方案,不是增加非空判断,
case TelephonyManager.CALL_STATE_RINGING: if(null != mTest){ mTest.test(); } break;
增加非空判断,只是将问题进行了规避,并没有正常的解决。最好的解决办法应该是
在将mTest置空时,应该取消电话的监听。这样当再次进入时,未销毁的Activity,由于已经取消了电话的监听,所以不会调用置空的对象的方法,也就不会出现上述空指针异常
package com.android.test;import android.app.Activity;import android.os.Bundle;import android.telephony.PhoneStateListener;import android.telephony.TelephonyManager;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;public class TestActivity extends Activity { private TelephonyManager mTelephonyManager; private FlashLightPhoneStateListener mFlashLightPhoneStateListener; private Test mTest; private Button btn; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mTelephonyManager = (TelephonyManager) this .getSystemService(TELEPHONY_SERVICE); btn = (Button) this.findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { TestActivity.this.finish(); } }); mFlashLightPhoneStateListener = new FlashLightPhoneStateListener(); mTest = new Test(); registerPhoneStateListener(); } private void registerPhoneStateListener() { mTelephonyManager.listen(mFlashLightPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); } private void unregisterPhoneStateListener() { if (mTelephonyManager != null) { mTelephonyManager.listen(mFlashLightPhoneStateListener, PhoneStateListener.LISTEN_NONE); } } class FlashLightPhoneStateListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state) { case TelephonyManager.CALL_STATE_IDLE: break; case TelephonyManager.CALL_STATE_RINGING: mTest.test(); break; case TelephonyManager.CALL_STATE_OFFHOOK: break; default: break; } } } @Override protected void onDestroy() { super.onDestroy(); unregisterPhoneStateListener(); mTest = null; }}
这个现象反馈出一个很重要的问题,那就是编程习惯很重要,有些朋友注册了电话的监听,但是不会主动的取消注册,注册了监听之后,不手动的取消注册,这样就给程序带来了很多隐藏的问题,这样出现一个问题,需要解决耗费的时间,有时候不会是一个小时,两个小时,很有可能是一天,或者更长。
- 一个不良编程习惯引起的怪异bug
- 程序员不良的习惯
- 程序员不良的习惯
- IOS编程中程序员真爱的不良习惯
- 程序员偷偷深爱的 9 个不良编程习惯
- 程序员偷偷深爱的 9 个不良编程习惯
- 程序员偷偷深爱的9个不良编程习惯
- 程序员偷偷深爱的9个不良编程习惯
- 程序员偷偷深爱的9个不良编程习惯
- 程序员偷偷深爱的9个不良编程习惯
- 程序员偷偷深爱的9个不良编程习惯
- memcpy引起的一个bug
- 一个分号引起的bug
- 一个BUG引起的思考
- 一些不良的思维习惯!!!
- 一些不良的思维习惯!!!
- 一些不良的思维习惯!!
- 使用笔记本的不良习惯
- poj1062 最短路问题
- css(盒子模型)
- POJ 1228 凸包
- 第一步开发环境搭建以及编译链制作
- spark在hadoop2.2.0 HA配置下的问题
- 一个不良编程习惯引起的怪异bug
- 开启我的CSDN博客之旅。
- linux+arm 编译内核
- 2-12. 两个有序链表序列的交集(20)
- 第一课 MVC结构和Struts简介
- TextArea提交表单后显示的内容前出现大段的空格问题
- hdu 1009--greedy
- linux vi 搜索功能的使用
- Android开发之旅:环境搭建及HelloWorld