App性能优化-内存泄漏

App性能优化-内存优化实践

1. 常见类型

  • 强引用引起民的内存泄漏
  • 单例对像调用匿名内部类持有引起的内存泄漏
  • 静态或者单例持有引用,导致View或Activity无法释放

2.常见处理方法

常见处理方法有 弱引用静态内部类置空释放 等处理方法

2.1 弱引用

  • context 等引用换弱引用,弱引用可能是解决内存泄漏相关问题的有效工具,值得掌握

    比如在使用context时:
    WeakReference weakContext = new WeakReference<>(context);
    如果使用时直接使用

       H5Activity.newInstance(weakContext.get(), Constant.H5_REGIST_SERVICE_PROTOCAL, null);
    复制代码

    注意,context.get() 之后不能重新被赋值引用,否则会重新添加成为强引用, 如果再使用Context引用了get(),则直接会恢复变成强引用,就失去了效果,比如这样的:

       WeakReference<Context> weakContext = new WeakReference<>(context);
       //经过声明后变成了强引用
       Context sContext = weakContext.get();
       H5Activity.newInstance(weakContext.get(), Constant.H5_REGIST_SERVICE_PROTOCAL, null);
    
    复制代码
  • 注意点:尽量用applicationContext来迭代Activity的context。资源文件的引用,如果可以用Appliction的context来获取文件,如getColor, getDrawable,则不使用Activity的context,来避免内存泄漏

2.2 单例对像调用匿名内部类

2.2.1 场景

匿名内部类会包含当前外部对象的引用,如果静态方法调用了这个匿名内部类,则会出现内存泄漏,比如这样的:

MainActivity{
    //极光推送绑定当前账号
    private void bindJpush(){
        JPush
        .getInstance()//
        .bindAlias(alias,new JLPushAliasBindListener(){
             //XXX处理绑定成功的逻辑
            }}
    }
}
复制代码
2.2.2 原因

因为JLPushAliasBindListener 是一个匿名内部类,会持有当前activity的引用,经过一JPush的单例的调用之后,JPush里会持有 listenr,listener会持有MainActivity的引用,就会造成MainActivity的泄漏,持有路径是这样的:

    Jpush->JLPushAliasBindListener->MainActivity
复制代码

这样即使退出登录后,MainActivity已经onDestroy了,但是因为一直被Jpush所持有,则迟迟不能回收,出现内存泄漏

2.2.3 方案
2.2.3.1 方案1-定义静态内部类代替匿名内部类

静态内部类实现接口代替原匿名内部类,可以使不再持有MainActivity的引用
如:

