本文的合集已經(jīng)編著成書趁蕊,高級Android開發(fā)強化實戰(zhàn)淤毛,歡迎各位讀友的建議和指導胖腾。在京東即可購買:https://item.jd.com/12385680.html
LeakCanary是檢測App內(nèi)存泄露的工具, 內(nèi)存泄露是Android開發(fā)中常見的問題, 使用程序的穩(wěn)定性下降.
注意: 集成到低版本應用, 會報出Bug.
Error:(2) Error retrieving parent for item: No resource found that matches the given name 'android:Theme.Material'.
修改編譯版本: compileSdkVersion 21 即可.
本文示例的Github下載地址.
主要原因:
生命周期較長的類使用Activity的Context, 導致Activity被引用, 無法被及時回收. 除了需要Activity頁面支持的控件類, 如Dialog等, 其他全部使用應用的Context替換Activity的Context, 即Context.getApplicationContext(). 還有就是單例不要持有頁面的控件, 單例持有控件, 控件附屬頁面, 最終頁面得不到釋放, 單例可以使用回調(diào)修改頁面, 內(nèi)部僅僅保留處理數(shù)據(jù)部分.
LeakCanary可以檢查出頁面的泄露問題, 并提供具體位置.
表明: LeakSingle的靜態(tài)單例sInstance引用LeakSingle的mContext, 從而導致MainActivity的instance泄露.
內(nèi)存泄露的原因已經(jīng)理解了, 那么我來講解一下如何檢測應用吧.
1. 依賴和引入
build.gradle的依賴.
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // or 1.4-beta1
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
引入應用
public class DemoApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
配置非常簡單, 會增加一個附屬應用, 去掉Application的引用, 就可以移除LeakCanary.
2. 泄露單例
泄露單例, 引入頁面的TextView, 強制保留的父Activity, 會導致內(nèi)存泄露, 可以在onDestroy時, 解引用避免.
/**
* 泄露單例, 設計混亂, 單例只應該做事務性的工作, 頁面操作應該使用回調(diào).
* 本示例僅做為反面示例, 切勿學習.
* <p/>
* Created by wangchenlong on 16/1/25.
*/
public class LeakSingle {
private Context mContext;
private TextView mTextView;
private static LeakSingle sInstance;
private LeakSingle(Context context) {
mContext = context;
}
public static LeakSingle getInstance(Context context) {
if (sInstance == null) {
sInstance = new LeakSingle(context);
}
return sInstance;
}
// 內(nèi)存泄露
public void setRetainedTextView(TextView tv) {
mTextView = tv;
mTextView.setText(mContext.getString(R.string.app_name));
}
// 刪除引用, 防止泄露
public void removeRetainedTextView() {
mTextView = null;
}
}
單例只應該做事務性的工作, 頁面操作應該使用回調(diào), 不是引入控件. 本示例僅做為反面示例, 切勿學習.
3. 泄露內(nèi)存
調(diào)用單例, 兩種引用都會導致內(nèi)存泄露, 第一種是Context引用泄露, 第二種是子控件引用泄露. 避免方式是在onDestroy中, 清除引用.
最優(yōu)方式:
在單例中只執(zhí)行事務性工作, 不執(zhí)行具體頁面操作, 可以使用接口回調(diào), 異步處理.
public class MainActivity extends AppCompatActivity {
@Bind(R.id.main_tv_text) TextView mTvText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
/**
* me.chunyu.spike.wcl_leakcanary_demo.MainActivity has leaked:
* GC ROOT static me.chunyu.spike.wcl_leakcanary_demo.LeakSingle.sInstance
* references me.chunyu.spike.wcl_leakcanary_demo.LeakSingle.mContext
* leaks me.chunyu.spike.wcl_leakcanary_demo.MainActivity instance
*/
// LeakSingle.getInstance(this).setRetainedTextView(mTvText);
/**
* me.chunyu.spike.wcl_leakcanary_demo.MainActivity has leaked:
* GC ROOT static me.chunyu.spike.wcl_leakcanary_demo.LeakSingle.sInstance
* references me.chunyu.spike.wcl_leakcanary_demo.LeakSingle.mTextView
* references android.support.v7.widget.AppCompatTextView.mContext
* leaks me.chunyu.spike.wcl_leakcanary_demo.MainActivity instance
*/
LeakSingle.getInstance(this.getApplication()).setRetainedTextView(mTvText);
}
@Override protected void onDestroy() {
super.onDestroy();
// 防止內(nèi)泄露
LeakSingle.getInstance(this.getApplication()).removeRetainedTextView();
}
}
根據(jù)LeakCanary中的檢測結果, 修改內(nèi)存泄露的地方, 就可以完美的解決問題.
4. 解析泄露位置
新手程序員經(jīng)常會問我, 如何理解LeakCanary的錯誤信息, 我來舉個例子講解一些.
如圖:
LeakCanary的內(nèi)存泄露提示一般會包含三個部分:
第一部分(LeakSingle類的sInstance變量)引用第二部分(LeakSingle類的mContext變量), 導致第三部分(MainActivity類的實例instance)泄露.
應用最常見的泄露位置就是Activity的實例, 要手動或使用shell命令, 啟動所有的Activity. LeakCanary判斷時機是Activity啟動到結束, 檢測這一過程是否會發(fā)生內(nèi)存泄露.
啟動Activity的shell命令是
adb shell am start -n [包名]/[Activity名]
詳細內(nèi)容參考第六點.
內(nèi)存泄露的問題對于應用的用戶體驗至關重要, 感謝Square的產(chǎn)品, 讓這件事變得如此簡單.
OK, that's all! Enjoy it!