前言
Android應用因爲本身可用內存的限制,需要特別重視內存泄露的問題,本文總結了Android中常見的一些內存泄露原因及避免方式。
一、單例造成的內存泄露
public class MusicManager {
private static MusicManager instance;
private Context context;
private MusicManager (Context context) {
this.context = context;
}
public static MusicManager getInstance(Context context) {
if (instance != null) {
instance = new MusicManager (context);
}
return instance;
}
}
public class MusicManager {
private static MusicManager instance;
private Context context;
private MusicManager(Context context) {
this.context = context.getApplicationContext();
}
public static MusicManager getInstance(Context context) {
if (instance != null) {
instance = new MusicManager(context);
}
return instance;
}
}
這樣就不管調用者傳入的是activity還是application的context都不會出現內存泄露二、非靜態內部類創建靜態實例造成的內存泄露
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 {
//...
}
}
這樣些雖然避免了對象的重複創建,但是因爲static修飾的變量的生命週期和應用一樣長,然後非靜態內部類會默認持有外部類的引用,這樣導致了activity的實例一直被持有,導致activity銷燬了但是不會被回收。正常的做法應該是將該內部類修飾成靜態的,或者抽取出來封裝成一個單例。
三、Handler造成的內存泄露
Handler在應用中的使用非常普遍,但是很多人在使用的時候會不經意間造成內存泄露。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(){
//發送消息
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);//移除消息
}
創建一個static的Handler內部類。static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
//創建一個靜態的handler的內部類,然後將activity的對象主動傳進去,
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
//用弱引用去持有該handler的對象
}
四、線程造成的內存泄露
線程造成的內存泄露主要是因爲線程的生命週期不可控
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
上述的代碼,很多人都會在項目中寫到,現在的Runnable對象是一個匿名內部類,他默認持有外部類的引用,如果在activity銷燬的時候,該線程還沒執行完成,此刻的activity的內存資源就無法被回收,正確的做法還是使用靜態內部類的方式,如下
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
new Thread(new MyRunnable()).start();
五、資源未關閉造成的內存泄露
六、註冊監聽器沒有及時移除導致的內存泄露
public class LeaksActivity extends Activity implements LocationListener {
private LocationManager locationManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leaks);
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);//拿到LocationManager對象
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
TimeUnit.MINUTES.toMillis(5), 100, this);//註冊一個監聽
}
// Listener implementation omitted
}
如上代碼,我們讓Android的LocationManager通知我們位置更新。我們需要傳入一個監聽器,在這裏我們讓Activity實現了位置監聽接口,這意味着LocationManager將持有該Activity的引用。現在如果出現了Activity銷燬的情況,而且沒有移除掉監聽的話,該Activity的內存資源將不會被回收,就會導致內存泄露七、WebView導致的內存泄露
WebView mWebView = new WebView(getApplicationgContext());
LinearLayout mll = findViewById(R.id.xxx);
mll.addView(mWebView);
protected void onDestroy(){
super.onDestroy();
mWebView.removeAllViews();
mWebView.destroy()
}
這樣WebView就會持有applicationContext,而不是activity的Context,但是這樣做會有個問題,當在WebView中打開連接或者打開的頁面加載flash時,會出現類型轉換異常,導致頁面崩潰,因爲加載flash的時候,系統會把WebView作爲一個父控件,然後在該空間上繪製flash,它想找一個Activity的context來繪製他,而你傳入的是ApplicationContext。
B、如果你不能給我刪除引用,那麼我自己來刪除,核心思想是通過反射的方式,實現刪除引用
public void setConfigCallback(WindowManager windowManager) {
try {
Field field = WebView.class.getDeclaredField("mWebViewCore");
field = field.getType().getDeclaredField("mBrowserFrame");
field = field.getType().getDeclaredField("sConfigCallback");
field.setAccessible(true);
Object configCallback = field.get(null);
if (null == configCallback) {
return;
}
field = field.getType().getDeclaredField("mWindowManager");
field.setAccessible(true);
field.set(configCallback, windowManager);
} catch(Exception e) {
}
}
但是該方法是基於於android的webkit內核的,在android
4.4及以後版本中會失效,因爲4.4之後更換了WebView的內核爲chromium。public void onAttachedToWindow() {
if (isDestroyed()) return;//有是否destroyed的判斷
if (mIsAttachedToWindow) {
Log.w(TAG, "onAttachedToWindow called when already attached. Ignoring");
return;
}
......
mComponentCallbacks = new AwComponentCallbacks();
mContext.registerComponentCallbacks(mComponentCallbacks);
}
@Override
public void onDetachedFromWindow() {
if (isDestroyed()) return;
if (!mIsAttachedToWindow) {
Log.w(TAG, "onDetachedFromWindow called when already detached. Ignoring");
return;
}
......
if (mComponentCallbacks != null) {
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mComponentCallbacks = null;
}
......
}
protected void onDestroy() {
if (mWebView != null) {
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.destroy();
mWebView = null;
}
super.onDestroy();
}
我們可以通過將WebView從父控件中移除來觸發WebView的onDetachedFromWindow方法,讓它提前執行onDetachedFromWindow方法。我在開發時,在mx4
pro的手機上遇到過這種問題,其系統就是android 5.1的,有一個頁面的對象一直回收不了,導致內存一直增加最後OOM,最後發現是因爲WebView這個控件導致的。注:本文內容來自網絡他人共享以及自己平時的總結,因水平有限,難免出錯,歡迎大家指正。