当前位置:   article > 正文

Lottie 框架Native Heap内存爆炸问题解决_android lottie内存占用太高

android lottie内存占用太高

Lottie框架Native Heap内存爆炸问题解决

Lottie 是在 Android 和 iOS上 原生渲染 的After Effects(AE)动画,Lottie是 Airbnb 开源 的支持Android 和 iOS 的动画库,它可以解析 AE 动画中用Bodymovin 导出的json文件,并在移动设备上利用原生库进行渲染 !项目地址:https://github.com/airbnb/lottie-android

遇到的问题

Lottie框架帮我们解决很多过场动画和图标点击动画这些应用场景,可能是因为太好用了,最近一个版本的UI大量使用到了lottie动画,这个时候问题就出现了,使用Profiler进行内存检测的时候,发现了Native Heap 内存可以飙升到1GB,Android 8.0之后Bitmap 像素占用的内存分配到了 Native Heap中(目前主流用户大部分都在8.0以上的版本了),基本上就可以确定就是lottie框架加载动效太多造成的,那么我们就来清除Bitmap的缓存吧

源码解析

根据阅读源码可以
LottieAnimationView 是主要进行属性设置以及状态管理的,我们来看invalidateDrawable方法

@Override
public void invalidateDrawable(@NonNull Drawable dr) {
    if (getDrawable() == lottieDrawable) {
        // We always want to invalidate the root drawable so it redraws the whole drawable.
        // Eventually it would be great to be able to invalidate just the changed region.
        // 绘制lottie动画
        super.invalidateDrawable(lottieDrawable);
    } else {
        // Otherwise work as regular ImageView
        super.invalidateDrawable(dr);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

由于后续的源码调用的链路是这样子的LottieDrawbale.draw(canvas)–》CompositionLayer.draw - 》 BaseLayer.draw -》 ImageLayer.draw ,我们来看看ImageLayer的源码

  @Override public void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) {
    Bitmap bitmap = getBitmap();
    if (bitmap == null || bitmap.isRecycled()) {
      return;
    }
    float density = Utils.dpScale();

    paint.setAlpha(parentAlpha);
    if (colorFilterAnimation != null) {
      paint.setColorFilter(colorFilterAnimation.getValue());
    }
    canvas.save();
    canvas.concat(parentMatrix);
    src.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
    dst.set(0, 0, (int) (bitmap.getWidth() * density), (int) (bitmap.getHeight() * density));
    canvas.drawBitmap(bitmap, src, dst , paint);
    canvas.restore();
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

那么getBitmap()方法是调用的ImageAssetManager.bitmapForId

    @Nullable
    public Bitmap bitmapForId(String id) {
        LottieImageAsset asset = imageAssets.get(id);
        if (asset == null) {
            return null;
        }
        Bitmap bitmap = asset.getBitmap();
        // 检测bitmap是否已经回收
        if (bitmap != null && !bitmap.isRecycled()) {
            return bitmap;
        }
        if (delegate != null) {
            bitmap = delegate.fetchBitmap(asset);
            if (bitmap != null) {
                putBitmap(id, bitmap);
            }
            return bitmap;
        }

        String filename = asset.getFileName();
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inScaled = true;
        opts.inDensity = 160;

        if (filename.startsWith("data:") && filename.indexOf("base64,") > 0) {
            // Contents look like a base64 data URI, with the format data:image/png;base64,<data>.
            byte[] data;
            try {
                data = Base64.decode(filename.substring(filename.indexOf(',') + 1), Base64.DEFAULT);
            } catch (IllegalArgumentException e) {
                Logger.warning("data URL did not have correct base64 format.", e);
                return null;
            }
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opts);
            return putBitmap(id, bitmap);
        }

        InputStream is;
        try {
            if (TextUtils.isEmpty(imagesFolder)) {
                throw new IllegalStateException("You must set an images folder before loading an image." +
                        " Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder");
            }
            is = context.getAssets().open(imagesFolder + filename);
        } catch (IOException e) {
            Logger.warning("Unable to open asset.", e);
            return null;
        }
        bitmap = BitmapFactory.decodeStream(is, null, opts);
        bitmap = Utils.resizeBitmapIfNeeded(bitmap, asset.getWidth(), asset.getHeight());
        return putBitmap(id, bitmap);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

解决思路

  1. ImageLayer drawLayer 方法中最后一行 添加bitmap.recycle()每绘制一张图片 就回收一张
  2. ImageAssetManager 增加clearCache方法 在动画加载完毕之后将bitmap集合进行全部回
  3. ImageAssetManager bitmapForId 修改为bitmap不会null并且没有回收的情况下 才直接使用,否则就要重新进行加载资源
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/179393?site
推荐阅读
相关标签
  

闽ICP备14008679号