android 内存优化一

常见内存泄露原因

Context对象泄漏

1、如果一个类持有Context对象的强引用,就需要检查其生存周期是否比Context对象更长。否则就可能发生Context泄漏。

2、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

例如View#setTag(int, Object)的内存泄漏https://code.google.com/p/android/issues/detail?id=18273

3、把Context对象赋给static变量。

避免Context对象泄漏Checklist

1、检查所有持有对Context对象强引用的对象的生命周期是否超出其所持有的Context对象的生命周期。

2、检查有没有把View传出到View所在Context之外的地方,如果有的话就需要检查生命周期。

3、工具类中最好不要有Context成员变量,尽量在调用函数时直接通过调用参数传入。如果必须有Context成员变量时,可以考虑使用WeakReference来引用Context对象。

4、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

5、 检查把Context或者View对象赋给static变量的地方,看是否有Context泄漏。

6、检查所有把View放入容器类的地方(特别是static容器类),看是否有内存泄漏。7、使用WeakHashMap也需要注意有没有value-key的引用。

7、尽量使用ApplicationContext。

Handler对象泄漏

1、发送到Handler的Message实际上是加入到了主线程的消息队列等待处理,每一个Message持有其目标Handler的强引用。

如我们通常使用的匿名内部类Handler

HandlermHandler = new Handler() {
    @Override
    public voidhandleMessage(Message msg) {
       mImageView.setImageBitmap(mBitmap);
    }
}

上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象 (通常是一个Activity)的引用,因为View会依附着一个Activity。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图 片)一起出现,这个后台线程在任务执行完毕(例如图片下载完 毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况 下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给 Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片 下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条 Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

当然,应为是Handler对外部持有引用的原因,我们就可以将Activity设置为一个弱引用,在不必要的时候,不再执行内部方法。

import android.app.Activity;
importandroid.content.Context;
importandroid.os.Handler;
importandroid.os.Message;
  
importjava.lang.ref.WeakReference;
  
publicclass WeakRefHandler extends Handler
{
    WeakReference<context> mWeakContext;
  
    public WeakRefHandler(Context context)
    {
        mWeakContext = newWeakReference<context>(context);
    }
  
    @Override
    public void handleMessage(Message msg)
    {
        if((mWeakContext.get() instanceofActivity )&& ((Activity)mWeakContext.get()).isFinishing())
                return ;
        if(mWeakContext==null){
            return ;
        }
        super.handleMessage(msg);
    }
}
避免内部Getters/Setters
在Android中,虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。
 

避免使用浮点数

通常的经验是,在Android设备中,浮点数会比整型慢两倍。

Adapter适配器
 
在Android中Adapter使用十分广泛,特别是在list中。所以adapter是数据的 “集散地” ,所以对其进行内存优化是很有必要的。
下面算是一个标准的使用模版:
主要使用convertView和ViewHolder来进行缓存处理
//ViewHolder方式
@Override
public View getView(int position, View convertView, ViewGroup parent) {
  ViewHolder holder;
  if(convertView == null)
  {
    holder = new ViewHolder();
    convertView = mInflater.inflate(R.layout.list_item, null);
    holder.img = (ImageView)item.findViewById(R.id.img)
    holder.title = (TextView)item.findViewById(R.id.title);
    holder.info = (TextView)item.findViewById(R.id.info);
    convertView.setTag(holder);
  }else
  {
    holder = (ViewHolder)convertView.getTag();
  }
    holder.img.setImageResource(R.drawable.ic_launcher);
    holder.title.setText("Hello");
    holder.info.setText("World");
  }
                                                                                                                                                                        
  return convertView;
}

//HolderView方式
@Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
  HolderView holderView;
 
  if (convertView instanceof HolderView) {
    holderView = (HolderView) convertView;
  } else {
    holderView = new HolderView(mContext);
  }
  holderView.bind("标题", R.drawable.ic_launcher, "sajsa");
  return holderView;
}
public class HolderView extends GridLayout {
  private ImageView img;
  private TextView title;

