記憶體洩漏系列文章:
效能優化——記憶體洩漏(1)入門篇
效能優化——記憶體洩漏(2)工具分析篇
效能優化——記憶體洩漏(3)程式碼分析篇
一、簡述
在上一篇《效能優化——記憶體洩漏(2)工具分析篇》中,介紹瞭如何使用工具幫助我們檢查APP中是否存在記憶體洩漏、及如何定位到記憶體洩漏,但專案並不能完全依賴工具來檢查,畢竟定位記憶體洩漏比較麻煩,還不如在開發時就考慮到記憶體洩漏問題,儘可能減少記憶體洩漏,後續優化才不會那麼痛苦。下面就來看看開發中,哪些程式碼可能造成記憶體洩漏,及避免記憶體洩漏的對應解決方案。
二、程式碼分析
1、靜態變數引起的記憶體洩露
1)錯誤示例
這個可以拿之前的Demo來說明,Demo程式碼如下:
// 單例工具類
public class CommonUtil {
private static CommonUtil mInstance;
private Context mContext;
public CommonUtil(Context context) {
mContext = context;
}
public static CommonUtil getInstance(Context context) {
if (mInstance == null) {
synchronized (CommonUtil.class) {
if (mInstance == null) {
mInstance = new CommonUtil(context);
}
}
}
return mInstance;
}
...
}
// Activity中使用單例工具
public class MemoryLeakActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_leak);
CommonUtil.getInstance(this);
}
}複製程式碼
當呼叫getInstance()時,如果傳入的context是Activity,那麼只要這個單例沒有被釋放,則這個Activity也不會被釋放,直到程式退出後才會釋放。
2)解決方案
不要傳入Activity,可以使用getApplicationContext()來代替。
2、非靜態內部類引起記憶體洩露(包括匿名內部類)
在Java中,非靜態的內部類和匿名內部類都會隱式地持有其外部類的引用。
1)錯誤示例
public class MemoryLeakActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_leak);
loadData();
}
public void loadData() {
new Thread() {
@Override
public void run() {
try {
Thread.sleep(10000);
System.out.println("模擬同步網路資料完畢");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}複製程式碼
上面的程式碼中,使用Thread匿名內部類開闢執行緒同步網路資料,而這個內部類會隱式持有外部類的引用,當退出介面後,該內部類任務還在進行,導致該介面無法被GC回收,於是就會產生記憶體洩漏。
APP退出後,執行GC,獲取記憶體快照。可以看到MemoryLeakActivity的Total Count為1,說明存在記憶體洩漏。
2)解決方案
靜態的內部類不會持有外部類的引用。
(1) 使用靜態方法
public static void loadData() {
new Thread() {
@Override
public void run() {
try {
Thread.sleep(10000);
System.out.println("模擬同步網路資料完畢");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}複製程式碼
(2) 使用靜態內部類
public void loadData() {
new MyThread().start();
}
static class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(10000);
System.out.println("模擬同步網路資料完畢");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}複製程式碼
3)擴充
但專案開發中,可能會存在一定要使用內部類去持有外部類的情況,比如資料同步完成後,需要修改介面上的文字資訊,而靜態內部類無法直接持有外部類的引用,這又該怎麼解決呢?其實可以通過內部類的建構函式將外部類傳入,並使用弱引用儲存(GC執行後釋放),並在內部類中做好判空即可。
static class MyThread extends Thread {
Reference mReference;
public MyThread(Context context) {
mReference = new WeakReference<>(context);
}
@Override
public void run() {
try {
Thread.sleep(10000);
MemoryLeakActivity context = (MemoryLeakActivity) mReference.get();
if (context != null) {
context.mTv.setText("模擬同步網路資料完畢");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} 複製程式碼
APP退出後,執行GC,獲取記憶體快照。可以看到MemoryLeakActivity的Total Count為0,說明沒有記憶體洩漏。
3、不需要用的監聽未移除會發生記憶體洩露
1)錯誤示例
public class MemoryLeakActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager mSm;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_leak);
mSm = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = mSm.getDefaultSensor(Sensor.TYPE_ALL);
mSm.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}複製程式碼
本例中使用getSystemService()獲取感測器服務,並設定了監聽,但沒有在onDestroy()方法中將監聽移除,此類監聽沒有及時清除的話,必定造成記憶體洩漏。
2)解決方案
只需在onDestroy()中移除監聽即可。
@Override
protected void onDestroy() {
super.onDestroy();
mSm.unregisterListener(this);
}複製程式碼
4、資源未關閉引起的記憶體洩露情況
1)錯誤示例
比如:BroadCastReceiver、Cursor、Bitmap、IO流、自定義屬性。
當不需要使用的時候,要記得及時釋放資源。否則就會記憶體洩露。
2)解決方案
這裡以Cursor、IO流和自定義屬性為例。
(1)Cursor或IO流
try {
...
使用cursor/io讀取資料操作
...
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor/io != null) {
cursor/io.close();
cursor/io = null;
}
}複製程式碼
(2)自定義屬性
TypedArray a = theme.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
TypedArray appearance = null;
int ap = a.getResourceId(
com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
a.recycle();// 不執行回收會造成記憶體洩漏複製程式碼