1.静态变量造成的内存泄漏
public class MainActivity extends Activity{
private static final String TAG = "MainActivity";
private static Context sContext;
private static View sView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//这里直接把当前Activity赋值给了静态变量sContext
sContext = this;
//这种写法和上面的类似
sView = new View(this);
}
}
当MainActivity对象完成任务需要回收时,却有一个静态变量引用它(静态变量的生命周期与Application相同),造成内存泄漏
2.单例模式造成的内存泄漏
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
我们来分析下为什么会产生内存泄漏呢?
AppManager appManager=AppManager.getInstance(this);
这句传入的是Activity的Context,我们都知道,Activty是间接继承于Context的,当这Activity退出时,Activity应该被回收, 但是单例中又持有它的引用,导致Activity回收失败,造成内存泄漏。为了以防误传Activity的Context , 我们可以修改一下单例的代码,如下:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
这样子修改,不管外面传入什么Context,最终都会使用Applicaton的Context,而我们单例的生命周期和应用的一样长,这样就防止了内存泄漏。
3.非静态内部类创建静态实例造成的内存泄漏
public class NonStaticActivity extends AppCompatActivity {
private static Config sConfig;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_non_static);
//Config类并不是静态类,
sConfig = new Config();
}
class Config {
}
}
首先,非静态内部类默认会持有外部类的引用。
然后又使用了该非静态内部类创建了一个静态的实例。
该静态实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
正确的做法有两种,一种是将内部类Config改成静态内部类,还有就是将Config抽取出来,封装成一个单例,如上一个例子那样,但是需要context时单例要切记注意Context的泄漏,使用applicationContext。
4.WebView造成的内存泄漏
即使当我退出页面时在我的BrowserActivity的onDestroy()方法中进行内存占用回收但并没有效果:
mWebView.removeAllViews();
mWebView.destroy();
mWebView=null;
当我点开了多少条新闻内存中就存在多少个BrowserActivity的实例,说明我退出时这个BrowserActivity没有被回收,这样的话当我浏览的新闻比较多时,内存就会累积存在一定的OOM风险,而且新闻界面一般存在大量图片,所以这个问题是必须要解决的。
a. 不要在.xml文件中定义webview节点
不要再布局文件中定义webview的节点,而是在需要的时候动态生成。你可以在需要webview的布局位置放一个LinearLayout,需要时在代码中动态生成webview并add进去:
mWebView=new WebView(this);
LinearLayout linearLayout = findViewById(R.id.xxx);
linearLayout.addView(mWebView);
然后在onDestroy()方法中调用:
@Override
protected void onDestroy() {
if( mWebView!=null) {
mWebView.setVisibility(View.GONE);
mWebView.removeAllViews();
mWebView.destroy();
}
super.onDestroy();
}
关于创建webview时mWebView=new WebView(this);到底是传入ApplicationContext还是Activity的context:个人建议还是传activity的context。传ApplicationContext貌似可以防止webview对activity的引用,但是在很多情况下会报错,很多都有说到webview加载的页面弹出dialog时会出现报错;所以传入activity的context然后做好销毁工作。
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) {
}
}
然后在activity中调用:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setConfigCallback(WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
}
public void onDestroy() {
setConfigCallback(null);
super.onDestroy();
}
c. 进程
为加载WebView的界面开启新进程,在该页面退出之后关闭这个进程。
这个方法我没有测试,不知道应用和效果如何,有兴趣的可以试试。
d.从根源解决(划重点)
前面的方法都没有解决我内存泄漏的问题,然后我看到了一篇文章是从源码角度分析了webview内存泄漏的原因,最后按作者的方法解决了问题,后面会贴上原文地址。这里简单说一下:
原文里说的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();
}
这个方法亲测有效。
5.Handler造成的内存泄漏
我在我的项目中使用了handler,此时mHandler会隐式地持有一个外部类对象引用这里就是MainActivity,当执行postDelayed方法时,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,MessageQueue是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
mTextView = (TextView) findViewById(R.id.text);//模拟内存泄露
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("test");
}
}, 5 * 60 * 1000);
}
}
解决办法是 在HandlerActivity onDestroy里面移除消息队列中所有消息和所有的Runnable。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
其他原因造成的内存泄漏
造成内存泄漏的原因有很多,我们这里只是列举了其中比较典型的几种,当然还有好多原因会造成内存泄漏,比如资源开启但是未关闭、多线程等等等等。但是我们有LeakCanary这个利器哈。