为什么要进行内存优化?
在android中,对内存的优化是一个app很重要的一个指标,内存优化的比较好的app用起来比较流畅而且出现崩溃的概率会大大降低。 虽然由于科技的发展,使得手机内存越来越高,但是由于引用版本更新迭代以及技术的革新,导致我们的app使用内存也越来越高。
一张图来描述微信各个版本所占用的系统内存:
常见程序程序所占用的内存:
如果内存无法妥善处理好,容易出现如下问题:内存泄漏、内存抖动、内存溢出。
##内存泄漏
Android进程中某些对象(垃圾对象)已经没有使用价值了,比如一个执行了finish的Activity,但是它们却可以直接或间接地被引用到,导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用卡顿、最终Crash。
如图:
虽然A、B对象相互调用,或A、B、C三者相互调用,但也是属于垃圾对象,可以被GC检测回收。
###内存泄漏导致的问题
1.导致用户手机可用内存变少
2.程序出现卡顿
3.导致应用莫名退出
4.应用程序Force Close
5.用户流失
#内存泄漏的解决方式
##举例:Handler内存泄漏
1.为什么Handler使用会造成内存泄漏?
熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理。那么当Activity退出后,msg可能仍然存在于消息队列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。
2.正确的Handler使用方式(不展示错误的使用方式,避免看到陷入误区)
(1)将Handler声明为静态内部类。因为静态内部类不会持有外部类的引用,所以不会导致外部类实例出现内存泄露。
(2)在Handler中添加对外部Activity的弱引用。由于Handler被声明为静态内部类,不再持有外部类对象的引用,导致无法在handleMessage()中操作Activity中的对象,所以需要在Handler中增加一个对Activity的弱引用。
代码展示:
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
##举例:动画造成的内存泄漏
(1)问题描述
这个问题,也是我在开发过程中经常不注意的一个问题。对于动画在关闭页面没有进行任何cancel的操作或者将动画在不需要的时候只是将执行动画的对象设置为不可见,尤其是那种无限循环的动画,比如属性动画中,将属性动画的执行周期设置为无限循环:animator.setRepeatCount(ValueAnimator.INFINITE);
虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。
(2)解决方式:
@Override
protected void onDestroy() {
super.onDestroy();
// 取消动画
animator.cancel();
}
不仅包括属性动画,无限循环的动画,最好所有的动画都要在适当的时候进行cancel( )的操作,而且,对于使用动画的控件,也要进行clearAnimation( )的操作。
##举例:WebView使用不合理导致的内存泄漏
分析:关于WebView的泄漏,主要由于Activity释放后,WebView还会持有当前Activity的引用导致。后续新启一篇文章单独介绍。
先附上WebVIew的常用解决方式:
public void destroy() {
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();
try {
mWebView.destroy();
} catch (Throwable ex) {
}
}
}