MianActivity.class{
       //定义静态内部类 
       static class JpushBindListener implements JLPushAliasBindListener {
           @Override
           public void onSuccess(AliasBindResult aliasBindResult) {
               LogUtil.e("bindAlias success" + aliasBindResult.toString());
               PushUtils.bindPushToken(true, aliasBindResult)
                       .subscribe(bindPushTokenBean -> {
                        //处理相关逻辑xxx           
                        }
       }
}
复制代码
2.2.3.2 方案2-定义外部类

定义普通接口实现类来替换原匿名内部类,原因和静态内部类思想一致,可以不再持有MainActivity的引用。

//外部类
public class JpushBindListener implements JLPushAliasBindListener {
    @Override
    public void onSuccess(AliasBindResult aliasBindResult) {
        LogUtil.e("bindAlias success" + aliasBindResult.toString());
        PushUtils.bindPushToken(true, aliasBindResult)
                .subscribe(bindPushTokenBean -> {
                    String bindPushTokenStr = GsonUtil.toJson(aliasBindResult);
                    SpUtils.setPrefString(SpKeys.KEY_PUSH_BIND_ALIAS, bindPushTokenStr);
                    LogUtils.e("bindalias suc----" + bindPushTokenBean);
                }, throwable -> LogUtils.e("bindalias failed----" + throwable.getMessage()));
    }
}
复制代码

MainActivity调用

JLPush.getInstance().bindAlias(userid, new JpushBindListener());
复制代码
2.2.3.3 方案2-手动解除引用
  • 手动解除引用,关联生命周期,在onDestroy时时候置null,解除对MainActivity的引用,
protected void onDestroy(){
    //手动赋值为空,将Jpush里的listener置为空,解除对MainActivity的引用
    JLPush.getInstance().bindAlias(userid, null);
}

复制代码

2.3 静态类或者单例持有context引用,导致Activity等引用无法释放

这个和第2.2很像,是直接引用了对象,导致静态类的生命周期内,无法对其持有的context对象进行回收,下面用两个案例来说明,及对应的处理方法:

2.3.1 案例1-单例持有context
2.3.1.1 泄漏原因

经典的单例模式持有context,有一Nfc读卡类,属于导入使用第3方sdk,需传入context进行初始化,持有context,导致在读卡完成之后,无法释放。

  1. 初始化时传入activity,却未提供初始化空的方法

    NfcReadCardActivity {
    onCreate(){
            NFCSDKInitializer.GetInstance().initNFCIntent(this);
        }
    }
    复制代码
  2. 在NFCSDKInitializer持有当前activity引用导致泄漏

    class NFCSDKInitializer{
      public void initNFCIntent(final Activity activity) { 
            this.activity = activity;
        //xxxxxx
      }
    }
    复制代码
2.3.1.2 解决方案

因为是第三方sdk原因,
1)无法在第三方sdk内去进行弱引用优化处理,
2)也无法通过修改源代码,提供主动释放资源的api,
所以采用绑定生命周期的方法,即在activity销毁时,手动将 NFCSDKInitializer 中的activity赋值为空,解除对activity的引用

   protected void onDestroy() {
        super.onDestroy();
        //将activity引用,用空赋值
        NFCSDKInitializer.GetInstance().initNFCIntent(null);
    }
复制代码
2.3.2 案例二:单例间接持有context引用
2.3.2.1案例分析

现有读卡JlDeviceEngine工具类,提供单例模式get()调用,负责读卡操作,实例化后持有读卡类CardReader,CardReader在读卡过程中持有的Activity,引用调用栈为

JlDeviceEngine->CardReader-> ReadcardConsumer ->ReadcardPresenter.mView->NfcReadCardActivity
复制代码
2.3.2.2 解决方案
  1. 结束时置空释放

断开其中持有view的引用部分,在读卡完成后,在onStop方法中,将持有的mContext 和 mView置空进行释放,相关代码实现:

    // ReadcardPresenter.Java类
    @Override
    public void stop() {
        mContext = null;//避免内存泄漏
        mView = null;
    }

复制代码
  1. 使用弱引用,解决强引用泄漏问题
ReadcardConsumer.Java类
  
    //使用弱引用
    WeakReference< ReadCardContact.View> viewWeakReference;//解决资源泄漏问题

    public ReadcardConsumer(ReadCardContact.View view) {
        viewWeakReference = new WeakReference<>(view);
    }
       
    //调用时使用方法
    @Override
    public void onInputPin() {
        if (viewWeakReference.get() != null) {
                viewWeakReference.get().showInputPin(PosUtils.getPOSSN());
        }
    }

复制代码
  1. 二次优化方案: 找到源头解决内存泄漏的根源

因为内存泄漏的根本源头在于 JlDeviceEngine,持有cardReader实例化对象CardReaderNfcImpl,此对象如果一直持有,不释放是对内存的消耗,综合考虑每次读卡完成之后,释放CardReader,这样从源头释放静态单例对像引发的泄漏问题

    //ReadCardPresenter.Java类添加
   /**
     * 停止读卡
     */
    @Override
    public void stop() {
        JlDeviceEngine.getInstance().stopReadCard();//释放读卡资源
        mContext = null;//避免内存泄漏
        mView = null;
    }

    //JlDeviceEngine 中的释放读卡资源引用
    /**
     * 停止读卡,释放资源
     */
    public void stopReadCard() {
        if (cardReader != null) {
            cardReader.stopReadcard();
            cardReader = null;
        }
    }

复制代码

3.总结

文中分析了弱引用、静态内部类替代匿名内部类、主动置空释放资源等操作引起内存泄漏的原因,结合案例分析一些项目的关于内存泄漏的典型解决方法,提供一些解决思路,供参考

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享