PhoneStateListener memery leaked ( LeakCanary ) 手機來電狀態監聽,泄漏無法解決

1、問題

用LeakCanary 分析手機app的泄漏情況, 發現有監聽手機狀態的界面全部泄漏,試了很久,都沒有改成功

2、手機來電狀態監聽

2.1、權限

   <uses-permission android:name="android.permission.READ_PHONE_STATE" />

2.2、普通代碼(會泄漏)

//註冊
    public void register(PhoneStateListener listener) {
        TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
        tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
    }

//銷燬
   public void unRegister(PhoneStateListener listener) {

        if (tm != null) {
            tm.listen(listener, PhoneStateListener.LISTEN_NONE);
            tm = null;
        }

    }

3、改爲弱引用, 子線程註冊 (依舊泄漏)

    TelephonyManager tm;
    WeakReference<Context> weakReference;

//註冊
    public void register(PhoneStateListener listener) {


        new Thread(new Runnable() {
            @Override
            public void run() {

        tm = (TelephonyManager) weakReference.get().getSystemService(Service.TELEPHONY_SERVICE);
        tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);

            }
        }).start();


    }

//銷燬
   public void unRegister(PhoneStateListener listener) {

        if (tm != null) {
            tm.listen(listener, PhoneStateListener.LISTEN_NONE);
            tm = null;
        }

    }

4、結論

查的幾種方式 是都會泄漏, 不管listener ,tm 最終設置爲null 還是弱引用,子線程操作什麼的。

5、改進(無法釋放, 規避 tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE) 概率)

在做手機來電狀態監聽時, 直接操作TelephonyManager 的方法是方便了很多,但是會使得context一直持有,泄漏。 改進方式是把使用方式寫全,先監聽電話廣播,識別到是來電,再用TelephonyManager設置PhoneStateListener,這樣子就只有來電時那個 Activity 是泄漏的, 並且使用弱引用。(沒使用子線程註冊)。

public class LogicTelephonyManager {
    final private String TAG = "LogicTelephonyManager";

    TelephonyManager tm;
    WeakReference<Context> weakReference;
    IntentFilter intentFilterPhone;
    PhoneReceiver receiver;

    public LogicTelephonyManager(Context context) {


        // this.context = context;
        weakReference = new WeakReference<Context>(context);

        intentFilterPhone = new IntentFilter();
        intentFilterPhone.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
        receiver = new PhoneReceiver();
        context.registerReceiver(receiver, intentFilterPhone);
    }


//註冊 
    public void register(PhoneStateListener listener) {


//        new Thread(new Runnable() {
//            @Override
//            public void run() {

        tm = (TelephonyManager) weakReference.get().getSystemService(Service.TELEPHONY_SERVICE);
        tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);

//            }
//        }).start();


    }


//銷燬
    public void unRegister(PhoneStateListener listener) {

        if (context != null) {
            context.unregisterReceiver(receiver);
        }

        if (tm != null) {
            tm.listen(listener, PhoneStateListener.LISTEN_NONE);
            tm = null;
        }

    }

    public class PhoneReceiver extends BroadcastReceiver {


        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
                // 如果是去電(撥出)
            } else {
                //不是去電當成來電

                //TODO:可以在這裏做註冊。 不過我的業務是把他發射出去在接收端進行註冊綁定 listener
                EventBus.getDefault().post(new PhoneReceiverEvent());
            }
        }
    }

}

場景類

public class XXP {




        LogicTelephonyManager telephonyManagerUtil;


        public XXP(Activity activity) {

EventBus.getDefault().register(this);



            telephonyManagerUtil = new LogicTelephonyManager(activity);
}




        @Subscribe 

        public void onEvent(PhoneReceiverEvent event) {

if (event != null) {

            LogDebugUtil.i("LogicTelephonyManager", "PhoneReceiverEvent = " + event);

            if (telephonyManagerUtil != null) {
telephonyManagerUtil.register(listener);
}
}
}


        public void onDestroy() {
EventBus.getDefault().unregister(this);



            if (telephonyManagerUtil != null) {
telephonyManagerUtil.unRegister(listener);
}

            System.gc();
}






        PhoneStateListener listener = new PhoneStateListener() {

            @Override
            public void onCallStateChanged(int state, String incomingNumber) {
                //注意,方法必須寫在super方法後面,否則incomingNumber無法獲取到值。
                super.onCallStateChanged(state, incomingNumber);
                switch (state) {
                    case TelephonyManager.CALL_STATE_IDLE:
                        LogDebugUtil.d(TAG, "掛斷");
                        break;
                    case TelephonyManager.CALL_STATE_OFFHOOK:
                        LogDebugUtil.d(TAG, "接聽");


                        break;
                    case TelephonyManager.CALL_STATE_RINGING:
                        LogDebugUtil.d(TAG, "響鈴:來電號碼" + incomingNumber);
                        //輸出來電號碼


                        break;
                }
            }
        };
    }

6、補充

這個或許就是爲什麼要註冊廣播, 再註冊監聽方法吧。 因爲監聽後無法釋放 也是尷尬。 有能路過能解決的 倒是可以留言指導下

發佈了145 篇原創文章 · 獲贊 58 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章