轉載自https://www.jianshu.com/p/ab4a7e353076java
若是一個無用對象(不須要再使用的對象)仍然被其餘對象持有引用,形成該對象沒法被系統回收,以至該對象在堆中所佔用的內存單元沒法被釋放而形成內存空間浪費,這中狀況就是內存泄露。android
在Android開發中,一些很差的編程習慣會致使咱們的開發的app存在內存泄露的狀況。下面介紹一些在Android開發中常見的內存泄露場景及優化方案。編程
單例模式在Android開發中會常常用到,可是若是使用不當就會致使內存泄露。由於單例的靜態特性使得它的生命週期同應用的生命週期同樣長,若是一個對象已經沒有用處了,可是單例還持有它的引用,那麼在整個應用程序的生命週期它都不能正常被回收,從而致使內存泄露。api
public class AppSettings { private static AppSettings sInstance; private Context mContext; private AppSettings(Context context) { this.mContext = context; } public static AppSettings getInstance(Context context) { if (sInstance == null) { sInstance = new AppSettings(context); } return sInstance; } }
像上面代碼中這樣的單例,若是咱們在調用getInstance(Context context)
方法的時候傳入的context
參數是Activity
、Service
等上下文,就會致使內存泄露。網絡
以Activity
爲例,當咱們啓動一個Activity
,並調用getInstance(Context context)
方法去獲取AppSettings
的單例,傳入Activity.this
做爲context
,這樣AppSettings
類的單例sInstance
就持有了Activity
的引用,當咱們退出Activity
時,該Activity
就沒有用了,可是由於sIntance
做爲靜態單例(在應用程序的整個生命週期中存在)會繼續持有這個Activity
的引用,致使這個Activity
對象沒法被回收釋放,這就形成了內存泄露。app
爲了不這樣單例致使內存泄露,咱們能夠將context
參數改成全局的上下文:異步
private AppSettings(Context context) { this.mContext = context.getApplicationContext(); }
全局的上下文Application Context
就是應用程序的上下文,和單例的生命週期同樣長,這樣就避免了內存泄漏。ide
單例模式對應應用程序的生命週期,因此咱們在構造單例的時候儘可能避免使用Activity
的上下文,而是使用Application
的上下文。oop
靜態變量存儲在方法區,它的生命週期從類加載開始,到整個進程結束。一旦靜態變量初始化後,它所持有的引用只有等到進程結束纔會釋放。學習
好比下面這樣的狀況,在Activity
中爲了不重複的建立info
,將sInfo
做爲靜態變量:
public class MainActivity extends AppCompatActivity { private static Info sInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (sInfo != null) { sInfo = new Info(this); } } } class Info { public Info(Activity activity) { } }
Info
做爲Activity
的靜態成員,而且持有Activity
的引用,可是sInfo
做爲靜態變量,生命週期確定比Activity
長。因此當Activity
退出後,sInfo
仍然引用了Activity
,Activity
不能被回收,這就致使了內存泄露。
在Android開發中,靜態持有不少時候都有可能由於其使用的生命週期不一致而致使內存泄露,因此咱們在新建靜態持有的變量的時候須要多考慮一下各個成員之間的引用關係,而且儘可能少地使用靜態持有的變量,以免發生內存泄露。固然,咱們也能夠在適當的時候講靜態量重置爲null,使其再也不持有引用,這樣也能夠避免內存泄露。
非靜態內部類(包括匿名內部類)默認就會持有外部類的引用,當非靜態內部類對象的生命週期比外部類對象的生命週期長時,就會致使內存泄露。
非靜態內部類致使的內存泄露在Android開發中有一種典型的場景就是使用Handler
,不少開發者在使用Handler
是這樣寫的:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { // 作相應邏輯 } } }; }
也許有人會說,mHandler
並未做爲靜態變量持有Activity
引用,生命週期可能不會比Activity
長,應該不必定會致使內存泄露呢,顯然不是這樣的!
熟悉Handler
消息機制的都知道,mHandler
會做爲成員變量保存在發送的消息msg
中,即msg
持有mHandler
的引用,而mHandler
是Activity
的非靜態內部類實例,即mHandler
持有Activity
的引用,那麼咱們就能夠理解爲msg
間接持有Activity
的引用。msg
被髮送後先放到消息隊列MessageQueue
中,而後等待Looper
的輪詢處理(MessageQueue
和Looper
都是與線程相關聯的,MessageQueue
是Looper
引用的成員變量,而Looper
是保存在ThreadLocal
中的)。那麼當Activity
退出後,msg
可能仍然存在於消息對列MessageQueue
中未處理或者正在處理,那麼這樣就會致使Activity
沒法被回收,以至發生Activity
的內存泄露。
一般在Android開發中若是要使用內部類,但又要規避內存泄露,通常都會採用靜態內部類+弱引用的方式。
public class MainActivity extends AppCompatActivity { private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new MyHandler(this); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } private static class MyHandler extends Handler { private WeakReference<MainActivity> activityWeakReference; public MyHandler(MainActivity activity) { activityWeakReference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = activityWeakReference.get(); if (activity != null) { if (msg.what == 1) { // 作相應邏輯 } } } } }
mHandler
經過弱引用的方式持有Activity
,當GC執行垃圾回收時,遇到Activity
就會回收並釋放所佔據的內存單元。這樣就不會發生內存泄露了。
上面的作法確實避免了Activity
致使的內存泄露,發送的msg
再也不已經沒有持有Activity
的引用了,可是msg
仍是有可能存在消息隊列MessageQueue
中,因此更好的是在Activity
銷燬時就將mHandler
的回調和發送的消息給移除掉。
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
非靜態內部類形成內存泄露還有一種狀況就是使用Thread
或者AsyncTask
。
好比在Activity中直接new
一個子線程Thread
:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { // 模擬相應耗時邏輯 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
或者直接新建AsyncTask
異步任務:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { // 模擬相應耗時邏輯 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.execute(); } }
不少初學者都會像上面這樣新建線程和異步任務,卻不知這樣的寫法很是地不友好,這種方式新建的子線程Thread
和AsyncTask
都是匿名內部類對象,默認就隱式的持有外部Activity
的引用,致使Activity
內存泄露。要避免內存泄露的話仍是須要像上面Handler
同樣使用靜態內部類+弱應用的方式(代碼就不列了,參考上面Hanlder
的正確寫法)。
好比咱們在Activity
中註冊廣播,若是在Activity
銷燬後不取消註冊,那麼這個剛播會一直存在系統中,同上面所說的非靜態內部類同樣持有Activity
引用,致使內存泄露。所以註冊廣播後在Activity
銷燬後必定要取消註冊。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.registerReceiver(mReceiver, new IntentFilter()); } private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 接收到廣播須要作的邏輯 } }; @Override protected void onDestroy() { super.onDestroy(); this.unregisterReceiver(mReceiver); } }
在註冊觀察則模式的時候,若是不及時取消也會形成內存泄露。好比使用Retrofit+RxJava
註冊網絡請求的觀察者回調,一樣做爲匿名內部類持有外部引用,因此須要記得在不用或者銷燬的時候取消註冊。
Timer
和TimerTask
在Android中一般會被用來作一些計時或循環任務,好比實現無限輪播的ViewPager
:
public class MainActivity extends AppCompatActivity { private ViewPager mViewPager; private PagerAdapter mAdapter; private Timer mTimer; private TimerTask mTimerTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); mTimer.schedule(mTimerTask, 3000, 3000); } private void init() { mViewPager = (ViewPager) findViewById(R.id.view_pager); mAdapter = new ViewPagerAdapter(); mViewPager.setAdapter(mAdapter); mTimer = new Timer(); mTimerTask = new TimerTask() { @Override public void run() { MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { loopViewpager(); } }); } }; } private void loopViewpager() { if (mAdapter.getCount() > 0) { int curPos = mViewPager.getCurrentItem(); curPos = (++curPos) % mAdapter.getCount(); mViewPager.setCurrentItem(curPos); } } private void stopLoopViewPager() { if (mTimer != null) { mTimer.cancel(); mTimer.purge(); mTimer = null; } if (mTimerTask != null) { mTimerTask.cancel(); mTimerTask = null; } } @Override protected void onDestroy() { super.onDestroy(); stopLoopViewPager(); } }
當咱們Activity
銷燬的時,有可能Timer
還在繼續等待執行TimerTask
,它持有Activity的引用不能被回收,所以當咱們Activity銷燬的時候要當即cancel
掉Timer
和TimerTask
,以免發生內存泄漏。
這個比較好理解,若是一個對象放入到ArrayList
、HashMap
等集合中,這個集合就會持有該對象的引用。當咱們再也不須要這個對象時,也並無將它從集合中移除,這樣只要集合還在使用(而此對象已經無用了),這個對象就形成了內存泄露。而且若是集合被靜態引用的話,集合裏面那些沒有用的對象更會形成內存泄露了。因此在使用集合時要及時將不用的對象從集合remove
,或者clear
集合,以免內存泄漏。
在使用IO
、File
流或者Sqlite
、Cursor
等資源時要及時關閉。這些資源在進行讀寫操做時一般都使用了緩衝,若是及時不關閉,這些緩衝對象就會一直被佔用而得不到釋放,以至發生內存泄露。所以咱們在不須要使用它們的時候就及時關閉,以便緩衝能及時獲得釋放,從而避免內存泄露。
動畫一樣是一個耗時任務,好比在Activity
中啓動了屬性動畫(ObjectAnimator
),可是在銷燬的時候,沒有調用cancle
方法,雖然咱們看不到動畫了,可是這個動畫依然會不斷地播放下去,動畫引用所在的控件,所在的控件引用Activity
,這就形成Activity
沒法正常釋放。所以一樣要在Activity
銷燬的時候cancel
掉屬性動畫,避免發生內存泄漏。
@Override protected void onDestroy() { super.onDestroy(); mAnimator.cancel(); }
關於WebView的內存泄露,由於WebView在加載網頁後會長期佔用內存而不能被釋放,所以咱們在Activity銷燬後要調用它的destory()
方法來銷燬它以釋放內存。
另外在查閱WebView
內存泄露相關資料時看到這種狀況:
Webview
下面的Callback
持有Activity
引用,形成Webview
內存沒法釋放,即便是調用了Webview.destory()
等方法都沒法解決問題(Android5.1以後)。
最終的解決方案是:在銷燬WebView
以前須要先將WebView從
父容器中移除,而後在銷燬WebView
。詳細分析過程請參考這篇文章:WebView內存泄漏解決方法。
@Override protected void onDestroy() { super.onDestroy(); // 先從父控件中移除WebView mWebViewContainer.removeView(mWebView); mWebView.stopLoading(); mWebView.getSettings().setJavaScriptEnabled(false); mWebView.clearHistory(); mWebView.removeAllViews(); mWebView.destroy(); }
內存泄露在Android內存優化是一個比較重要的一個方面,不少時候程序中發生了內存泄露咱們不必定就能注意到,全部在編碼的過程要養成良好的習慣。總結下來只要作到如下這幾點就能避免大多數狀況的內存泄漏:
構造單例的時候儘可能別用Activity
的引用;
靜態引用時注意應用對象的置空或者少用靜態引用;
使用靜態內部類+軟引用代替非靜態內部類;
及時取消廣播或者觀察者註冊;
耗時任務、屬性動畫在Activity
銷燬時記得cancel
;
文件流、Cursor
等資源及時關閉;
Activity
銷燬時WebView
的移除和銷燬。
象持有它的引用從而致使它不能被回收,這就致使本該被回收的對象不能被回收而停留在堆內存中,內存泄漏就產生了。
內存泄漏有什麼影響呢?它是形成應用程序OOM的主要緣由之一。因爲Android系統爲每一個應用程序分配的內存有限,當一個應用中產生的內存泄漏比較多時,就不免會致使應用所須要的內存超過這個系統分配的內存限額,這就形成了內存溢出而致使應用Crash。
瞭解了內存泄漏的緣由及影響後,咱們須要作的就是掌握常見的內存泄漏,並在之後的Android程序開發中,儘可能避免它。下面小編蒐羅了5個Android開發中比較常見的內存泄漏問題及解決辦法,分享給你們,一塊兒來看看吧。
1、單例形成的內存泄漏
Android的單例模式很是受開發者的喜好,不過使用的不恰當的話也會形成內存泄漏。由於單例的靜態特性使得單例的生命週期和應用的生命週期同樣長,這就說明了若是一個對象已經不須要使用了,而單例對象還持有該對象的引用,那麼這個對象將不能被正常回收,這就致使了內存泄漏。
以下這個典例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
AppManager {
private
static
AppManager instance;
private
Context context;
private
AppManager(Context context) {
this
.context = context;
}
public
static
AppManager getInstance(Context context) {
if
(instance !=
null
) {
instance =
new
AppManager(context);
}
return
instance;
}
}
|
這是一個普通的單例模式,當建立這個單例的時候,因爲須要傳入一個Context,因此這個Context的生命週期的長短相當重要:
一、傳入的是Application的Context:這將沒有任何問題,由於單例的生命週期和Application的同樣長 ;
二、傳入的是Activity的Context:當這個Context所對應的Activity退出時,因爲該Context和Activity的生命週期同樣長(Activity間接繼承於Context),因此當前Activity退出時它的內存並不會被回收,由於單例對象持有該Activity的引用。
因此正確的單例應該修改成下面這種方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
AppManager {
private
static
AppManager instance;
private
Context context;
private
AppManager(Context context) {
this
.context = context.getApplicationContext();
}
public
static
AppManager getInstance(Context context) {
if
(instance !=
null
) {
instance =
new
AppManager(context);
}
return
instance;
}
}
|
這樣無論傳入什麼Context最終將使用Application的Context,而單例的生命週期和應用的同樣長,這樣就防止了內存泄漏。
2、非靜態內部類建立靜態實例形成的內存泄漏
有的時候咱們可能會在啓動頻繁的Activity中,爲了不重複建立相同的數據資源,會出現這種寫法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
MainActivity
extends
AppCompatActivity {
private
static
TestResource mResource =
null
;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if
(mManager ==
null
){
mManager =
new
TestResource();
}
//...
}
class
TestResource {
//...
}
}
|
這樣就在Activity內部建立了一個非靜態內部類的單例,每次啓動Activity時都會使用該單例的數據,這樣雖然避免了資源的重複建立,不過這種寫法卻會形成內存泄漏,由於非靜態內部類默認會持有外部類的引用,而又使用了該非靜態內部類建立了一個靜態的實例,該實例的生命週期和應用的同樣長,這就致使了該靜態實例一直會持有該Activity的引用,致使Activity的內存資源不能正常回收。正確的作法爲:
將該內部類設爲靜態內部類或將該內部類抽取出來封裝成一個單例,若是須要使用Context,請使用ApplicationContext 。
3、Handler形成的內存泄漏
Handler的使用形成的內存泄漏問題應該說最爲常見了,平時在處理網絡任務或者封裝一些請求回調等api都應該會藉助Handler來處理,對於Handler的使用代碼編寫一不規範即有可能形成內存泄漏,以下示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
class
MainActivity
extends
AppCompatActivity {
private
Handler mHandler =
new
Handler() {
@Override
public
void
handleMessage(Message msg) {
//...
}
};
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private
void
loadData(){
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
|
這種建立Handler的方式會形成內存泄漏,因爲mHandler是Handler的非靜態匿名內部類的實例,因此它持有外部類Activity的引用,咱們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那麼當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,因此致使該Activity的內存資源沒法及時回收,引起內存泄漏,因此另一種作法爲:
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
|
public
class
MainActivity
extends
AppCompatActivity {
private
MyHandler mHandler =
new
MyHandler(
this
);
private
TextView mTextView ;
private
static
class
MyHandler
extends
Handler {
private
WeakReference<Context> reference;
public
MyHandler(Context context) {
reference =
new
WeakReference<>(context);
}
@Override
public
void
handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if
(activity !=
null
){
activity.mTextView.setText(
""
);
}
}
}
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private
void
loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
|
建立一個靜態Handler內部類,而後對Handler持有的對象使用弱引用,這樣在回收時也能夠回收Handler持有的對象,這樣雖然避免了Activity泄漏,不過Looper線程的消息隊列中仍是可能會有待處理的消息,因此咱們在Activity的Destroy時或者Stop時應該移除消息隊列中的消息,更準確的作法以下:
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
34
35
36
37
|
public
class
MainActivity
extends
AppCompatActivity {
private
MyHandler mHandler =
new
MyHandler(
this
);
private
TextView mTextView ;
private
static
class
MyHandler
extends
Handler {
private
WeakReference<Context> reference;
public
MyHandler(Context context) {
reference =
new
WeakReference<>(context);
}
@Override
public
void
handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if
(activity !=
null
){
activity.mTextView.setText(
""
);
}
}
}
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private
void
loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected
void
onDestroy() {
super
.onDestroy();
mHandler.removeCallbacksAndMessages(
null
);
}
}
|
使用mHandler.removeCallbacksAndMessages(null);是移除消息隊列中全部消息和全部的Runnable。固然也可使用mHandler.removeCallbacks();或mHandler.removeMessages();來移除指定的Runnable和Message。
4、線程形成的內存泄漏
對於線程形成的內存泄漏,也是平時比較常見的,以下這兩個示例可能每一個人都這樣寫過:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//——————test1
new
AsyncTask<Void, Void, Void>() {
@Override
protected
Void doInBackground(Void... params) {
SystemClock.sleep(
10000
);
return
null
;
}
}.execute();
//——————test2
new
Thread(
new
Runnable() {
@Override
public
void
run() {
SystemClock.sleep(
10000
);
}
}).start();
|
上面的異步任務和Runnable都是一個匿名內部類,所以它們對當前Activity都有一個隱式引用。若是Activity在銷燬以前,任務還未完成, 那麼將致使Activity的內存資源沒法回收,形成內存泄漏。正確的作法仍是使用靜態內部類的方式,以下:
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
|
static
class
MyAsyncTask
extends
AsyncTask<Void, Void, Void> {
private
WeakReference<Context> weakReference;
public
MyAsyncTask(Context context) {
weakReference =
new
WeakReference<>(context);
}
@Override
protected
Void doInBackground(Void... params) {
SystemClock.sleep(
10000
);
return
null
;
}
@Override
protected
void
onPostExecute(Void aVoid) {
super
.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if
(activity !=
null
) {
//...
}
}
}
static
class
MyRunnable
implements
Runnable{
@Override
public
void
run() {
SystemClock.sleep(
10000
);
}
}
//——————
new
Thread(
new
MyRunnable()).start();
new
MyAsyncTask(
this
).execute();
|
這樣就避免了Activity的內存資源泄漏,固然在Activity銷燬時候也應該取消相應的任務AsyncTask::cancel(),避免任務在後臺執行浪費資源。
5、資源未關閉形成的內存泄漏
對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷,不然這些資源將不會被回收,形成內存泄漏。
以上就是android編程中,常見的5大內存泄漏問題及相應的解決辦法,若是你們在編程中遇到了上述泄漏問題,不妨能夠試試對應的方法。若是你們還有什麼疑問,能夠去「學習問答」版塊直接提出。