一个不良编程习惯引起的怪异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;    }}


这个现象反馈出一个很重要的问题,那就是编程习惯很重要,有些朋友注册了电话的监听,但是不会主动的取消注册,注册了监听之后,不手动的取消注册,这样就给程序带来了很多隐藏的问题,这样出现一个问题,需要解决耗费的时间,有时候不会是一个小时,两个小时,很有可能是一天,或者更长。



0 0