前言
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这个控件导致的。注:本文内容来自网络他人共享以及自己平时的总结,因水平有限,难免出错,欢迎大家指正。