内存泄漏篇

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这个利器哈。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容