由于開了StrictMode,今天看log發現類似這種錯誤:
E/StrictMode: android.os.StrictMode$InstanceCountViolation: class com.XXX.XXX.XXX.XXX.LiveVideoActivity; instances=2; limit=1
E/StrictMode: android.os.StrictMode$InstanceCountViolation: class com.XXX.XXX.XXX.XXX.LiveVideoActivity; instances=3; limit=1
E/StrictMode: android.os.StrictMode$InstanceCountViolation: class com.XXX.XXX.XXX.XXX.LiveVideoActivity; instances=4; limit=1
E/StrictMode: android.os.StrictMode$InstanceCountViolation: class com.XXX.XXX.XXX.XXX.LiveVideoActivity; instances=5; limit=1
……
activity的執行個體在穩步增長,本來猜測是有些地方沒有釋放Context導緻,記憶體洩露,檢查了下代碼,把所有可能引起這個問題的地方都處理了,但是還沒是有問題,沒轍了,開始問度娘,問bing(就這個問題的搜尋結果有感 如果通路不了google,建議還是用bing....)終于在 Stack Overflow 找到了同樣的情況。
http://stackoverflow.com/questions/5956132/android-strictmode-instancecountviolation
大神給出了找出記憶體洩露的方法 ,如下:
after backing out from MyActivity and seeing the log message
make a heap dump (.hprof)
open it in Eclipse Memory Analyzer
run OQL: select * from instanceof full.package.name.of.MyActivity
select all with Ctrl+Click or Shift+Click
right click and Merge Shortest Path to GC Roots > with all references
最終抓到了 hprof檔案 ,分析方法在這篇文章中找到:
http://blog.csdn.net/ccwwff/article/details/7817139
運作cmd打開指令行,cd到\ android-sdk-windows\tools所在目錄,并輸入指令hprof-conv xxxxx.hprof yyyyy.hprof,其中xxxxx.hprof為原始檔案,yyyyy.hprof為轉換過後的檔案。轉換過後的檔案自動放在android-sdk-windows\tools 目錄下。
這裡有個問題,其實是在
<pre name="code" class="plain">hprof-conv是在 \ android-sdk-windows\platform-tools 裡
最終發現問題出在 PhoneStateListener 上。
原因是,我做了個需求 播放器在背景繼續保持運作,是以我需要監聽通話狀态,再來電話的時候,暫停播放 ,免得播放器搶占了音頻(此處有待商榷,應該有别的解決方案)。
我在網上随便查了下PhoneStateListener的用法,然後抄了下。。。但是沒人告訴我,這玩意會引起記憶體洩露。。。是以啊,随随便便查到的東西,千萬不要直接用到代碼裡,得仔細研究下。。。
老規矩 繼續查,在網上查到了這個東西需要解除監聽,大神說要這樣用:
You should try cleaning up on onPause and then onResume re-instate the stuff you need. This will help clean up some memory pressure and leaks.
解除監聽的方法是:
telephonyManager.listen(telePhonePhoneStateListener,PhoneStateListener.LISTEN_NONE);
但是我的需求是在背景也得繼續監聽啊,是以,我把這個方法扔到了onDestroy()裡。。。試了下,沒什麼卵用。。。沒辦法繼續。。。
找到了這個:
http://www.07net01.com/2015/08/898417.html
在項目開發過程中通過ddms的堆看到記憶體一直持續在增長,很容易想到發生記憶體洩露,引用沒有被釋放,通過dump 最終發現是 PhoneStateListener 内部對自己有一個強引用的handler,如果是在主線程中引用的PhoneStateListener,那麼他将釋放不掉,引發記憶體洩露。
解決方法就好的是在子線程中建立 PhoneStateListener ,其次是在PhoneStateListener中使用弱引用。比如需要activity的對象。那麼就要在建立PhoneStateListener的時候将傳入activity的弱引用 WeakRef ,這樣就不用擔心記憶體洩露的問題,不過這種弱引用的方式用起來簡單一些,但是他還有個小問題,就是PhoneStateListener自己是釋放不掉的,雖然他不再持有外部的一些引用,那麼就要求不要在PhoneStateListener裡面有過多的資源建立。
想試了下弱引用,發現沒搞懂這裡怎麼使用弱引用。。。
恩,那就用子線程,代碼如下:
moniterTekePhone = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
sonThreadlooper = Looper.myLooper();
telephonyManager = (TelephonyManager)mActivity.getSystemService(Context.TELEPHONY_SERVICE);
telePhonePhoneStateListener = new TelePhonePhoneStateListener(mDoTelePhonyWorkListener);
telephonyManager.listen(telePhonePhoneStateListener,PhoneStateListener.LISTEN_CALL_STATE);
Looper.loop();
}
});
<pre name="code" class="java">moniterTekePhone.start();
銷毀用,在onDestroy()中調用:
public void destoryMoniterTelePhone(){
if(telephonyManager != null){
telephonyManager.listen(telePhonePhoneStateListener,PhoneStateListener.LISTEN_NONE);
telephonyManager = null;
}
if(moniterTekePhone != null && moniterTekePhone.isAlive()){
if(sonThreadlooper != null){
try{
sonThreadlooper.quit();
}catch(Exception e){
e.printStackTrace();
}
}
sonThreadlooper = null;
}
}
試了下,還是未能成功解決問題。。。依然維持了對目前activity的引用。
百思不得其解,估計是binder的問題。
繼續百度,發現有另一種寫法:監聽廣播
IntentFilter intentFilterPhone = new IntentFilter();
intentFilterPhone.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
phoneReceiver.setTelePhonyWorkListener(mDoTelePhonyWorkListener);
mActivity.registerReceiver(phoneReceiver, intentFilterPhone);
重寫廣播:
public class PhoneReceiver extends BroadcastReceiver {
DoTelePhonyWorkListener telePhonyWorkListener;
public void setTelePhonyWorkListener(DoTelePhonyWorkListener listener){
telePhonyWorkListener = listener;
}
public interface DoTelePhonyWorkListener{
void callStateIdle();
void callStateRinging();
void callStateOffHook();
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
// 如果是去電(撥出)
} else {
TelephonyManager tm = (TelephonyManager) context
.getSystemService(Service.TELEPHONY_SERVICE);
// 設定一個監聽器
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
}
}
PhoneStateListener listener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
// state 目前狀态 incomingNumber,貌似沒有去電的API
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
if( telePhonyWorkListener != null){
telePhonyWorkListener.callStateRinging();
}
break;
case TelephonyManager.CALL_STATE_IDLE:
if(telePhonyWorkListener != null){
telePhonyWorkListener.callStateIdle();
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
if(telePhonyWorkListener != null){
telePhonyWorkListener.callStateOffHook();
}
break;
}
}
};
}
最後 在onDestroy中解除注冊廣播。
這會兒是真的解決了。。。。