Android內存洩露實戰解析。本站提示廣大學習愛好者:(Android內存洩露實戰解析)文章只能為提供參考,不一定能成為您想要的結果。以下是Android內存洩露實戰解析正文
Java是渣滓收受接管說話的一種,其長處是開辟者無需特地治理內存分派,下降了運用因為部分毛病(segmentation fault)招致瓦解,同時避免未釋放的內存把客棧(heap)擠爆的能夠,所以寫出來的代碼更加平安。
不幸的是,在Java中仍存在許多輕易招致內存洩露的邏輯能夠(logical leak)。假如不當心,你的Android運用很輕易糟蹋失落未釋放的內存,終究招致內存用光的毛病拋出(out-of-memory,OOM)。
1.普通內存洩露(traditional memory leak)的緣由是:當該對象的一切援用都曾經釋放了,對象仍未被釋放。(譯者注:Cursor忘卻封閉等)
2.邏輯內存洩露(logical memory leak)的緣由是:當運用不再須要這個對象,當仍未釋放該對象的一切援用。
假如持有對象的強援用,渣滓收受接管器是沒法在內存中收受接管這個對象。
在Android開辟中,最輕易激發的內存洩露成績的是Context。好比Activity的Context,就包括年夜量的內存援用,例如View Hierarchies和其他資本。一旦洩露了Context,也意味洩露它指向的一切對象。Android機械內存無限,太多的內存洩露輕易招致OOM。
檢測邏輯內存洩露須要客觀斷定,特殊是對象的性命周期其實不清楚。榮幸的是,Activity有著明白的性命周期,很輕易發明洩露的緣由。Activity.onDestroy()被視為Activity性命的停止,法式下去看,它應當被燒毀了,或許Android體系須要收受接管這些內存(譯者注:當內存不敷時,Android會收受接管看不見的Activity)。
假如這個辦法履行完,在客棧中仍存在持有該Activity的強援用,渣滓收受接管器就沒法把它標志成已收受接管的內存,而我們原來目標就是要收受接管它!
成果就是Activity存活在它的性命周期以外。
Activity是分量級對象,應當讓Android體系來處置它。但是,邏輯內存洩露老是在不經意間產生。(譯者注:已經試過一個Activity招致20M內存洩露)。在Android中,招致潛伏內存洩露的圈套不過乎兩種:
全局過程(process-global)的static變量。這個疏忽運用的狀況,持有Activity的強援用的怪物。
活在Activity性命周期以外的線程。沒有清空對Activity的強援用。
檢討一下你有無碰到以下的情形。
1.Static Activities
在類中界說了靜態Activity變量,把以後運轉的Activity實例賦值於這個靜態變量。
假如這個靜態變量在Activity性命周期停止後沒有清空,就招致內存洩露。由於static變量是貫串這個運用的性命周期的,所以被洩露的Activity就會一向存在於運用的過程中,不會被渣滓收受接管器收受接管。
static Activity activity; void setStaticActivity() { activity = this; } View saButton = findViewById(R.id.sa_button); saButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticActivity(); nextActivity(); } });
2.Static Views
相似的情形會產生在單例形式中,假如Activity常常被用到,那末在內存中保留一個實例是很適用的。正如之前所述,強迫延伸Activity的性命周期是相當風險並且不用要的,不管若何都不克不及如許做。
特別情形:假如一個View初始化消耗年夜量資本,並且在一個Activity性命周期內堅持不變,那可以把它釀成static,加載到視圖樹上(View Hierachy),像如許,當Activity被燒毀時,應該釋放資本。(譯者注:示例代碼中並沒有釋放內存,把這個static view置null便可,然則照樣不建議用這個static view的辦法)
static view; void setStaticView() { view = findViewById(R.id.sv_button); } View svButton = findViewById(R.id.sv_button); svButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticView(); nextActivity(); } });
3.Inner Classes
持續,假定Activity中有個外部類,如許做可以進步可讀性和封裝性。將如我們創立一個外部類,並且持有一個靜態變量的援用,祝賀,內存洩露就離你不遠了(譯者注:燒毀的時刻置空,嗯)。
private static Object inner; void createInnerClass() { class InnerClass { } inner = new InnerClass(); } View icButton = findViewById(R.id.ic_button); icButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { createInnerClass(); nextActivity(); } });
外部類的優勢之一就是可以拜訪內部類,不幸的是,招致內存洩露的緣由,就是外部類持有內部類實例的強援用。
4.Anonymous Classes
類似地,匿名類也保護了內部類的援用。所之內存洩露很輕易產生,當你在Activity中界說了匿名的AsyncTsk。當異步義務在後台履行耗時義務時代,Activity不幸被燒毀了(譯者注:用戶加入,體系收受接管),這個被AsyncTask持有的Activity實例就不會被渣滓收受接管器收受接管,直到異步義務停止。
void startAsyncTask() { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { while(true); } }.execute(); } super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); View aicButton = findViewById(R.id.at_button); aicButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAsyncTask(); nextActivity(); } });
5.Handler
異樣事理,界說匿名的Runnable,用匿名類Handler履行。Runnable外部類會持有內部類的隱式援用,被傳遞到Handler的新聞隊列MessageQueue中,在Message新聞沒有被處置之前,Activity實例不會被燒毀了,因而招致內存洩露。
void createHandler() { new Handler() { @Override public void handleMessage(Message message) { super.handleMessage(message); } }.postDelayed(new Runnable() { @Override public void run() { while(true); } }, Long.MAX_VALUE >> 1); } View hButton = findViewById(R.id.h_button); hButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { createHandler(); nextActivity(); } });
6.Threads
我們再次經由過程Thread和TimerTask來展示內存洩露。
void spawnThread() { new Thread() { @Override public void run() { while(true); } }.start(); } View tButton = findViewById(R.id.t_button); tButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { spawnThread(); nextActivity(); } });
7.TimerTask
只需是匿名類的實例,不論是否是在任務線程,都邑持有Activity的援用,招致內存洩露。
void scheduleTimer() { new Timer().schedule(new TimerTask() { @Override public void run() { while(true); } }, Long.MAX_VALUE >> 1); } View ttButton = findViewById(R.id.tt_button); ttButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { scheduleTimer(); nextActivity(); } });
8.Sensor Manager
最初,經由過程Context.getSystemService(int name)可以獲得體系辦事。這些辦事任務在各自的過程中,贊助運用處置後台義務,處置硬件交互。假如須要應用這些辦事,可以注冊監聽器,這會招致辦事持有了Context的援用,假如在Activity燒毀的時刻沒有刊出這些監聽器,會招致內存洩露。
void registerListener() { SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL); sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST); } View smButton = findViewById(R.id.sm_button); smButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { registerListener(); nextActivity(); } });
總結
看過那末多會招致內存洩露的例子,輕易招致吃光手機的內存使渣滓收受接管處置更加頻發,乃至最壞的情形會招致OOM。渣滓收受接管的操作是很昂貴的開支,會招致肉眼可見的卡頓。所以,實例化的時刻留意持有的援用鏈,並常常停止內存洩露檢討。