內(nèi)存泄露的原因
內(nèi)存優(yōu)化中非常關(guān)鍵的一點(diǎn)瓷胧,就是避免內(nèi)存泄露玄渗,因?yàn)閮?nèi)存泄露會(huì)嚴(yán)重的導(dǎo)致內(nèi)存浪費(fèi)翔曲。
無用的對(duì)象引用一直未被釋放败玉,就會(huì)導(dǎo)致內(nèi)存泄露。
如果代碼中疫铜,無用的對(duì)象的引用一直沒有被清除掉茂浮,就會(huì)造成內(nèi)存泄露。
當(dāng)按Back鍵關(guān)掉一個(gè)Activity時(shí)壳咕,此Activity就暫時(shí)沒用了席揽。
但是,如果某個(gè)后臺(tái)任務(wù)一直持有著該Activity對(duì)象的引用谓厘,此時(shí)就會(huì)導(dǎo)致內(nèi)存泄露幌羞。
四種引用類型
1. 強(qiáng)引用
對(duì)象類型 對(duì)象名 = new 對(duì)象構(gòu)造方法();
如:String str = new String("Test");
清除強(qiáng)引用對(duì)象中的引用如下:
str = null;
強(qiáng)引用對(duì)象一般不會(huì)被GC回收,它寧愿被拋出OOM異常竟稳,想要回收就要置空引用属桦。
2. 軟應(yīng)用 SoftReference
private void demo() {
String str = new String("");
SoftReference<String> softReference = new SoftReference<>(str);
}
當(dāng)系統(tǒng)內(nèi)存不足時(shí),軟引用對(duì)象會(huì)被GC回收他爸。
清除軟引用對(duì)象中的引用鏈聂宾,可以通過模擬系統(tǒng)內(nèi)存不足來清除,也可以手動(dòng)清除诊笤,手動(dòng)清除如下:
SoftReference<String> softReference = new SoftReference<String>(str);
softReference.clear();
3. 弱引用 WeakReference
private void demo() {
String str = new String("");
WeakReference<String> softReference = new WeakReference<>(str);
}
當(dāng)每次GC時(shí)系谐,弱可及對(duì)象就會(huì)被回收。
清除弱引用對(duì)象中的引用鏈可以通過手動(dòng)調(diào)用gc代碼來清除讨跟,如下:
private void demo() {
String str = new String("");
WeakReference<String> softReference = new WeakReference<>(str);
softReference.get();//獲取引用
softReference.clear();
System.gc();
}
4. 虛引用 PhantomReference
虛引用在代碼中出現(xiàn)的頻率極低蔚鸥,主要目的是為了檢測(cè)對(duì)象是否已經(jīng)被系統(tǒng)回收。
PhantomReference phantomReference = new PhantomReference<>(arg0, arg1);
補(bǔ)充
一個(gè)對(duì)象的可及性由最強(qiáng)的那個(gè)來決定许赃。
System.gc()只會(huì)回收堆內(nèi)存中存放的對(duì)象。
String str = "demo";
WeakReference<String> weakReference = new WeakReference<>(str);
System.gc();
"demo"被存放在常量池里馆类,所以gc()不會(huì)去回收混聊。
常見的內(nèi)存泄露
1.內(nèi)部類導(dǎo)致內(nèi)存泄露
1. 內(nèi)部類導(dǎo)致內(nèi)存泄漏的原因
內(nèi)部類實(shí)例會(huì) 隱式引用 外部類的引用。
內(nèi)部類如果沒有持有外部類的引用乾巧,是沒辦法去調(diào)用外部類的屬性和方法的句喜。
然而,內(nèi)部類沒有明顯的去指定和聲明引用沟于,所以稱之為 隱式引用咳胃。
泄露原因:
比如在Activity中創(chuàng)建一個(gè)內(nèi)部類鞠柄,然后在內(nèi)部類中去執(zhí)行一些耗時(shí)操作伍掀。
操作在執(zhí)行過程中仅父,Activity被關(guān)掉,此時(shí)Activity對(duì)象也不會(huì)被釋放
因?yàn)槟莻€(gè)內(nèi)部類還持有著對(duì)Activity的引用咆槽。
在Activity中創(chuàng)建一個(gè) 內(nèi)部類 去繼承Thread
然后讓該Thread執(zhí)行一些后臺(tái)任務(wù),未執(zhí)行完静暂,關(guān)閉Activity搏熄,就造成會(huì)內(nèi)存泄露。
public class MainActivity extends AppCompatActivity {
?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startThread();
}
});
}
?
private void startThread() {
Thread thread = new Thread() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
SystemClock.sleep(1000);
}
}
};
thread.start();
}
}
當(dāng)點(diǎn)擊頁面按鈕執(zhí)行startThread()后来惧,再按下back鍵關(guān)閉Activity
幾秒后LeakCanary就會(huì)提示內(nèi)存泄露了冗栗。
2. 內(nèi)部類內(nèi)存泄漏解決方案
聲明這個(gè)內(nèi)部類為靜態(tài)類,避免這個(gè)內(nèi)部類去隱式引用外部類Activity即可供搀。
public class MainActivity extends AppCompatActivity {
?
@Override
protected void onCreate(Bundle savedInstanceState) {
...與先前相比未做變化隅居,不再描述
}
?
private void startThread() {
Thread thread = new MyStaticThread();
thread.start();
}
?
將內(nèi)部類聲明為靜態(tài)類
private static class MyStaticThread extends Thread {
?
@Override
public void run() {
for (int i = 0; i < 200; i++) {
SystemClock.sleep(1000);
}
}
}
}
2.1 控制后臺(tái)任務(wù)
為了效率和優(yōu)化,建議通過一個(gè)boolean類型的標(biāo)志位來控制后臺(tái)任務(wù)葛虐。
在外部類Activity的onDestory()中胎源,將boolean值進(jìn)行修改,使后臺(tái)任務(wù)退出循環(huán)挡闰。
public class MainActivity extends AppCompatActivity {
...
//Activity頁面是否已經(jīng)destroy
private static boolean isDestroy = false;
?
private static class MyStaticThread extends Thread {
?
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(!isDestroy){
SystemClock.sleep(1000);
}
}
}
}
?
@Override
protected void onDestroy() {
super.onDestroy();
isDestroy = true;
}
}
2.3 通過聲明 引用 調(diào)用外部類屬性乒融,方法。
當(dāng)該內(nèi)部類聲明為靜態(tài)時(shí)摄悯,將不再持有外部類Activity的引用
此時(shí)也不能再直接使用外部類中的方法赞季、變量。
此時(shí)就需要通過引用調(diào)用
public class MainActivity extends AppCompatActivity {
private boolean isDestroy = false;
private void startThread() {
Thread thread = new MyStaticThread(MainActivity.this);
thread.start();
}
?
private static class MyStaticThread extends Thread {
?
private WeakReference<MainActivity> softReference = null;
?
MyStaticThread(MainActivity mainActivity){
this.softReference = new WeakReference<>(mainActivity);
}
?
@Override
public void run() {
MainActivity mainActivity = softReference.get();
for (int i = 0; i < 200; i++) {
//使用前最好對(duì)MainActivity對(duì)象做非空判斷
//如果它已經(jīng)被回收奢驯,就不再執(zhí)行后臺(tái)任務(wù)
if(mainActivity != null && !mainActivity.isDestroy){
SystemClock.sleep(1000);
}
}
}
}
?
@Override
protected void onDestroy() {
super.onDestroy();
isDestroy = true;
}
}
Handler
public class MainActivity extends AppCompatActivity {
?
private static final int MESSAGE_DELAY = 0;
private Button mButton;
?
private void startDelayTask() {
//發(fā)送一條消息申钩,該消息會(huì)被延時(shí)10秒后才處理
...
}
?
private Handler mHandler = new InsideHandler(MainActivity.this);
?
private static class InsideHandler extends Handler {
private WeakReference<MainActivity> mSoftReference;
?
InsideHandler(MainActivity activity) {
mSoftReference = new WeakReference<MainActivity>(activity);
}
?
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = mSoftReference.get();
if (mainActivity != null) {
switch (msg.what) {
case MESSAGE_DELAY:
//通過軟引用中的mainActivity可以拿到那個(gè)非靜態(tài)的button對(duì)象
mainActivity.mButton.setText("延時(shí)修改了按鈕的文本");
break;
}
}
}
}
}
當(dāng)Activity頁面退出時(shí),將handler中的所有消息進(jìn)行移除瘪阁,做到滴水不漏撒遣。
其實(shí)就是在onDestroy中寫上:
@Override
protected void onDestroy() {
super.onDestroy();
//參數(shù)為null時(shí),handler中所有消息和回調(diào)都會(huì)被移除
mHandler.removeCallbacksAndMessages(null);
}
總結(jié)
1. 將內(nèi)部類聲明為靜態(tài)的
2. 通過引用獲取外部屬性管跺,方法
3. 注意判斷引用獲取是否為空
4. 在Activity銷毀方法內(nèi)注銷Handler等
2.Context導(dǎo)致內(nèi)存泄露
有時(shí)候我們會(huì)創(chuàng)建一個(gè)靜態(tài)類义黎,比如說AppManager、XXXManager豁跑。
這些靜態(tài)類可能還會(huì)以單例模式存在廉涕。
當(dāng)需要做關(guān)于UI的處理,所以傳遞了一個(gè)Context進(jìn)來艇拍。
public class ToastManager {
private Context mContext;
ToastManager(Context context){
mContext = context;
}
?
private static ToastManager mManager = null;
?
public void showToast(String str){
if(str == null){
return;
}
Toast.makeText(mContext, str, Toast.LENGTH_SHORT).show();
}
?
public static ToastManager getInstance(Context context){
if(mManager == null){
synchronized (ToastManager.class){
if(mManager == null){
mManager = new ToastManager(context);
}
}
}
return mManager;
}
}
使用時(shí)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
ToastManager instance = ToastManager.getInstance(MainActivity.this);
}
}
此時(shí)會(huì)發(fā)生內(nèi)存泄露
因?yàn)殪o態(tài)實(shí)例比Activity生命周期長(zhǎng)狐蜕,在使用靜態(tài)類時(shí)將Activity作為context參數(shù)傳了進(jìn)來
當(dāng)Activity被關(guān)掉,但是靜態(tài)實(shí)例中還保有對(duì)它的引用
會(huì)導(dǎo)致Activity沒法被及時(shí)回收卸夕,造成內(nèi)存泄露
解決方案:
在傳Context上下文參數(shù)時(shí)层释,盡量傳跟Application應(yīng)用相同生命周期的Context。
比如getApplicationContext()快集,因?yàn)殪o態(tài)實(shí)例的生命周期跟應(yīng)用Application一致贡羔。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ToastManager instance = ToastManager.getInstance(getApplicationContext());
}
}
Context的作用域
Context的具體實(shí)現(xiàn)子類
Activity廉白、Application、Service
1.Activity
出于安全原因的考慮治力,Android是不允許Activity或Dialog憑空出現(xiàn)的蒙秒。
一個(gè)Activity的啟動(dòng),必須要建立在另一個(gè)Activity的基礎(chǔ)上宵统,以此形成的返回棧晕讲。
Dialog則必須在一個(gè)Activity上面彈出。
在這種場(chǎng)景下马澈,只能使用Activity類型的Context瓢省,否則將會(huì)出錯(cuò)。
總結(jié)
1. 凡是跟UI相關(guān)的痊班,都建議使用Activity做為Context來處理.
2. 注意Context引用的持有勤婚,防止內(nèi)存泄漏。