如何防止内存泄露 - 1835434698/1835434698.github.io GitHub Wiki
内存泄露多种多样,举例说明一下如何防治内存泄露。
1、Handler内存泄露
还是老生常谈的Handler内存泄露,有些人觉得创建一个Handler没有post或者send消息,或者没有循环处理的消息,只是调用一次就销毁了,不会造成内存泄露的,其实这种想法是不正确的。你没有给Handler写死循环就不代表它执行完你的任务就会销毁。别忘了Handler本身就是一个“死循环”,只是MessageQueue为空的时候looper就休眠了,所以,大部分情况下Handler是不会自动销毁的。举一个例子,某下拉刷新控件的早期版本就有这么一个类PtrFrameLayout(后来作者修复了,直接去掉了Handler),源码
private Handler mHandler = new Handler();
public void autoRefresh(final boolean atOnce, final int duration) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mStatus != PTR_STATUS_INIT) {
return;
}
mFlag |= atOnce ? FLAG_AUTO_REFRESH_AT_ONCE : FLAG_AUTO_REFRESH_BUT_LATER;
mStatus = PTR_STATUS_PREPARE;
if (mPtrUIHandlerHolder.hasHandler()) {
mPtrUIHandlerHolder.onUIRefreshPrepare(PtrFrameLayout.this);
if (DEBUG) {
PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag);
}
}
mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToRefresh(), duration);
if (atOnce) {
mStatus = PTR_STATUS_LOADING;
performRefresh();
}
}
}, 200);
}
没有写销毁Handler的方法,因此内存泄露了。在实际项目中用刷新的页面是很多的,如果都用了这个,每一个页面都会内存泄露,一个App就会很严重的泄露。
修复方法:
首先想到的肯定是无侵入性修复方法。
1、view不是有postDelayed方法吗?是的如果是view的话用这个替换Handler即可,如果是ViewGroup的话就不行了。
2、viewGroup不是有一个onDetachedFromWindow方法吗?在这个里面直接销毁Handler多好了。
但是,onDetachedFromWindow方法解释是 This is called when the view is detached from a window.当视图与窗口分离时,将调用此命令。也就是当前view从视图窗口分离出去了,一般情况下分离出去就等于销毁了,但是,如何是有缓存的控件呢比如listView、ViewPage等,他们从视图窗口分离并不等于销毁。因此在onDetachedFromWindow方法里面销毁Handler的时候需要在调用Handler的地方判断一下是否为空。
2、自定义View内存泄露
有些自定义view会需要开启一个线程不停的运行,但是设计者没有考虑到页面关闭时销毁线程,并且很多地方都在调用这个View,如果添加销毁方法需要改动的方法太多。例子
mThread = new Thread(new Runnable {
@Override
public void run() {
for (float i = 0; i < Integer.MAX_VALUE; ) { // 创建一个死循环,每循环一次i+0.1
try {
for (int j = 0; j < pointers.size(); j++) { // 循环改变每个指针高度
float rate = (float) Math.abs(Math.sin(i + j)); // 利用正弦有规律的获取0~1的数。
pointers.get(j).setHeight((basePointY - getPaddingTop()) * rate); // rate 乘以 可绘制高度,来改变每个指针的高度
}
Thread.sleep(pointerSpeed); // 休眠一下下,可自行调节
if (isPlaying) { ///这个只会暂停不会停止
AndroidThreadExecutor.getInstance().postToMainThread(VoicePlayingView.this::invalidate);
i += 0.1;
}
} catch (InterruptedException ignored) {
}
}
}
});
mThread.start();
/**
* 开始
*/
public void start() {
if (!isPlaying) {
if (mThread == null) {
mThread = new Thread(new MyRunnable());
mThread.start();
}
isPlaying = true; // 控制子线程中的循环
isDestory = false;
}
}
/**
* 暂停
*/
public void stop() {
isPlaying = false;
if (mThread != null) {
mThread.interrupt();
}
invalidate();
}
修复方法:
首先想到的肯定是无侵入性修复方法。
1、首先考虑到onDetachedFromWindow方法调用的时候可以停止runnable丫,但是不要忘了This is called when the view is detached from a window.当视图与窗口分离时,将调用此命令。也就是当前view从视图窗口分离出去了,一般情况下分离出去就等于销毁了,但是,如何是有缓存的控件呢比如listView、ViewPage等,他们从视图窗口分离并不等于销毁。因此此方法不可用。
然后想到可以模仿AutoDispose,拿到LifecycleOwner来监听生命周期,通过构造方法里面可以拿到context,强转Activity,监听生命周期Activity,当销毁的时候可以停止runnable。很高兴的去实现了。但是,完成后发现不行啊,因为Activity有fragment,fragment销毁了的时候,Activity并没有销毁,还是内存泄露了。。。。
继续找方法,Glide不就可以通过View获取生命周期防止泄露吗?开始撸代码。
private LifecyObserver lifecyObserver;
private void removeObserver() {
if (lifecyObserver != null){
((AppCompatActivity)contextSoftReference.get()).getLifecycle().removeObserver(lifecyObserver);
}
}
class LifecyObserver implements DefaultLifecycleObserver{
private SoftReference<VoicePlayingView> softReference;
public LifecyObserver(VoicePlayingView view){
softReference = new SoftReference<>(view);
}
@Override
public void onDestroy(@NonNull LifecycleOwner owner) {
softReference.get().isDestory = true;
softReference.get().removeObserver();
}
}
public VoicePlayingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
contextSoftReference = new SoftReference<>(context);
if (lifecyObserver == null){
if (contextSoftReference.get() instanceof AppCompatActivity){
lifecyObserver = new LifecyObserver(this);
Fragment fragment = findSupportFragment(this, (FragmentActivity) contextSoftReference.get());
if (fragment != null){
fragment.getLifecycle().addObserver(lifecyObserver);
}else {
((AppCompatActivity)contextSoftReference.get()).getLifecycle().addObserver(lifecyObserver);
}
}
}
}
可是发现Fragment为null啊,什么鬼?为什么?静心思考。这构造方法,这个时候View可能还没有绑定到Fragment。那就不在构造方法去设置,继续撸代码。
public void start() {
if (lifecyObserver == null){
if (contextSoftReference.get() instanceof AppCompatActivity){
lifecyObserver = new LifecyObserver(this);
Fragment fragment = findSupportFragment(this, (FragmentActivity) contextSoftReference.get());
if (fragment != null){
fragment.getLifecycle().addObserver(lifecyObserver);
}else {
((AppCompatActivity)contextSoftReference.get()).getLifecycle().addObserver(lifecyObserver);
}
}
}
if (!isPlaying) {
if (mThread == null) {
mThread = new Thread(new MyRunnable());
mThread.start();
}
isPlaying = true; // 控制子线程中的循环
isDestory = false;
}
}