轉(zhuǎn)載請注明原文地址:http://blog.csdn.net/mxm691292118/article/details/51020023
我把Android重難點(diǎn)和讀書筆記都整理在github上:https://github.com/miomin/AndroidDifficulty
如果你覺得對你有幫助的話,希望可以star/follow一下喲,我會持續(xù)保持更新。
一、內(nèi)存泄露
- 垃圾回收器無法回收原本應(yīng)該被回收的對象,這個(gè)對象就引發(fā)了內(nèi)存泄露。
- 內(nèi)存泄露的危害:
- (1)過多的內(nèi)存泄露最終會導(dǎo)致內(nèi)存溢出(OOM)
- (2)內(nèi)存泄露導(dǎo)致可用內(nèi)存不足,會觸發(fā)頻繁GC,不管是Android2.2以前的單線程GC還是現(xiàn)在的CMS和G1,都有一部分的操作會導(dǎo)致用戶線程停止(就是所謂的Stop the world),從而導(dǎo)致UI卡頓。
二、內(nèi)存溢出(OOM)
android為每個(gè)進(jìn)程設(shè)置Dalvik Heap Size閾值,這個(gè)閾值在不同的設(shè)備上會因?yàn)镽AM大小不同而各有差異。如果APP想要分配的內(nèi)存超過這個(gè)閾值,就會發(fā)生OOM。
ActivityManager.getMemoryClass()可以查詢當(dāng)前APP的Heap Size閾值,單位是MB。
在3.x以前,Bitmap分配在Native heap中,而在4.x之后,Bitmap分配在Dalvik或ART的Java heap中。
Android 2.x系統(tǒng),當(dāng)dalvik allocated + native allocated + 新分配的大小 >= dalvik heap 最大值時(shí)候就會發(fā)生OOM,也就是說在2.x系統(tǒng)中,考慮native heap對每個(gè)進(jìn)程的內(nèi)存限制。
- Android 4.x系統(tǒng),廢除了native的計(jì)數(shù)器,類似bitmap的分配改到dalvik的java heap中申請,只要allocated + 新分配的內(nèi)存 >= dalvik heap 最大值的時(shí)候就會發(fā)生OOM(art運(yùn)行環(huán)境的統(tǒng)計(jì)規(guī)則還是和dalvik保持一致),也就是說在4.x系統(tǒng)中,不考慮native heap對每個(gè)進(jìn)程的內(nèi)存限制,native heap只會收到本機(jī)總內(nèi)存(包括RAM以及SWAP區(qū)或分頁文件)的限制。
三、如何避免內(nèi)存泄漏
參考在MDCC 2015中國移動開發(fā)者大會上胡凱前輩的講述,整理總結(jié)。
1、使用輕量的數(shù)據(jù)結(jié)構(gòu)
2、不要使用Enum
3、大胖子Bitmap的處理
- Bitmap壓縮
- Lru機(jī)制處理Bitmap(下一節(jié)博客會詳細(xì)講解),也可以使用那些有名的圖片緩存框架。
4、不要使用String進(jìn)行字符串拼接
5、非靜態(tài)內(nèi)部類內(nèi)存泄露
在Activity中創(chuàng)建非靜態(tài)內(nèi)部類,非靜態(tài)內(nèi)部類會持有Activity的隱式引用,若內(nèi)部類生命周期長于Activity,會導(dǎo)致Activity實(shí)例無法被回收。(屏幕旋轉(zhuǎn)后會重新創(chuàng)建Activity實(shí)例,如果內(nèi)部類持有引用,將會導(dǎo)致旋轉(zhuǎn)前的實(shí)例無法被回收)。
解決方案:如果一定要使用內(nèi)部類,就改用static內(nèi)部類,在內(nèi)部類中通過WeakReference的方式引用外界資源。
正確的代碼示例:
static class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private WeakReference<PhotoAdapter> photoAdapter;
public ImageDownloadTask(PhotoAdapter photoAdapter) {
this.photoAdapter = new WeakReference<PhotoAdapter>(photoAdapter);
}
@Override
protected Bitmap doInBackground(String... params) {
//在后臺開始下載圖片
url = params[0];
Bitmap bitmap = photoAdapter.get().loadBitmap(url);
if (bitmap != null) {
//把下載好的圖片放入LruCache中
String key = MD5Tools.decodeString(url);
photoAdapter.get().put(key, bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//把下載好的圖片顯示出來
ImageView mImageView = (ImageView) photoAdapter.get().mGridView.get().findViewWithTag(MD5Tools.decodeString(url));
if (mImageView != null && bitmap != null) {
mImageView.setImageBitmap(bitmap);
photoAdapter.get().mDownloadTaskList.remove(this);//把下載好的任務(wù)移除
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
6、匿名內(nèi)部類內(nèi)存泄漏
private static class MyHandler extends Handler {
private final WeakReference<Context> context;
private MyHandler(Context context) {
this.context = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
}
}
}
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// 發(fā)送一個(gè)10分鐘后執(zhí)行的一個(gè)消息
mHandler.postDelayed(sRunnable, 600000);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
private static class MyThread extends Thread {
@Override
public void run() {
while (true) {
// TODO 耗時(shí)任務(wù)
}
}
}
new MyThread().start();
7、Context持有導(dǎo)致內(nèi)存泄漏
- Activity Context被傳遞到其他實(shí)例中,這可能導(dǎo)致自身被引用而發(fā)生泄漏。
- 解決:對于大部分非必須使用Activity Context的情況(創(chuàng)建Dialog的Context必須是Activity Context),應(yīng)該使用Application Context。
8、記得注銷監(jiān)聽器
- 注冊監(jiān)聽器的時(shí)候會add Listener,不要忘記在不需要的時(shí)候remove掉Listener。
9、資源文件需要選擇合適的文件夾進(jìn)行存放
- hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設(shè)備上會經(jīng)過scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片,那么根據(jù)換算關(guān)系,xxhdpi的手機(jī)去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,內(nèi)存占用是會顯著提高的。對于不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。
10、謹(jǐn)慎使用static對象
- static對象的生命周期過長,應(yīng)該謹(jǐn)慎使用
11、謹(jǐn)慎使用單例中不合理的持有
- 單例中的對象生命周期與應(yīng)用一致,注意不要在單例中進(jìn)行不必要的外界引用持有。如果一定要引用外部變量,需要在外部變量生命周期結(jié)束的時(shí)候接觸引用(賦為null)。
12、一定要記得關(guān)閉無用連接
- 在onDestory中關(guān)閉Cursor,I/O,數(shù)據(jù)庫,網(wǎng)絡(luò)的連接用完記得關(guān)閉。
注意:謹(jǐn)慎使用lager heap
- 不同的設(shè)備有不容的RAM,他們?yōu)閼?yīng)用程序設(shè)置了不同大小的Heap的閾值。雖然可以通過largeHeap=true的屬性來為應(yīng)用獲得一個(gè)更大的heap空間,然后通過getLargeMemoryClass()來獲取到這個(gè)更大的heap閾值。但是你要注意,largeHeap只是為了一些本來就需要大量內(nèi)存的APP存在,比如圖墻和圖片編輯軟件。所以,不要隨意的使用large heap,否則會影響系統(tǒng)整體的用戶體驗(yàn),會使每次gc時(shí)間更長。
四、內(nèi)存泄露檢測
public class MyApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
- 想要檢測更多,首先注冊一個(gè)RefWatcher:
public class MyApplication extends Application {
private static RefWatcher sRefWatcher;
@Override
public void onCreate() {
super.onCreate();
sRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher() {
return sRefWatcher;
}
}
- 然后對某個(gè)可能發(fā)生泄露的占用大內(nèi)存的對象進(jìn)行監(jiān)測:
MyApplication.getRefWatcher().watch(sLeaky);
- 對Fragment、BroadcastReceiver、Service進(jìn)行監(jiān)測:
public class MyFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
MyApplication.getRefWatcher().watch(this);
}
}
參考文獻(xiàn)
|