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