前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 进阶解密笔记-热修复

Android 进阶解密笔记-热修复

作者头像
Yif
发布2019-12-26 15:02:29
3740
发布2019-12-26 15:02:29
举报
文章被收录于专栏:Android 进阶Android 进阶
undefined
undefined

内存泄漏

什么是内存泄漏

内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

比如:当Activity的onDestroy()方法被调用后,Activity以及它涉及到的View和相关的Bitmap都应该被回收掉。但是,如果有一个后台线程做耗时操作,导致生命周期比Activity长,造成GC无法回收Activity,就造成内存泄漏。

内存泄漏后果

它是造成应用程序OOM的主要原因之一。由于android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就会导致程序崩溃等严重后果。

检测工具

最常见的是:Leakcanary leakCanarySquare开源框架,是一个Android和Java的内存泄露检测库,如果检测到某个 activity 有内存泄露,LeakCanary 就是自动地显示一个通知,所以可以把它理解为傻瓜式的内存泄露检测工具。通过它可以大幅度减少开发中遇到的oom问题,大大提高APP的质量。

常见的内存泄漏

单例造成的内存泄漏

单例在Android中经常使用,如果使用不当会造成内存泄漏,因为单例的静态特性使得他的生命周期与应用的生命周期一样长,这就造成当前对象的生命周期比单例短,单例又持有该对象的引用,GC无法回收该对象,就这导致内存泄漏,比如Context使用不当: 这里的Context如果使用的是activity的Context,造成单例持有activity的引用,它的生命周期又是整个应用的生命周期,导致activity无法销毁。则有可能造成内存泄漏,比如:

代码语言:javascript
复制
public class LoginManager {
    private static LoginManager mInstance;
    private Context mContext;
 
    //问题代码
    private LoginManager(Context context) {
        this.mContext = context;          
        //修改代码:this.mContext = context.getApplicationContext();
    }
    public static LoginManager getInstance(Context context) {
        if (mInstance == null) {
            synchronized (LoginManager.class) {
                if (mInstance == null) {
                    mInstance = new LoginManager(context);
                }
            }
        }
        return mInstance;
    }
    public void get() {}
}

场景: 在一个Activity中调用的,然后关闭该Activity则会出现内存泄漏。 LoginManager.getInstance(this).get();

解决方案: 可以将Context修改为AppLication的Context,代码如下: this.mContext = context.getApplicationContext(); 此时传入的是 ApplicationContext,因为 Application 的生命周期就是整个应用的生命周期,此时Context就和整个应用的生命周期一样,与单例的生命周期一样,单例持有的是整个application的引用,与activity无关,此时activity就正常可以销毁了,所以这将没有任何问题。

非静态内部类造成的内存泄漏

比如:

代码语言:javascript
复制
public class TestLeak extends AppCompatActivity {
    private static InnerClass mInnerClass = null;    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_test);
        if (mInnerClass == null) {
            mInnerClass = new InnerClass();
        }
    }    
    class InnerClass {
    }
}

代码中有一个非静态内部类InnerClass,有一个静态变量mInnerClass,在onCreate中进行了初始化操作,这个内部类实例就持有activity的强引用,而静态变量的生命周期与应用的生命周期一样长,而activity的生命周期比它短,想要销毁时,被持有引用,无法回收,继而造成内存泄漏。 解决方案:

  1. 将内部类设置为静态内部类,静态内部类不会持有外部类的引用,比如: public class TestLeak extends AppCompatActivity { private static InnerClass mInnerClass = null; @Override protected void onCreate(@Nullable Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.act_test);     if (mInnerClass == null) {         mInnerClass = new InnerClass();     } } static class InnerClass { } }
  2. 将该内部类抽取出来封装成一个单例,使用Application的Context。 public class InnerClass { private volatile static InnerClass instance;   private InnerClass(Context context) { }   public static InnerClass getInstance(Context context) {     if (instance == null) {         synchronized (InnerClass.class) {             if (instance == null) {                 instance = new InnerClass(context.getApplicationContext());             }         }     }     return instance; } } public class TestLeak extends AppCompatActivity { private static InnerClass mInnerClass = null;   @Override protected void onCreate(@Nullable Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.act_test);     if (mInnerClass == null) {         mInnerClass = InnerClass.getInstance(this);     } } }
  3. activity销毁时,将静态内部类设置为空 @Override protected void onDestroy() {     mInnerClass = null;     super.onDestroy(); }

