android 动画 图片 内存溢出,Android之内存溢出和内存泄漏的原因和解决方案

基础

JAVA是在JVM所虚拟出的内存环境中运行的,内存分为三个区:堆、栈和方法区。

栈(stack):是简单的数据结构,程序运行时系统自动分配,使用完毕后自动释放。优点:速度快。

堆(heap):用于存放由new创建的对象和数组。在堆中分配的内存,一方面由java虚拟机自动垃圾回收器来管理,另一方面还需要程序员提供修养,防止内存泄露问题。

方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

概念

内存溢出(Out of Memory):系统会给每个APP分配内存也就是Heap Size值。当APP占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存时就会抛出的Out Of Memory异常。

内存泄漏(Memory Leak):当一个对象不在使用了,本应该被垃圾回收器(JVM)回收。但是这个对象由于被其他正在使用的对象所持有,造成无法被回收的结果。内存泄漏最终会导致内存溢出。

内存抖动:内存抖动是指在短时间内有大量的对象被创建或者被回收的现象,主要是循环中大量创建、回收对象。这种情况应当尽量避免。

它们三者的重要等级分别:内存溢出 > 内存泄露 > 内存抖动。

内存溢出对我们的App来说,影响是非常大的。有可能导致程序闪退,无响应等现象,因此,我们一定要优先解决OOM的问题。

强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。 当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

软引用:如果一个对象只具有软引用,但内存空间足够时,垃圾回收器就不会回收它;直到虚拟机报告内存不够时才会回收, 只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

弱引用:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。 不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

虚引用:虚引用可以理解为虚设的引用,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。

虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。 程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。 如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

关系

内存泄漏是造成应用程序OOM的主要原因之一。由于Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。

我们的App多次出现内存泄露,可能就会导致内存溢出。但是,我们的App出现内存溢出,不一定就是因为内存泄露,因为本身Android系统分配给每一个的App的空间就是那么一点。另外,内存泄露也不一定就会出现内存溢出,因为还是泄露的速度比较慢,系统将进程杀死了,也就不会内存溢出。不过,发现内存泄露,我们还是要第一时间解决。

危害

内存溢出:会触发Java.lang.OutOfMemoryError,造成程序崩溃。

内存泄漏:过多的内存泄漏会造成内存溢出,同样也会造成相关UI的卡顿现象。

判断是否有内存泄露的工具

LeackCanary

Memory Monitor

DDMS

处理方式汇总

强引用,软引用和弱引用

释放强引用,使用软引用和弱引用;

大量的图片、音频、视频处理,当在内存比较低的系统上也容易造成内存溢出

建议使用第三方,或者JNI来进行处理;

Bitmap对象的处理

不要在主线程中处理图片

使用Bitmap对象要用recycle释放

// Bitmap对象没有被回收

if (!bitmapObject.isRecyled()) {

// 释放

bitmapObject.recycle();

// 提醒系统及时回收

System.gc();

}

控制图片的大小,压缩大图,高效处理,加载合适属性的图片。

当我们有些场景是可以显示缩略图的时候,就不要调用网络请求加载大图,例如在RecyclerView中,我们在上下滑动的时候,就不要去调用网络请求,当监听到滑动结束的时候,才去加载大图,以免上下滑动的时候产生卡顿现象。

非静态内部类和匿名內部类Handler、Thread、Runnable等由于持有外部类Activity的引用,从而关闭activity,线程未完成造成内存泄漏

在Activity中创建非静态内部类,非静态内部类会持有Activity的隐式引用,若内部类生命周期长于Activity,会导致Activity实例无法被回收。(屏幕旋转后会重新创建Activity实例,如果内部类持有引用,将会导致旋转前的实例无法被回收)。

如果一定要使用内部类,就改用static内部类,在内部类中通过WeakReference的方式引用外界资源。对Handler、Thread、Runnable等使用弱引用,并且调用removeCallbacksAndMessages等移除。

举例:在下面这段代码中存在一个非静态的匿名类对象Thread,会隐式持有一个外部类的引用MainActivity 。同理,若是这个Thread作为MainActivity的内部类而不是匿名内部类,他同样会持有外部类的引用。

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

leakFun();

}

private void leakFun() {

new Thread(new Runnable() {

@Override

public void run() {

try {

Thread.sleep(10 * 1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

});

}

}

在线程休眠的这10s内,会一直隐式持有外部类的引用MainActivity,如果在10s之前,销毁MainActivity,就会报内存泄漏。同理,若是这个Thread作为MainActivity的内部类而不是匿名内部类,也会内存泄漏。

总而言之:如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。

解决办法:在这里只需要将为Thread匿名类定义成静态的内部类即可(静态的内部类不会持有外部类的一个隐式引用)。或保证在Activity在销毁之前,完成任务!

在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。

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

对于使用了BraodcastReceiver,ContentObserver,Cursor,File,Stream,ContentProvider,Bitmap,动画,I/O,数据库,网络的连接等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

广播BraodcastReceiver:记得注销注册unregisterReceiver();

文件流File:记得关闭流InputStream / OutputStream.close();

数据库游标Cursor:使用后关闭游标cursor.close();

对于图片资源Bitmap:当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存,再赋为null

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

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();

}

}

集合对象及时清理,使得JVM回收:我们通常会把对象存入集合中,当不使用时,清空集合,让相关对象不再被引用;

objectList.clear();

objectList=null;

注销监听器

在onPause()/onDestroy()方法中解除监听器,包括在Android自己的Listener,Location Service或Display Manager Service以及自己写的Listener。

static关键字修饰的变量由于生命周期过长,容易造成内存泄漏

static对象的生命周期过长,应该谨慎使用。一定要使用要及时进行null处理。

静态变量Activity和View会导致内存泄漏。例如:context,textView实例的生命周期与应用的生命周期一样,而他们都持有当前Activity的(MainActivity )引用,一旦MainActivity 销毁,而他的引用一直被持有,就不会被回收。所以,内存泄漏就产出了。

public class MainActivity extends AppCompatActivity{

private static Context context;

private static TextView textView;

@Override

protected void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

context = this;

textView = new TextView(this);

}

}

如果使用Context ,尽可能使用Applicaiton的Context

单例模式造成的内存泄漏,如context的使用,单例中传入的是activity的context,在关闭activity时,activity的内存无法被回收,因为单例持有activity的引用。

在context的使用上,应该传入application的context到单例模式中,这样就保证了单例的生命周期跟application的生命周期一样。

因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。

单例模式应该尽量少持有生命周期不同的外部对象,一旦持有该对象的时候,必须在该对象的生命周期结束前null

public class TestManager {

private static TestManager instance;

private Context context;

private TestManager(Context context) {

this.context = context;

}

public static TestManager getInstance(Context context) {

if (instance != null) {

instance = new TestManager(context);

}

return instance;

}

}

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长 ;

2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。

所以正确的单例应该修改为下面这种方式:

public class TestManager {

private static TestManager instance;

private Context context;

private TestManager(Context context) {

this.context = context.getApplicationContext();

}

public static TestManager getInstance(Context context) {

if (instance != null) {

instance = new TestManager(context);

}

return instance;

}

}

这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。

不要使用String进行字符串拼接

严格的讲,String拼接只能归结到内存抖动中,因为产生的String副本能够被GC,不会造成内存泄露。

频繁的字符串拼接,使用StringBuffer或者StringBuilder代替String,可以在一定程度上避免OOM和内存抖动。

三方库

对于EventBus,RxJava等一些第三方开源框架的使用,若是在Activity销毁之前没有进行解除订阅将会导致内存泄漏。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值