內存泄漏也稱做「存儲滲漏」,用動態存儲分配函數動態開闢的空間,在使用完畢後未釋放,結果致使一直佔據該內存單元。直到程序結束。(其實說白了就是該內存空間使用完畢以後未回收)即所謂內存泄漏。html
內存泄漏形象的比喻是「操做系統可提供給全部進程的存儲空間正在被某個進程榨乾」,最終結果是程序運行時間越長,佔用存儲空間愈來愈多,最終用盡所有存儲空間,整個系統崩潰。因此「內存泄漏」是從操做系統的角度來看的。這裏的存儲空間並非指物理內存,而是指虛擬內存大小,這個虛擬內存大小取決於磁盤交換區設定的大小。由程序申請的一塊內存,若是沒有任何一個指針指向它,那麼這塊內存就泄漏了。java
——來自《百度百科》android
內存泄露實施後,項目的收穫:git
Contextgithub
Serviceshell
Handlerapp
Thread框架
慎用Context異步
善用Referenceide
類型 | 垃圾回收時間 | 生存時間 |
---|---|---|
強引用 | 永遠不會 | JVM中止運行時終止 |
軟引用 | 內存不足時 | 內存不足時終止 |
弱引用 | 垃圾回收時 | 垃圾回收時終止 |
虛引用 | 垃圾回收時 | 垃圾回收時終止 |
複用ConvertView
對象釋放
原理
根本緣由
怎麼解決
實踐分析
StrictMode
onCreate()
方法加上StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy .Builder() .detectAll() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy .Builder() .detectAll() .penaltyLog() .build());
Leakcanary
Leakcanary + StrictMode + monkey (推薦)
Adb命令
Android Monitor
MAT
Bitmap泄露
Bitmap泄露通常會泄露較多內存,視圖片大小、位圖而定
經典場景:App啓動圖
解決內存泄露先後內存相差10M+,可謂驚人
解決方案:
App啓動圖Activity的onDestroy()
中及時回收內存
@Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); recycleImageView(imgv_load_ad); } public static void recycleImageView(View view){ if(view==null) return; if(view instanceof ImageView){ Drawable drawable=((ImageView) view).getDrawable(); if(drawable instanceof BitmapDrawable){ Bitmap bmp = ((BitmapDrawable)drawable).getBitmap(); if (bmp != null && !bmp.isRecycled()){ ((ImageView) view).setImageBitmap(null); bmp.recycle(); bmp=null; } } } }
IO流未關閉
分析:經過日誌可知FileOutputStream()
未關閉
問題代碼:
public static void copyFile(File source, File dest) { FileChannel inChannel = null; FileChannel outChannel = null; Log.i(TAG, "source path: " + source.getAbsolutePath()); Log.i(TAG, "dest path: " + dest.getAbsolutePath()); try { inChannel = new FileInputStream(source).getChannel(); outChannel = new FileOutputStream(dest).getChannel(); inChannel.transferTo(0, inChannel.size(), outChannel); } catch (IOException e) { e.printStackTrace(); } }
解決方案:
public static void copyFile(File source, File dest) { FileChannel inChannel = null; FileChannel outChannel = null; Log.i(TAG, "source path: " + source.getAbsolutePath()); Log.i(TAG, "dest path: " + dest.getAbsolutePath()); try { inChannel = new FileInputStream(source).getChannel(); outChannel = new FileOutputStream(dest).getChannel(); inChannel.transferTo(0, inChannel.size(), outChannel); } catch (IOException e) { e.printStackTrace(); } finally { if (inChannel != null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (outChannel != null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
E/StrictMode: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks. java.lang.Throwable: Explicit termination method 'close' not called at dalvik.system.CloseGuard.open(CloseGuard.java:180) at java.io.FileOutputStream.<init>(FileOutputStream.java:89) at java.io.FileOutputStream.<init>(FileOutputStream.java:72) at com.heyniu.lock.utils.FileUtil.copyFile(FileUtil.java:44) at com.heyniu.lock.db.BackupData.backupData(BackupData.java:89) at com.heyniu.lock.ui.HomeActivity$11.onClick(HomeActivity.java:675) at android.support.v7.app.AlertController$ButtonHandler.handleMessage(AlertController.java:157) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
單例模式泄露
分析:經過截圖咱們發現SplashActivity被ActivityUtil的實例activityStack持有
引用代碼:
ActivityUtil.getAppManager().add(this);
public void add(Activity activity) { if (activityStack == null) { synchronized (ActivityUtil.class){ if (activityStack == null) { activityStack = new Stack<>(); } } } activityStack.add(activity); }
解決方案:
onDestroy()
生命週期移除引用@Override protected void onDestroy() { super.onDestroy(); ActivityUtil.getAppManager().remove(this); }
靜態變量持有Context實例泄露
分析:長生命週期持有短什麼週期引用致使泄露,詳見上文四大組件Context和Application的context使用
示例引用代碼:
private static HttpRequest req; public static void HttpUtilPost(Context context, int TaskId, String url, String requestBody,ArrayList<HttpHeader> Headers, RequestListener listener) { // TODO Auto-generated constructor stub req = new HttpRequest(context, url, TaskId, requestBody, Headers, listener); req.post(); }
解決方案:
public static void cancel(int TaskId) { if(req != null && req.get() != null){ req.get().AsyncCancel(TaskId); } }
private static WeakReference<HttpRequest> req; public static void HttpUtilPost(Context context, int TaskId, String url, String requestBody,ArrayList<HttpHeader> Headers, RequestListener listener) { // TODO Auto-generated constructor stub req = new WeakReference<HttpRequest>(new HttpRequest(context, url, TaskId, requestBody, Headers, listener)); req.get().post(); }
private static HttpRequest req; public static void HttpUtilPost(Context context, int TaskId, String url, String requestBody,ArrayList<HttpHeader> Headers, RequestListener listener) { // TODO Auto-generated constructor stub req = new HttpRequest(context.getApplicationContext(), url, TaskId, requestBody, Headers, listener); req.post(); }
Context泄露
Callback泄露
服務未解綁註冊泄露
分析:通常發生在註冊了某服務,不用時未解綁服務致使泄露
引用代碼:
private void initSensor() { // 獲取傳感器管理器 sm = (SensorManager) container.activity.getSystemService(Context.SENSOR_SERVICE); // 獲取距離傳感器 acceleromererSensor = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); // 設置傳感器監聽器 acceleromererListener = new SensorEventListener() { ...... }; sm.registerListener(acceleromererListener, acceleromererSensor, SensorManager.SENSOR_DELAY_NORMAL); }
解決方案:
onDestroy()
方法解綁服務@Override protected void onDestroy() { super.onDestroy(); sm.unregisterListener(acceleromererListener,acceleromererSensor); }
Handler泄露
分析:因爲Activity已經關閉,Handler任務還未執行完成,其引用了Activity的實例致使內存泄露
引用代碼:
handler.sendEmptyMessage(0);
解決方案:
onDestroy()
方法回收Handler@Override protected void onDestroy() { super.onDestroy(); handler.removeCallbacksAndMessages(null); }
異步線程泄露
分析:通常發生在線程執行耗時操做時,以下載,此時Activity關閉後,因爲其被異步線程引用,致使沒法被正常回收,從而內存泄露
引用代碼:
new Thread() { public void run() { imageArray = loadImageFromUrl(imageUrl); }.start();
解決方案:
onDestroy()
方法阻塞線程thread = new Thread() { public void run() { imageArray = loadImageFromUrl(imageUrl); }; thread.start(); @Override protected void onDestroy() { super.onDestroy(); if(thread != null){ thread.interrupt(); thread = null; } }