常見內存泄漏問題分析與解決:
什麼是內存泄漏:
內存管理的目的就是在開發過程中能夠有效的避免內存使用及內存泄漏問題,內存泄漏簡單的可以總結爲:
“沒用的對象出現無法回收的現象就是內存泄漏”。
內存泄漏會造成哪些問題:
應用可使用內存逐步變小,增加了堆內存壓力
降低了應用性能,比如頻繁觸發GC
嚴重的時候也會造成內存溢出,OOM
OOM 發生在,當我們嘗試進行創建對象,但是堆內存無法通過 GC 釋放足夠的空間,堆內存也無法再繼續增長,從而完成對象創建請求的時候,OOM 發生很有可能是內存泄露導致的,但並非所有的 OOM 都是由內存泄露引起的,內存泄露也並不一定引起 OOM。
平常開發中常見的內存泄漏案例:
單例模式造成內存泄漏:
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context;
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
}
return sInstance;
}
}
單例模式中若單獨持有View的Context的話,那麼當該View層銷燬時,單例任然存活且持有該View層的應用,這樣就會導致View層不能被及時釋放。
解決方法及建議:
1.明確單例模式使用場景,在合適的地方使用單例模式並且儘量避免傳入View層Context。
2.單例模式中若必須使用Context,可將單例模式應用對象的生命週期依附於整個應用的生命週期。
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context.getApplicationContext();
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
}
return sInstance;
}
}
部分類構造函數持有View的Context:
public class Sample {
private Context mContext;
public Sample(Context context){
this.mContext = context;
}
public Context getContext() {
return mContext;
}
}
// 外部調用
Sample sample = new Sample(MainActivity.this);
這種在構造函數中傳入了context的例子,處理不當也會造成內存泄漏問題。
解決方案:
1.對於View層的Context持有可以改爲弱引用
public class Sample {
private WeakReference<Context> mWeakReference;
public Sample(Context context){
this.mWeakReference = new WeakReference<>(context);
}
public Context getContext() {
if(mWeakReference.get() != null){
return mWeakReference.get();
}
return null;
}
}
// 外部調用
Sample sample = new Sample(MainActivity.this);
非靜態內部類/匿名類:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
在Activity中的onCreate 中創建了MyAscnyTask對象,並啓動了異步方法,這樣當異步方法未執行完畢前退出了Activity,那麼因爲MyAscnyTask仍持有Activity的引用,就會導致Activity不能及時釋放。
解決方法:將 MyAsyncTask 變成靜態內部類 (Handler的使用也可以參考這裏)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
List中填充了View層對象或控件:
在非View層持有一個List,裏面填充了持有View層的對象。
List viewLists = new ArrayLists(){};
for(int i=0;i<10;i++){
ImageView iv = new ImageView(this);
viewLists.add(iv);
}
在使用完畢後,記得置空和清除List中的持有對象,避免內存泄漏。
點擊事件及按鍵事件處理不當:
在單例模式中封裝windowManager處理每一層View的點擊/按鍵事件,但是在View層銷燬的時候沒有及時釋放掉單例中持有的時間回調。
解決方式:單例模式中提供釋放接口,在View層銷燬的時候,及時銷燬掉有關於自己的事件回調。單例模式中若持有對View層的引用,必須得提供釋放方法,讓View層去根據生命週期調用。
MVP模式不規範造成的內存泄漏:
MVP模式中,P層一般會持有View層的引用,所以P層需要提供UnBind方法去釋放掉對View層的引用。或自己封裝好BaseView,BasePresenter,BaseModel來進行基類封裝。
部分Android源生API帶來的內存泄漏問題:
在Android4.4中,TelephoneManager監聽sim卡信號變化的API存在內存泄漏問題。解決無果後,後期便將該對象與應用生命週期綁定而不是在某個activity中進行註冊。