  public HolderView(Context context, AttributeSet attrs) {
    super(context, attrs);
    View v = LayoutInflater.from(context).inflate(R.layout.list_item, this);
    title = (TextView) v.findViewById(R.id.title);
    img = (ImageView)item.findViewById(R.id.img)

  }
  public void bind(String stringtitle,int imgrsc, String stringinfo) {
    title.setText(stringtitle);
    img.setImageResource(imgrsc);

  }
}

//自己的
@Override
    protected View getExView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub

        NotificationLayoutItem itemView;
        if (convertView instanceof NotificationLayoutItem) {
            itemView = (NotificationLayoutItem) convertView;
        } else {
            // itemView = new NotificationLayoutItem(mContext);
            itemView = (NotificationLayoutItem) LayoutInflater.from(mContext)
                    .inflate(R.layout.notificationayoutitem, null);
        }
        itemView.setData(mList.get(position));
        return itemView;
    }
View Code
池(PooL)
 

对象池:

对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。 并非 所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开 销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。

 

 

线程池:

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对 线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。

 

java提供了ExecutorServiceExecutors类,我们可以应用它去建立线程池。

通常可以建立如下4种:

    /** 每次只执行一个任务的线程池 */  
    ExecutorService singleTaskExecutor =  Executors.newSingleThreadExecutor();  
      
    /** 每次执行限定个数个任务的线程池 */  
    ExecutorService limitedTaskExecutor = Executors.newFixedThreadPool(3);  
      
    /** 所有任务都一次性开始的线程池 */  
    ExecutorService allTaskExecutor = Executors.newCachedThreadPool();  
      
    /** 创建一个可在指定时间里执行任务的线程池,亦可重复执行 */  
    ExecutorService scheduledTaskExecutor = Executors.newScheduledThreadPool(3);  

引用类型:

引用分为四种级别,这四种级别由高到低依次为:强引用>软引用>弱引用>虚引用。

强引用(strong reference)
如:Object object=new Object(),object就是一个强引用了。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

软引用(SoftReference)
只有内存不够时才回收,常用于缓存;当内存达到一个阀值,GC就会去回收它;

 

弱引用(WeakReference)   

弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 

 

虚引用(PhantomReference)   

"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。  

 

软引用和弱引用的应用实例:

注意:对于SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,所以下面的内容可以选择忽略。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

下面以使用软引用为例来详细说明(弱引用的使用方式与软引用是类似的):

假设我们的应用会用到大量的默认图片,而且这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我 们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生 OutOfMemory异常。这时,我们可以考虑使用软引用技术来避免这个问题发生。

首先定义一个HashMap,保存软引用对象。

    //首先定义一个HashMap,保存软引用对象。

    private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();  

    //再来定义一个方法,保存Bitmap的软引用到HashMap。

    
    public void addBitmapToCache(String path) {  
           // 强引用的Bitmap对象  
           Bitmap bitmap = BitmapFactory.decodeFile(path);  
           // 软引用的Bitmap对象  
           SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);  
           // 添加该对象到Map中使其缓存  
           imageCache.put(path, softBitmap);  
       }  

   //获取的时候,可以通过SoftReference的get()方法得到Bitmap对象。

    
    public Bitmap getBitmapByPath(String path) {  
            // 从缓存中取软引用的Bitmap对象  
            SoftReference<Bitmap> softBitmap = imageCache.get(path);  
            // 判断是否存在软引用  
            if (softBitmap == null) {  
                return null;  
            }  
            // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空  
            Bitmap bitmap = softBitmap.get();  
            return bitmap;  
        }  
View Code

使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。

需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该 Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现 NullPointerException异常导致应用崩溃。

 

到底什么时候使用软引用,什么时候使用弱引用呢?

个人认为,如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。

还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。

另外,和弱引用功能类似的是WeakHashMap。WeakHashMap对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,回收以后,其条目从映射中有效地移除。WeakHashMap使用ReferenceQueue实现的这种机制。

原文地址:https://www.cnblogs.com/yujian-bcq/p/4563428.html