Handler 造成的内存泄漏

比如:

代码语言:javascript
复制
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_main);
        mTextView = (TextView) findViewById(R.id.text);        //模拟内存泄露
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("yangchong");
            }
        }, 2000);
    }
}

从上面问题代码,可以看出这里通过内部类方式创建handler,而在java中,非静态内部类会持有外部类的引用,这里的postDelayed是一个延迟处理消息,将一个handler装入到message中,将消息放进消息队列messageQueueLooper进行取消息进行处理。如果此时activity要退出了,想要调用destroy销毁,但是此时Looper正在处理消息,Looper的生命周期明显比activity长,这将使得activity无法被GC回收,最终造成内存泄漏。并且此时handler还持有activity的引用,也是造成内存泄漏的一个原因(不是根本原因)。

解决方案:

  1. 静态内部类+弱引用 将Handler的子类设置成 静态内部类,并且可加上 使用WeakReference弱引用持有Activity实例 原因:弱引用的对象拥有短暂的生命周期。而垃圾回收器不管内存是否充足都会回收弱引用对象。
代码语言:javascript
复制
public class HandlerActivity extends AppCompatActivity  {
    private static class MyHandler extends Handler {
    private final WeakReference<HandlerActivity> mActivity;
    public MyHandler(HandlerActivity activity) {
        mActivity = new WeakReference<HandlerActivity>(activity);
    }
 
    @Override
    public void handleMessage(Message msg) {
        HandlerActivity activity = mActivity.get();
        if (activity != null) {
        }
    }
 
    private final MyHandler mHandler = new MyHandler(this);
    private static final Runnable mRunnable = new Runnable() {
        @Override
        public void run() { }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler.postDelayed(mRunnable, 1000 * 60 * 1);
        finish();
    }
}
  1. Activity生命周期结束时,清空消息队列 只需在ActivityonDestroy()方法中调用mHandler.removeCallbacksAndMessages(null);就行了。 @Override protected void onDestroy() { super.onDestroy(); if(handler!=null){     handler.removeCallbacksAndMessages(null);     handler = null; } }

线程造成的内存泄漏

线程造成的内部泄漏以AsyncTask为例 比如:

代码语言:javascript
复制
public class MainActivity extends AppCompatActivity {
 
    private AsyncTask<Void, Void, Integer> asyncTask;
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.text);
        testAsyncTask();
        finish();
    }
    private void testAsyncTask() {
        asyncTask = new AsyncTask<Void, Void, Integer>() {
            @Override
            protected Integer doInBackground(Void... params) {
                int i = 0;
                //模拟耗时操作
                while (!isCancelled()) {
                    i++;
                    if (i > 1000) {
                        break;
                    }
 
                }
                return i;
            }
 
            @Override
            protected void onPostExecute(Integer integer) {
                super.onPostExecute(integer);
                mTextView.setText(String.valueOf(integer));
            }
        };
        asyncTask.execute();
    }
}

造成内存泄漏原因分析 在处理一个比较耗时的操作时,可能还没处理结束MainActivity就执行了退出操作,这时候线程的生命周期比activity长,又AsyncTask依然持有对MainActivity的引用,最后导致MainActivity无法被GC回收引发内存泄漏。 解决方案: 在Activity销毁时候也应该取消相应的任务AsyncTask.cancel()方法,避免任务在后台执行浪费资源,进而避免内存泄漏的发生

