轉載請注明原文地址:http://blog.csdn.net/mxm691292118/article/details/51020023
我把Android重難點和讀書筆記都整理在github上:https://github.com/miomin/AndroidDifficulty
如果你覺得對你有幫助的話,希望可以star/follow一下喲,我會持續保持更新。
android為每個進程設置Dalvik Heap Size閾值,這個閾值在不同的設備上會因為RAM大小不同而各有差異。如果APP想要分配的內存超過這個閾值,就會發生OOM。
ActivityManager.getMemoryClass()可以查詢當前APP的Heap Size閾值,單位是MB。
在3.x以前,Bitmap分配在Native heap中,而在4.x之后,Bitmap分配在Dalvik或ART的Java heap中。
Android 2.x系統,當dalvik allocated + native allocated + 新分配的大小 >= dalvik heap 最大值時候就會發生OOM,也就是說在2.x系統中,考慮native heap對每個進程的內存限制。
參考在MDCC 2015中國移動開發者大會上胡凱前輩的講述,整理總結。
1、使用輕量的數據結構
使用ArrayMap/SparseArray來代替HashMap,ArrayMap/SparseArray是專門為移動設備設計的高效的數據結構。
HashMap實現原理
HashMap內部使用一個默認容量為16的數組來存儲數據,采用拉鏈法解決hash沖突(數組+鏈表),如下圖:
Entry存儲的內容有key、value、hash值、next指針,通過計算hash(key)%len找到Entry在數組中的位置。
SparseArray
ArrayMap
2、不要使用Enum
3、大胖子Bitmap的處理
4、不要使用String進行字符串拼接
嚴格的講,String拼接只能歸結到內存抖動中,因為產生的String副本能夠被GC,不會造成內存泄露。
頻繁的字符串拼接,使用StringBuffer或者StringBuilder代替String,可以在一定程度上避免OOM和內存抖動。
5、非靜態內部類內存泄露
在Activity中創建非靜態內部類,非靜態內部類會持有Activity的隱式引用,若內部類生命周期長于Activity,會導致Activity實例無法被回收。(屏幕旋轉后會重新創建Activity實例,如果內部類持有引用,將會導致旋轉前的實例無法被回收)。
解決方案:如果一定要使用內部類,就改用static內部類,在內部類中通過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);//把下載好的任務移除 } } }
6、匿名內部類內存泄漏
跟非靜態內部類一樣,匿名內部類也會持有外部類的隱式引用,比較常見的情況有,耗時Handler,耗時Thread,都會造成內存泄漏,解決方式也是static+WeakReference,下面給出正確寫法。
Handler的正確寫法:
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() { }};@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_home); // 發送一個10分鐘后執行的一個消息 mHandler.postDelayed(sRunnable, 600000);}
private static class MyThread extends Thread { @Override public void run() { while (true) { // TODO 耗時任務 } }}new MyThread().start();
7、Context持有導致內存泄漏
8、記得注銷監聽器
9、資源文件需要選擇合適的文件夾進行存放
10、謹慎使用static對象
11、謹慎使用單例中不合理的持有
12、一定要記得關閉無用連接
注意:謹慎使用lager heap
這里介紹LeakCanary,一款非常好用的內存泄露檢測工具,安裝在手機上,能夠通過Log的方式告訴你是哪塊代碼發生了內存泄露。
使用方法,在Application中install LeakCanary(默認只能檢測Activity內容的內存泄露):
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); }}
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; }}
MyApplication.getRefWatcher().watch(sLeaky);
public class MyFragment extends Fragment { @Override public void onDestroy() { super.onDestroy(); MyApplication.getRefWatcher().watch(this); }}