zoukankan      html  css  js  c++  java
  • 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实现的这种机制。

  • 相关阅读:
    查看git submodule更改
    /var/lib/docker空间占用过大迁移
    docker -修改容器
    docker重命名镜像repository和tag
    方法的重写、重载。
    方法的声明与使用。
    二维数组。
    标准输入输出流概述和输出语句。
    冒泡排序法。
    IO流,对象操作流优化。
  • 原文地址:https://www.cnblogs.com/yujian-bcq/p/4563428.html
Copyright © 2011-2022 走看看