静态内部类与非静态内部类之间存在一个最大的区别,就是非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。 代码:
public class Outer { private void outerDo() {} class Inter { private void innerDo() { // 内部类可以直接访问外部类成员,原因在于隐式持有了一个外部类引用 outerDo(); // Outer.this 就是内部类隐式持有的外部类引用 Outer.this.outerDo(); } } }
如果Inter的实例为静态的会导致内存泄漏。
解决方法:将Inter改成静态内部类
例如:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { // 做相应逻辑 } } }; }
熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueue和Looper都是与线程相关联的,MessageQueue是Looper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。
解决办法:静态内部类+弱引用
例如:
public class MainActivity extends AppCompatActivity { private static class ParseHandler extends XyHandler<MainActivity> { private ParseHandler(MainActivity activity) { super(activity); } @Override protected void handleMessage(Message msg, MainActivity activity) { switch (msg.what) { case 0: activity.doSomething(); break; default: break; } } } private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new ParseHandler(this); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } public void doSomething(){ } } public abstract class XyHandler<T> extends Handler { private WeakReference<T> mWeak; public XyHandler(T t) { mWeak = new WeakReference<>(t); } @Override public void handleMessage(Message msg) { if (mWeak == null || mWeak.get() == null) { return; } handleMessage(msg, mWeak.get()); super.handleMessage(msg); } protected abstract void handleMessage(Message msg, T t); }
这种情况不常遇到,一般有些新手会犯这个错误。这种就是一些对象的引用是静态的,导致无法回收。
例如:
public class MainActivity extends AppCompatActivity { privite static MainActivity mMainActivity; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMainActivity = this; } }
还有自定义View或者ViewGroup中也是出现这种状况,导致内存无法释放,出现内存溢出。对于这种Context不能用静态引用,如果需要调用这些对象内的方法可以将对应方法代码提取出来。
线程一般是指Thread和AsyncTask
Thread,例如:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { // 模拟相应耗时逻辑 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
AsyncTask,例如:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { // 模拟相应耗时逻辑 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.execute(); } }
我们在刚开始写代码的时候都是像上面一样写线程和异步任务,但是这种方式使用Thread和AsyncTask都是匿名内部类对象,默认持有外部类Activity的引用,在Activity回收时可能会有Thread和AsyncTask没有执行完的情况,所以可能会造成内存泄漏。
IO流、File流或者数据库、Cursor等资源在使用完成后要及时关闭,如果没有及时关闭,会导致缓冲对象一直被占用,不能得到释放,发生内存泄漏。Bitmap未回收。
例如:
public class AppSettings { private static AppSettings sInstance; private Context mContext; private AppSettings(Context context) { this.mContext = context; } public static AppSettings getInstance(Context context) { if (sInstance == null) { sInstance = new AppSettings(context); } return sInstance; } }
单例模式的生命周期会和整个应用的生命周期一样,如果在使用单例模式时传入的是Activity或者Service等声明周期短于Application的声明周期时,都会造成内存泄漏。因此我们传入的Context应该是Application的Context,这样生命周期就和Application的声明周期一样,避免造成内存泄漏
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.registerReceiver(mReceiver, new IntentFilter()); } private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 接收到广播需要做的逻辑 } }; @Override protected void onDestroy() { super.onDestroy(); this.unregisterReceiver(mReceiver); } }
广播是非静态内部类,会持有Activity引用,而广播注册会将广播对象注册到系统内部,如果没有取消注册,那么系统中会存在Activity的应用,因此不能释放Activity,造成内存泄漏。
解决办法:就是在OnDestroy方法中取消注册广播。
集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。
原文里说的webview引起的内存泄漏主要是因为org.chromium.android_webview.AwContents 类中注册了component callbacks,但是未正常反注册而导致的。
org.chromium.android_webview.AwContents 类中有这两个方法 onAttachedToWindow 和 onDetachedFromWindow;系统会在attach和detach处进行注册和反注册component callback;
在onDetachedFromWindow() 方法的第一行中:
if (isDestroyed()) return;,
如果 isDestroyed() 返回 true 的话,那么后续的逻辑就不能正常走到,所以就不会执行unregister的操作;我们的activity退出的时候,都会主动调用 WebView.destroy() 方法,这会导致 isDestroyed() 返回 true;destroy()的执行时间又在onDetachedFromWindow之前,所以就会导致不能正常进行unregister()。
然后解决方法就是:让onDetachedFromWindow先走,在主动调用destroy()之前,把webview从它的parent上面移除掉。
ViewParent parent = mWebView.getParent(); if (parent != null) { ((ViewGroup) parent).removeView(mWebView); } mWebView.destroy();
完整的activity的onDestroy()方法:
@Override protected void onDestroy() { if( mWebView!=null) { // 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再 // destory() ViewParent parent = mWebView.getParent(); if (parent != null) { ((ViewGroup) parent).removeView(mWebView); } mWebView.stopLoading(); // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错 mWebView.getSettings().setJavaScriptEnabled(false); mWebView.clearHistory(); mWebView.clearView(); mWebView.removeAllViews(); mWebView.destroy(); } super.on Destroy(); }