原文地址:http://blog.csdn.net/feng8888bbb/article/details/70161455
我們知道,Dalvik虛擬機實則也算是一個Java虛擬機,只不過它執行的不是class文件,而是dex文件。雖然Android 4.4發佈了一個ART運行時,準備用來替換掉之前一直使用的Dalvik虛擬機,希望籍此解決飽受詬病的性能問題。但是這裏我們先通過羅昇陽的Dalvik虛擬機簡要介紹和學習計劃瞭解Dalvik虛擬機的內存管理與垃圾回收,對我們分析android的內存優化會有很多的啓示的。
內存泄漏
內存溢出
避免內存泄漏
- public class HomeActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.act_home);
- findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- startAsyncTask();
- }
- });
- }
- /*關於隱式引用----內部類可以直接去調用外部類的成員(屬性和方法),
- 如果沒有持有外部類的引用,內部類是沒辦法去調用外部類的成員,
- 但是內部類又沒有顯示的去指定聲明引用,所以稱之爲隱式引用。*/
- /*AsyncTask是一個匿名的內部類,隱式的持有外部類(MainActivity)的引用,
- 當activity被銷燬的時候,如果AsyncTask(代碼sleep 20秒,模擬了一個耗時操作)
- 沒有執行完成,則MainActivity將會泄漏*/
- void startAsyncTask() {
- // This async task is an anonymous class and therefore has a hidden reference to the outer
- // class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),
- // the activity instance will leak.
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- // Do some slow work in background
- SystemClock.sleep(20000);
- return null;
- }
- }.execute();
- }
- }
- package com.example.guoliuya.memorytest;
- import android.content.Context;
- /**
- * Created by idea on 2016/12/30.
- * 單例模式持有context 對象引發內存泄漏示例
- */
- public class MyInstanceTest {
- private Context context;
- private static MyInstanceTest mInstance;
- public static MyInstanceTest getInstance(Context context) {
- if (mInstance == null) {
- synchronized (MyInstanceTest.class) {
- if (mInstance == null)
- mInstance = new MyInstanceTest(context);
- //解決方法 把context的引用替換成ApplicationContext的引用
- mInstance = new MyInstanceTest(context.getApplicationContext());
- }
- }
- return mInstance;
- }
- private MyInstanceTest(Context context) {
- this.context = context;
- }
- }
- public class HomeActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.act_home);
- test();
- }
- //加上static,變成靜態匿名內部類
- public static void test() {
- //匿名內部類會引用其外圍實例HomeActivity.this,所以會導致內存泄漏
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- try {
- Thread.sleep(10000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }).start();
- }
- //解決方法
- public class HomeActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.act_home);
- test();
- }
- //加上static,變成靜態匿名內部類
- public static void test() {
- //匿名內部類會引用其外圍實例HomeActivity.this,所以會導致內存泄漏
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- try {
- Thread.sleep(10000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }).start();
- }
- }
- public class HomeActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.act_home);
- findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- loadData();
- }
- });
- }
- private Handler mHandler = new Handler() {
- public void handleMessage(android.os.Message msg) {
- switch (msg.what) {
- case 0:
- // 刷新數據
- break;
- default:
- break;
- }
- };
- };
- private void loadData() {
- //獲取數據
- mHandler.sendEmptyMessage(0);
- }
- }
- //解決方法
- //第一步,將Handler改成靜態內部類。
- private static class MyHandler extends Handler {
- //第二步,將需要引用Activity的地方,改成弱引用。
- private WeakReference<HomeActivity> homaActivityInstance;
- public MyHandler(HomeActivity hai) {
- this.homaActivityInstance = new WeakReference<HomeActivity>(hai);
- }
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- HomeActivity aty = homaActivityInstance == null ? null : homaActivityInstance.get();
- //如果Activity被釋放回收了,則不處理這些消息
- if (aty == null||aty.isFinishing()) {
- return;
- }
- }
- }
- private void loadData() {
- // 獲取數據
- myHandler.sendEmptyMessage(0);
- }
- @Override
- protected void onDestroy() {
- //第三步,在Activity退出的時候移除回調
- super.onDestroy();
- myHandler.removeCallbacksAndMessages(null);
- }
總結:
2.Cursor遊標結果集,I/O流,數據庫,網絡的連接用完及時關閉。
資源性對象比如(Cursor,File文件等)往往都用了一些緩衝,我們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時回收內存。它們的緩衝不僅存在於 java虛擬機內,還存在於java虛擬機外。如果我們僅僅是把它的引用設置爲null,而不關閉它們,往往會造成內存泄漏。因爲有些資源性對象,比如 SQLiteCursor(在析構函數finalize(),如果我們沒有關閉它,它自己會調close()關閉),如果我們沒有關閉它,系統在回收它時也會關閉它,但是這樣的效率太低了。因此對於資源性對象在不使用的時候,應該調用它的close()函數,將其關閉掉,然後才置爲null.在我們的程序退出時一定要確保我們的資源性對象已經關閉。
3.在Android程序裏面存在很多需要register 和 unregister的監聽器,我們需要確保及時unregister監聽器
4.使用ArrayMap/SparseArray來代替HashMap,ArrayMap/SparseArray是專門爲移動設備設計的高效的數據結構。
5.不要輕易使用Enum
這點在Google的Android官方培訓課程提到過,具體可以參考胡凱前輩的《
Android性能優化典範(三)》
6.避免創建不必要的對象
最好能重用對象而不是在每次需要的時候就創建一個相同功能的新對象
String s = new String("hello world"); //don't do this!
使用單例模式
當心無意識的自動裝箱
public static void main(String[] args) { Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); }
7.資源文件需要選擇合適的文件夾進行存放
hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設備上會經過scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片,那麼根據換算關係,xxhdpi的手機去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,內存佔用是會顯著提高的。對於不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。
8.謹慎使用static對象
static對象的生命週期過長,應該謹慎使用
9.不要使用String進行字符串拼接
嚴格的講,String拼接只能歸結到內存抖動中,因爲產生的String副本能夠被GC,不會造成內存泄露。
頻繁的字符串拼接,使用StringBuffer(不建議使用)或者StringBuilder代替String,可以在一定程度上避免OOM和內存抖動。
10.非靜態內部類內存泄露
在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);//把下載好的任務移除
- }
- }
- }
11.匿名內部類內存泄漏
跟非靜態內部類一樣,匿名內部類也會持有外部類的隱式引用,比較常見的情況有,耗時Handler,耗時Thread,都會造成內存泄漏,解決方式也是static+WeakReference。
12.webview對象沒有及時的destroy
一般情況下我們在activity的destory()方法裏面會調用webView.destory(),但注意在android5.1之後,這樣做會有引起內存泄漏的風險。具體可參考Android
5.1 WebView內存泄漏分析
13.慎用Services
service用於在後臺執行一些耗時操作,只用當它執行任務的時候纔開啓,否則其他時刻都應該不工作,service完成任務之後要主動停止,否則如果用戶發現有常駐後臺行爲的應用並且可能卸載它甚至引起內存泄漏。
當你開啓一個service,系統會傾向爲了保留這個service而一直保留service所在的進程。這使得進程的運行代價很高,因爲系統沒有辦法把service所佔用的RAM空間騰出來讓給其他組件。
推薦使用IntentService, 它會在工作線程處理完交代給它的intent任務之後自動停止。
IntentService是Service類的子類,用來處理異步請求。客戶端可以通過startService(Intent)
方法傳遞請求給IntentService。IntentService在onCreate()函數中通過HandlerThread單獨
開啓一個線程來處理所有Intent請求對象(通過startService的方式發送過來的)所對應的任務,這
樣以免事務處理阻塞主線程。執行完所一個Intent請求對象所對應的工作之後,如果沒有新的Intent
請求達到,則自動停止Service;否則執行下一個Intent請求所對應的任務。
IntentService在處理事務時,還是採用的Handler方式,創建一個名叫ServiceHandler的內部
Handler,並把它直接綁定到HandlerThread所對應的子線程。 ServiceHandler把處理一個intent
所對應的事務都封裝到叫做onHandleIntent的虛函數;因此我們直接實現虛函數onHandleIntent,再
在裏面根據Intent的不同進行不同的事務處理就可以了。
另外,IntentService默認實現了Onbind()方法,返回值爲null。
使用IntentService需要兩個步驟:
1、寫構造函數
2、實現虛函數onHandleIntent,並在裏面根據Intent的不同進行不同的事務處理就可以了。
好處:處理異步請求的時候可以減少寫代碼的工作量,比較輕鬆地實現項目的需求
注意:IntentService的構造函數一定是參數爲空的構造函數,然後再在其中調用super("name")這種形式的構造函數。
因爲Service的實例化是系統來完成的,而且系統是用參數爲空的構造函數來實例化Service的