美文网首页
android性能优化之内存优化

android性能优化之内存优化

作者: Peakmain | 来源:发表于2019-01-02 16:18 被阅读0次

android的内存管理机制

内存分配机制

  • 1.每个应用程序都运行在单独的进程中
  • 2.应用程序的进程从Zygote进程fork出来
  • 3.每个应用进程都对应自己唯一的虚拟机实例
  • 4.每个虚拟机都有堆内存阈值即最大值限制
  • 5.即使进程退出了,数据仍然在内存中`

进程优先级

1.前台进程,可见进程,服务进程,后台进程,空进程
2.android会将进程评定为它可能会达到的最高级别

回收效益:android总是会倾向于杀死一个能回收更多内存的进程

java的引用方式

1.强引用

  • 只要某个对象有强引用与之关联,JVM必定不会回收这个对象
  • 即使内存不足,JVM宁愿抛出OOM错误哦也不会回收这种对象

2.软引用

  • 用来描述一些有用但不是必需的对象
  • 对于软引用关联着的对象,只有在内存不足的时候JVM才会回收这个对象
  • 很适合用来实现缓存:比如网页缓存,图片缓存等;

例:SoftReference aSoftRef=new SoftReference (aRef)

3.弱引用

  • 弱应用是用来描述非必需的对象
  • 当jvm进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联着的对象

例:WeaiReference <data> ref=new WeaiReference <data>(new data);

4.虚引用

  • 不影响对象的生命周期
  • 如果一个对象与虚引用关联,则跟没有引用与之关联一样
  • 在任何时候都可能被垃圾回收器回收

例:ReferenceQueue <String> queue=new ReferenceQueue <String>();
PhantomReference<String>pr=new PhantomReference<String>(new String("peakmain"),queue);

内存泄露

指由于错误或疏忽造成程序未能释放已经不再使用的内存

内存抖动

指内存频繁地分配和回收

内存抖动的后果

  • 频繁的gc会导致卡顿
  • 严重的时候还会导致oom

解决方案

  • 尽量避免在循环体内创建对象,应该把对象创建移到循环体外
  • 避免在View的onDraw方法里频繁的创建对象
  • 对于能够复用的对象,可以使用对象池将他们缓存起来

内存溢出

应用申请超过最大内存空间

产生原因

  • 应用存在内存泄露,长时间积累导致OOM
  • 应用某些逻辑操作疯狂的消耗大量内存

解决方案

  • 规避内存泄露
  • 图片进行压缩显示或局部显示

图片优化

首先我们举个例子看下图片占用的内存的大小

  private final String path = "/sdcard/Download/image1.jpg";
    private void showSize() throws IOException {
        File file = new File(path);
        if (!file.exists()) {
            return;
        }
        FileInputStream fis = new FileInputStream(file);
        int size = fis.available();
        Log.e("TAG", "size=" + size);
        Bitmap bitmap= BitmapFactory.decodeStream(fis);
        Log.e("TAG", "bitmapsize=" + bitmap.getByteCount());
    }

我的图片的结果是

image.png
图片占用内存影响因素
图片的长度,图片的宽度,单位像素所占用的字节数

图片占用的内存=图片长度图片宽度单位像素所占用的字节数

关于图片压缩,Google提供中文文档:http://hukai.me/android-training-course-in-chinese/graphics/displaying-bitmaps/load-bitmap.html

 public  Bitmap decodeSampledBitmapFromFile(String path, int reqWidth, int reqHeight) {

        //加载图片信息,不加载图片
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        //BitmapFactory.decodeResource(res, resId, options);
        BitmapFactory.decodeFile(path,options);

        // 计算压缩比
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        Log.e("TAG","inSampleSize:"+ options.inSampleSize );
        // 加载图片到内存
        options.inJustDecodeBounds = false;
        //单位像素所占用的字节数
        options.inPreferredConfig= Bitmap.Config.RGB_565;
        return  BitmapFactory.decodeFile(path,options);
    }

    public  int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
      //如果只是宽或者高比较长,可以只对一个比例
        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

  
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }
压缩前后结果对比.png

图片压缩的主要思路:

  • 1.获取图片的像素宽高
  • 2.计算需要的压缩比例
  • 3.将图片用计算出的比例压缩并再加载到内存中使用

图片的局部显示

 private Bitmap decodeRegionBitmap(int offX) throws IOException {
        //获取图片的宽高信息
        BitmapFactory.Options options=new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeFile(path,options);
        final int height = options.outHeight;
        final int width = options.outWidth;
        //设置图片局部显示的区域
        BitmapRegionDecoder bitmapRegionDecoder=BitmapRegionDecoder.newInstance(path,false);
        BitmapFactory.Options newOptions=new BitmapFactory.Options();
        newOptions.inPreferredConfig= Bitmap.Config.RGB_565;
        //加载局部图片
        Bitmap bitmap=bitmapRegionDecoder
                .decodeRegion(new Rect(0+offX,0,width/2+offX,height),newOptions);
        return bitmap;
    }

效果如下


效果图.gif

图片的内存缓存策略
三级缓存

  • 1.建立一个图片缓存池,用于存放图片对应的bitmap对象
  • 2.在显示的时候,先从缓存池中获取,如果取到了了直接显示,如果获取不到再从sd卡或者网络中获取

缓存池特性:总有大小限制,能够智能化的回收移除池内一部分图片

缓存池的二级缓存

  • 两个缓存池,一个强引用,一个软应用
  • 强应用保存常用的图片,软应用保存其他图片
    二级缓存实现方式
  • LinekdHashMap+软引用
  //二级缓存
    HashMap<String, SoftReference<Bitmap>> mSecondCache = new HashMap<>();
    final int MAX_NUM = 10;
    //一级缓存:强应用缓存
    //最近访问量
    HashMap<String, Bitmap> mFirstCache = new LinkedHashMap<String, Bitmap>(MAX_NUM, 0.75f, true) {
        //移除最老的因素
        @Override
        protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
            // return super.removeEldestEntry(eldest);
            if (size() > MAX_NUM) {
                mSecondCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
                return true;
            }
            return false;
        }
    };
  • Lrucache+软引用

软引用在缓存的处理上是没有效率的,因为效率比较低,所以一般情况下是可以取掉

数据结构优化

String StringBuilder StringBuffer三者的区别

 private void stringJoin() {
        long time1 = System.currentTimeMillis();
        String result = "";
        for (int i = 0; i <4000; i++) {
            result = result + data[i];
        }
        long time2 = System.currentTimeMillis();
        long time = time2 - time1;
        Log.e(TAG, "stringjoinTime:" + time);
    }

    private void stringBufferJoin() {
        long time1 = System.currentTimeMillis();
        StringBuffer result = new StringBuffer();
        for (int i = 0; i < 4000; i++) {
            result.append(data[i]);
        }
        long time2 = System.currentTimeMillis();
        long time = time2 - time1;
        Log.e(TAG, "stringBufferJoinTime:" + time);
    }

    private void stringBuilderJoin() {
        long time1 = System.currentTimeMillis();
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < 4000; i++) {
            result.append(data[i]);
        }
        long time2 = System.currentTimeMillis();
        long time = time2 - time1;
        Log.e(TAG, "stringBuilderJoinTime:" + time);
    }
结果.png
  • String字符串常量
  • StringBuffer字符串变量
  • StringBuilder字符串变量
  • StringBuffer是线程安全的
  • StringBuilder,String是线程非安全
  • 执行效率上:在大部分情况:StringBuilder>StringBuffer>String

HashMap

  • 内部结构是个哈希表的拉链结构
  • HashMap默认实现的扩容是以2倍增加
  • 利用Hash算法实现增删改查,效率高

ArrayMap

  • ArrayMap内部利用两个数组
  • mHashes数组用来保存每一个key和value
  • mArray数组大小为mHashes的2倍,依次保存key和value
  • 利用二分查找实现查询

SparseArray

  • 内部是两个数组来进行数据存储的(一个存放key,一个存放value)
  • key为int值,不是hash值。内存空间占用少
  • 利用二分查找实现查询

代码优化

常见内存泄露场景

  • 单例导致的内存泄露——尽量使用Application的上下文
  • 静态变量导致的内存泄露
  • 非静态内部类导致的内存泄露
  • 未取消注册或回调导致的内存泄露
  • Timer和TimerTask导致的内存泄漏
  • 集合中的对象未清理导致的内存泄露
  • 资源未关闭或释放导致的内存泄露
  • 属性动画没有即使停止导致的内存泄漏
  • webview导致的内存泄漏

其他优化

  • 1.避免在android中使用java的枚举类型
  • 2.尽量减少锁个数减小锁范围
  • 3.尽量使用线程池替代多线程操作
  • 4.尽可能不要使用依赖注入

相关文章

网友评论

      本文标题:android性能优化之内存优化

      本文链接:https://www.haomeiwen.com/subject/baknlqtx.html