代码语言:javascript
复制
private void destroyAsyncTask() {
    if (asyncTask != null && !asyncTask.isCancelled()) {
        asyncTask.cancel(true);
    }
    asyncTask = null;
}
@Override
protected void onDestroy() {
    super.onDestroy();
    destroyAsyncTask();
}

不需要用的监听未移除会发生内存泄露

比如: add监听,放到集合里面

代码语言:javascript
复制
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
    @Override
    public void onWindowFocusChanged(boolean b) {
        //监听view的加载,view加载出来的时候,计算他的宽高等。
    }
});

解决方案 计算完后,一定要移除这个监听

代码语言:javascript
复制
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);

资源未关闭造成的内存泄漏

比如:BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback,EventBus等在 Activity onDestroy 或者某类生命周期结束之后一定要 unregister 或者 close 掉,否则这个 Activity 类会被系统强引用,不会被内存回收。值得注意的是,关闭的语句必须在finally中进行关闭,否则有可能因为异常未关闭资源,致使activity泄漏。

动画资源未释放导致内存泄漏

比如:

代码语言:javascript
复制
public class LeakActivity extends AppCompatActivity {
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        textView = (TextView)findViewById(R.id.text_view);
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
        objectAnimator.start();
    }
}

解决方案: 在属性动画中有一类无限循环动画,如果在Activity中播放这类动画并且在onDestroy中去停止动画,那么这个动画将会一直播放下去,这时候Activity会被View所持有,从而导致Activity无法被释放。解决此类问题则是需要早Activity中onDestroy去去调用objectAnimator.cancel()来停止动画。

代码语言:javascript
复制
@Override
protected void onDestroy() {
    super.onDestroy();
    mAnimator.cancel();
}

系统bug之InputMethodManager导致内存泄漏

每次从MainActivity退出程序时总会报InputMethodManager内存泄漏,原因系统中的InputMethodManager持有当前MainActivity的引用,导致了MainActivity不能被系统回收,从而导致了MainActivity的内存泄漏。查了很多资料,发现这是 Android SDK中输入法的一个Bug,在15<=API<=23中都存在,目前Google还没有解决这个Bug。 解决方案如下:

代码语言:javascript
复制
public class MemoryLeakUtil {
 
    public static void fixInputMethodMemoryLeak(Context context) {
        if (context == null)
            return;
        InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        if (inputMethodManager == null)
            return;
 
        String[] viewArr = new String[]{&quot;mCurRootView&quot;, &quot;mServedView&quot;, &quot;mNextServedView&quot;};
        Field field;
        Object fieldObj;
        for (String view : viewArr) {
            try {
                field = inputMethodManager.getClass().getDeclaredField(view);
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                fieldObj = field.get(inputMethodManager);
                if (fieldObj != null &amp;&amp; fieldObj instanceof View) {
                    View fieldView = (View) fieldObj;
                    if (fieldView.getContext() == context) {
                        //注意需要判断View关联的Context是不是当前Activity,否则有可能造成正常的输入框输入失效
                        field.set(inputMethodManager, null);
                    } else {
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
 
}

最后在泄漏的Activity,调用工具类

代码语言:javascript
复制
 @Override
    protected void onDestroy() {
        //手动切断InputMethodManager里的View引用链
        MemoryLeakUtil.fixInputMethodMemoryLeak(this);
        super.onDestroy();
    }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年7月27日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内存泄漏
    • 什么是内存泄漏
      • 内存泄漏后果
        • 检测工具
        • 常见的内存泄漏
          • 单例造成的内存泄漏
            • 非静态内部类造成的内存泄漏
              • Handler 造成的内存泄漏
                • 线程造成的内存泄漏
                  • 不需要用的监听未移除会发生内存泄露
                    • 资源未关闭造成的内存泄漏
                      • 动画资源未释放导致内存泄漏
                        • 系统bug之InputMethodManager导致内存泄漏
                        相关产品与服务
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档