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,导致在读卡完成之后,无法释放。
-
初始化时传入activity,却未提供初始化空的方法
NfcReadCardActivity { onCreate(){ NFCSDKInitializer.GetInstance().initNFCIntent(this); } } 复制代码
-
在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 解决方案
- 结束时置空释放
断开其中持有view的引用部分,在读卡完成后,在onStop方法中,将持有的mContext 和 mView置空进行释放,相关代码实现:
// ReadcardPresenter.Java类
@Override
public void stop() {
mContext = null;//避免内存泄漏
mView = null;
}
复制代码
- 使用弱引用,解决强引用泄漏问题
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());
}
}
复制代码
- 二次优化方案: 找到源头解决内存泄漏的根源
因为内存泄漏的根本源头在于 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.总结
文中分析了弱引用、静态内部类替代匿名内部类、主动置空释放资源等操作引起内存泄漏的原因,结合案例分析一些项目的关于内存泄漏的典型解决方法,提供一些解决思路,供参考