使用SurfaceView加载多张大分辨率图片做帧动画,解决OOM问题

2023-11-09

项目需求:动态背景


需求很简单,只是用帧动画做一个动态的背景而已,但若是70多张图片都是1920*1080,一张485k的话,传统意义上的帧动画就很难实现了,往往加载10张就开始OOM。


一般来说,常用的实现动态背景的有效方式有三种:


①视频:果断粗暴,清晰度很有保证,但是在无限轮播重复的时候,总会有一瞬间的卡顿,这真的很让人郁闷。


②GIF动态图:直接用Glide加载就可以实现无限重复的动态背景,而且衔接没有丝毫停顿,但是有个问题,清晰度很难保证,有时候GIF太大,加载也会很缓慢。


③帧动画:清晰度非常高,但是等着OOM吧!


下面我们使用第四种方法,继承SurfaceView,实现加载多张大图片,完成真正意义上的帧动画。

图片资源接口:PictureInterface

package com.giousa.frameanimationtest;

/**
 * Description:
 * Author:Giousa
 */
public interface PictureInterface {

    int[] srcId =
            {R.drawable.a_00000, R.drawable.a_00001, R.drawable.a_00002, R.drawable.a_00003,
                    R.drawable.a_00004, R.drawable.a_00005, R.drawable.a_00006,
                    R.drawable.a_00007, R.drawable.a_00008, R.drawable.a_00009,
                    R.drawable.a_00010, R.drawable.a_00011, R.drawable.a_00012, R.drawable.a_00013,
                    R.drawable.a_00014, R.drawable.a_00015, R.drawable.a_00016,
                    R.drawable.a_00017, R.drawable.a_00018, R.drawable.a_00019
                    , R.drawable.a_00020, R.drawable.a_00021, R.drawable.a_00022, R.drawable.a_00023,
                    R.drawable.a_00024, R.drawable.a_00025, R.drawable.a_00026,
                    R.drawable.a_00027, R.drawable.a_00028, R.drawable.a_00029
                    , R.drawable.a_00030, R.drawable.a_00031, R.drawable.a_00032, R.drawable.a_00033,
                    R.drawable.a_00034, R.drawable.a_00035, R.drawable.a_00036,
                    R.drawable.a_00037, R.drawable.a_00038, R.drawable.a_00039
                    , R.drawable.a_00040, R.drawable.a_00041, R.drawable.a_00042, R.drawable.a_00043,
                    R.drawable.a_00044, R.drawable.a_00045, R.drawable.a_00046,
                    R.drawable.a_00047, R.drawable.a_00048, R.drawable.a_00049
                    , R.drawable.a_00050, R.drawable.a_00051, R.drawable.a_00052, R.drawable.a_00053,
                    R.drawable.a_00054, R.drawable.a_00055, R.drawable.a_00056,
                    R.drawable.a_00057, R.drawable.a_00058, R.drawable.a_00059
                    , R.drawable.a_00060, R.drawable.a_00061, R.drawable.a_00062, R.drawable.a_00063,
                    R.drawable.a_00064, R.drawable.a_00065, R.drawable.a_00066,
                    R.drawable.a_00067, R.drawable.a_00068, R.drawable.a_00069
                    , R.drawable.a_00070, R.drawable.a_00071, R.drawable.a_00072, R.drawable.a_00073,
                    R.drawable.a_00074, R.drawable.a_00075

            };
}

继承SurfaceView的类FrameAnimation:

package com.giousa.frameanimationtest;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.util.ArrayList;

/**
 * Description:
 * Author:Giousa
 * Date:2016/11/4
 * Email:65489469@qq.com
 */
public class FrameAnimation extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    private SurfaceHolder mSurfaceHolder;

    private boolean mIsThreadRunning = true; // 线程运行开关
    public static boolean mIsDestroy = false;// 是否已经销毁

    private int[] mBitmapResourceIds;// 用于播放动画的图片资源id数组
    private ArrayList<String> mBitmapResourcePaths;// 用于播放动画的图片资源path数组
    private int totalCount;//资源总数
    private Canvas mCanvas;
    private Bitmap mBitmap;// 显示的图片

    private int mCurrentIndext;// 当前动画播放的位置
    private int mGapTime = 150;// 每帧动画持续存在的时间
    private boolean mIsRepeat = false;

    private OnFrameFinishedListener mOnFrameFinishedListener;// 动画监听事件

    public FrameAnimation(Context context) {
        this(context, null);
        initView();
    }

    public FrameAnimation(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    public FrameAnimation(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        initView();

    }

    private void initView() {

        mSurfaceHolder = this.getHolder();
        mSurfaceHolder.addCallback(this);

        // 白色背景
        setZOrderOnTop(true);
        setZOrderMediaOverlay(true);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 当surfaceView销毁时, 停止线程的运行. 避免surfaceView销毁了线程还在运行而报错.
//        mIsThreadRunning = false;
//        try {
//            Thread.sleep(mGapTime);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//
//        mIsDestroy = true;
    }

    /**
     * 制图方法
     */
    private void drawView() {
        // 无资源文件退出
        if (mBitmapResourceIds == null && mBitmapResourcePaths == null) {
            Log.e("frameview", "the bitmapsrcIDs is null");

            mIsThreadRunning = false;

            return;
        }

        // 锁定画布
        if(mSurfaceHolder != null){
            mCanvas = mSurfaceHolder.lockCanvas();
        }
        try {
            if (mSurfaceHolder != null && mCanvas != null) {

                mCanvas.drawColor(Color.WHITE);

                if (mBitmapResourceIds != null && mBitmapResourceIds.length > 0)
                    mBitmap = BitmapFactory.decodeResource(getResources(), mBitmapResourceIds[mCurrentIndext]);
                else if (mBitmapResourcePaths != null && mBitmapResourcePaths.size() > 0) {
                    mBitmap = BitmapFactory.decodeFile(mBitmapResourcePaths.get(mCurrentIndext));

                }

                Paint paint = new Paint();
                paint.setAntiAlias(true);
                paint.setStyle(Paint.Style.STROKE);
                Rect mSrcRect, mDestRect;
                mSrcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
                mDestRect = new Rect(0, 0, getWidth(), getHeight());
                mCanvas.drawBitmap(mBitmap, mSrcRect, mDestRect, paint);

                // 播放到最后一张图片
                if (mCurrentIndext == totalCount - 1) {
                    //TODO 设置重复播放
                    //播放到最后一张,当前index置零
                    mCurrentIndext = 0;
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            mCurrentIndext++;

            if(mCurrentIndext >= totalCount){
                mCurrentIndext = 0;
            }
            if (mCanvas != null) {
                // 将画布解锁并显示在屏幕上
                if(mSurfaceHolder!=null){
                    mSurfaceHolder.unlockCanvasAndPost(mCanvas);
                }
            }

            if (mBitmap != null) {
                // 收回图片
                mBitmap.recycle();
            }
        }
    }

    @Override
    public void run() {
        if (mOnFrameFinishedListener != null) {
            mOnFrameFinishedListener.onStart();
        }

        // 每隔150ms刷新屏幕
        while (mIsThreadRunning) {
            drawView();
            try {
                Thread.sleep(mGapTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (mOnFrameFinishedListener != null) {
            mOnFrameFinishedListener.onStop();
        }
    }

    /**
     * 开始动画
     */
    public void start() {
        if (!mIsDestroy) {
            mCurrentIndext = 0;
            mIsThreadRunning = true;
            new Thread(this).start();
        } else {
            // 如果SurfaceHolder已经销毁抛出该异常
            try {
                throw new Exception("IllegalArgumentException:Are you sure the SurfaceHolder is not destroyed");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 设置动画播放素材的id
     *
     * @param bitmapResourceIds 图片资源id
     */
    public void setBitmapResoursID(int[] bitmapResourceIds) {
        this.mBitmapResourceIds = bitmapResourceIds;
        totalCount = bitmapResourceIds.length;
    }

    /**
     * 设置动画播放素材的路径
     *
     * @param bitmapResourcePaths
     */
    public void setmBitmapResourcePath(ArrayList bitmapResourcePaths) {
        this.mBitmapResourcePaths = bitmapResourcePaths;
        totalCount = bitmapResourcePaths.size();
    }

    /**
     * 设置每帧时间
     */
    public void setGapTime(int gapTime) {
        this.mGapTime = gapTime;
    }

    /**
     * 结束动画
     */
    public void stop() {
        mIsThreadRunning = false;
    }

    /**
     * 继续动画
     */
    public void reStart() {
        mIsThreadRunning = false;
    }

    /**
     * 设置动画监听器
     */
    public void setOnFrameFinisedListener(OnFrameFinishedListener onFrameFinishedListener) {
        this.mOnFrameFinishedListener = onFrameFinishedListener;
    }

    /**
     * 动画监听器
     *
     * @author qike
     */
    public interface OnFrameFinishedListener {

        /**
         * 动画开始
         */
        void onStart();

        /**
         * 动画结束
         */
        void onStop();
    }

    /**
     * 当用户点击返回按钮时,停止线程,反转内存溢出
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // 当按返回键时,将线程停止,避免surfaceView销毁了,而线程还在运行而报错
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            mIsThreadRunning = false;
        }

        return super.onKeyDown(keyCode, event);
    }


}

主界面:MainActivity

package com.giousa.frameanimationtest;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements PictureInterface{

    private final String TAG = MainActivity.class.getSimpleName();
    private FrameAnimation mFrameAnimation;
    private Button mSecond;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }



    private void initView() {
        mFrameAnimation = (FrameAnimation) findViewById(R.id.frame_animation);
        mSecond = (Button) findViewById(R.id.btn_second);
        mSecond.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);
            }
        });
    }

    private void initData() {
        initAnimation();
    }

    private void initAnimation() {
        //设置资源文件
        mFrameAnimation.setBitmapResoursID(srcId);
        //设置监听事件
        mFrameAnimation.setOnFrameFinisedListener(new FrameAnimation.OnFrameFinishedListener() {
            @Override
            public void onStop() {
                Log.e(TAG, "stop");

            }

            @Override
            public void onStart() {
                Log.e(TAG, "start");
                Log.e(TAG, Runtime.getRuntime().totalMemory() / 1024 + "k");
            }
        });

        //设置单张图片展示时长
        mFrameAnimation.setGapTime(150);
        mFrameAnimation.start();
    }
}

主布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.giousa.frameanimationtest.MainActivity">

    <com.giousa.frameanimationtest.FrameAnimation
        android:id="@+id/frame_animation"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <Button
        android:gravity="center"
        android:textSize="25sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转到第二界面"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginBottom="88dp"
        android:id="@+id/btn_second"/>
</RelativeLayout>


~~~~~~~~~Demo资源下载~~~~~~~~~



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用SurfaceView加载多张大分辨率图片做帧动画,解决OOM问题 的相关文章

  • HRNet-Semantic-Segmentation图像,视频推理

    源码 https github com HRNet HRNet Semantic Segmentation 我用的是pytorchv1 1分支 这么好的项目居然没有inference代码 于是自己整理了一个简单的demo jit和onnx
  • ISO/IEC 27001:2022 发布(中文),信息安全、网络安全和隐私保护 信息安全管理系统 要求

    ISO IEC 27001 2022 发布 中文 信息安全 网络安全和隐私保护 信息安全管理系统 要求 ISOIEC27001 2022中文信息安全 网络安全和隐私保护 数据集文档类资源 CSDN下载ISOIEC27001 2022中文信息
  • 2021年我最喜欢的Java Web报表工具-实现批量+套打入园通知书

    最近龙哥的园子开张了 不少同学加好友要求入园 为满足同学们的需求 特开此篇 用锐浪JAVA WEB报表实现批量 套打 生成打印入园通知书 锐浪JAVA WEB报表官网 我们先看一下今天要实现的效果 龙哥的 入园通知书 注 自已瞎编的通知书
  • LeetCode第529题:扫雷游戏

    让我们一起来玩扫雷游戏 给你一个大小为 m x n 二维字符矩阵 board 表示扫雷游戏的盘面 其中 M 代表一个 未挖出的 地雷 E 代表一个 未挖出的 空方块 B 代表没有相邻 上 下 左 右 和所有4个对角线 地雷的 已挖出的 空白
  • 驱动的调用

    目录 设备文件 编辑 测试驱动 读写回环测试 步骤 源文件 详细的讲解看注释即可 应用和驱动之间的数据交换 在应用层调用open来打开这个系统文件 在向这个设备文件使用read write等即可调用驱动的函数去工作 设备文件 设备文件连接着
  • Sparkstreaming读取kafka数据写入hive和es

    一 主要流程 此demo用到的软件如下 软件需先自行安装 springboot 1 5 9 RELEASE hadoop 2 7 2 spark 2 1 1 elasticsearch 5 2 2 kafka 0 10 2 1 hive s
  • Cron表达式解读

    背景说明 解读0 0 10 与 0 10 的区别 各个字符代表的含义 0代表从0分开始 代表任意字符 代表递增 1 0 0 10 代表从0分钟开始 每10分钟执行任务一次 启动时间 xx 20 05 第一次执行时间 xx 20 10 第二次
  • 项目心得(三)

    赛车游戏项目心得 介绍 确定项目 分工 进度规划 资源结构 游戏数据结构 遇到的问题 项目展示 开始场景 主菜单场景 成就查询场景 诗词系统场景 无尽塔场景 车库场景 阶位场景 视频演示 项目文件 总结 介绍 我带领的第二次小组团队项目 大
  • 1.1 密码学哈希函数

    我们需要理解的第一个密码学的基础知识是密码学哈希函数 哈希函数是一个数学函数 具有以下三个特性 其输入可为任意大小的字符串 它产生固定大小的输出 为使本章讨论更具体 我们假设输出值大小为256位 但是 我们的讨论适用于任意规模的输出 只要其
  • 操作系统虚拟机linux系统内建立的文件为只读,如何可读可写?

    1 先建立文件 按照自己的需求建立文件 2 打开主目录 发现a c文件为只读形式 3 在终端输入chmod 666 a c 4 返回主目录 文件为可读可写形式 5 打开a c文件就可以编辑代码并保存啦
  • llS6 and llS7解析漏洞

    1 什么是llS Internet Information Services 互联网信息服务 是微软公司由微软公司提供的基于运行Microsoft windows的互联网基本服务 web服务 因为IIS是在windows操作系统平台下开发的
  • 2021-10-25尤破金10.25黄金今日行情价格走势分析及黄金原油最新策略解套

    黄金行情走势分析 周一 10月25日 国际金价徘徊在1800美元重要心理关口附近 美元指数反弹限制了金价升势 在美联储主席鲍威尔表示通胀可能持续到明年后 投资人思索美联储可能会如何应对通胀压力 在鲍威尔上周五 10月22日 表示美联储应开始
  • Pytorch分布式训练(一)

    参考文献 Writing Distributed Applications with PyTorch PyTorch Tutorials 2 0 1 cu117 documentation 33 完整讲解PyTorch多GPU分布式训练代码
  • 简单报价单模板_科普:小程序定制和模板开发有什么区别?

    小程序常见的开发方式有三种 自己源代码开发 找外包团队定制开发 使用小程序模板类开发工具 对于不懂技术的小白来说 源代码开发困难太大 那么后两种方式该如何选择呢 它们到底都有什么区别 接下来就跟大家科普一下这些知识 1 成本不同 小程序模板
  • ClickHouse(四)表引擎

    官网 表引擎 ClickHouse文档 表引擎在 ClickHouse 中的作用十分关键 直接决定了数据如何存储和读取 是否支持并发读写 是否支持 index 支持的 query 种类 是否支持主备复制等 1 表引擎概述 ClickHous
  • unity3D之动态的创建球体游戏对象js

    function OnGUI if GUILayout Button 创建立方体 GUILayout Height 50 var objCube GameObject CreatePrimitive PrimitiveType Sphere

随机推荐

  • JS 插入排序

    算法描述 插入排序的算法描述是一种简单直观的排序算法 它的工作原理是通过构建有序序列 对于未排序数据 在已排序序列中从后向前扫描 找到相应位置并插入 一般来说 插入排序都采用in place在数组上实现 具体算法描述如下 从第一个元素开始
  • 【OpenCV4】拉普拉斯算子提取边缘 cv::Laplacian() 用法详解和代码示例(c++)

    作用原理 拉普拉斯算子可以用于边缘检测 同时该算子不具有方向性 可以同时检测到 X 方向和 Y 方向的边缘 综合后检测出图像的边缘 即拉普拉斯算子是 各向同性 的 这在很多情况下是一个优点 因为我们一般来说会同时关注 X 方向和 Y 方向的
  • xcode4的环境变量,Build Settings参数,workspace及联编设置

    一 xcode4中的环境变量 BUILT PRODUCTS DIR build成功后的 最终产品路径 可以在Build Settings参数的Per configuration Build Products Path项里设置 TARGET
  • 英语台词--冰与火之歌

    Your roof your rules 你的地盘 你做主
  • C++基础——常引用与类型转换详解

    通过前两篇对引用的讲解 想必大家都对引用有了较为深刻的理解 没看过的小伙伴可以去看看 C 基础 引用讲解1 C 基础 引用讲解2 目录 一 常引用 1 权限的平移 2 权限的放大 3 权限的缩小 二 引用的类型转换 一 常引用 这次我来说一
  • 史上最全的Python兼职接单挣钱教程,十分详细(附基础教程)

    学python编程能挣钱吗 怎么挣钱 答案是可以的 有两点我都赚到钱的方法 接私活和自媒体 一 赚钱第一种方式 接私活 刚学会python那会 就有认识的朋友介绍做一个网站的私活 当时接单赚了4K 仅代表本人个人收益 后又自己接过开发网站后
  • A2W和W2A 很好的多字节和宽字节字符串的转换宏

    分享一下我老师大神的人工智能教程 零基础 通俗易懂 http blog csdn net jiangjunshow 也欢迎大家转载本篇文章 分享知识 造福人民 实现我们中华民族伟大复兴 作者 朱金灿 来源 http blog csdn ne
  • github CPlusPlusThings 基础学习笔记

    来源 Light City CPlusPlusThings C 那些事 github com 目录 const static this inline sizeof 函数指针 纯虚函数和抽象类 vptr vtable virtual vola
  • Dell R410服务器查看系统raid级别

    注意 raid 如果有问题 会导致机器网络自动断开连接 连不上机器 1 查看inux 怎么查看raid做的 是几 软件raid 只能通过Linux系统本身来查看 cat proc mdstat 可以看到raid级别 状态等信息 硬件raid
  • 前端JSON数据传值到后端接收方式

    前端发送的数据 前端JS请求 1 demo为JSON格式数据 2 let para 3 dataJ JSON stringfiy demo 4 5 这一段是Vue封装的方法 本质就是一条url 6 this http post webRoo
  • js去除url中的localtion.search部分

    localtion search是url中拼接的参数部分 js去除url中的localtion search部分包括 部分 if location search var old url window location href var ne
  • Java算法结构---------线性表

    线性表相关介绍 线性表是一种最常用 最简单的线性结构 线性表的主要操作特定是 可以在任意位置上插入一个数据元素和删除一个数据元素 线性表可以用顺序存储结构和链式存储结构实现 用顺序存储结构实现的线性表称为顺序表 用链式存储结构实现的线性表称
  • node.js(第七章)登录鉴权的方式一Cookie&Session

    1 Cookie Session HTTP 无状态 我们知道 HTTP 是无状态的 也就是说 HTTP 请求方和响应方间无法维护状态 都是一次性的 它不知道前后的请求都发生了什么 但有的场景下 我们需要维护状态 最典型的 一 个用户登陆CS
  • Linux基础笔记15

    文本处理 wc 用于统计文件的字节数 单词数 行数等信息 并将统计结果标准输出到终端 w 统计单词数 c 统计字节数 l 统计行数 m 统计字符数 L 显示最长行的长度 help 显示帮助信息 version 显示版本信息 root iZr
  • PHP静态绑定知识点学习记录

    最近在学习JAVA基础中关于静态方法的知识 回想起PHP可以使用self 或者static 两种方式来进行静态方法的调用 有些忘记两者的差异 因此 做一下学习记录 后期绑定 的意思是说 static 不再被解析为定义当前方法所在的类 而是在
  • Linux 学习笔记1 安装linux详细教程

    系统 CentOS 8 1 1911 x86 64 dvd1 软件 VMware Workstation Pro 16 安装centos VM安装的步骤 1 去BIOS里修改设置开启以常交 f2 f10 2 安装虚拟机软件 vm15 5 演
  • QT(qCompress和qUncompress)与zlib(compress和uncompress)相互调用

    因为QT也是用zlib库的 所以理论上数据是可以直接互通的 但现实是残酷的 通过对qCompress和compress压缩的数据进行打印 可以知道qCompress比compress的数据长四个字节 而这四个字节的内容则未压缩前的数据长度
  • 解决图像目标检测两框重叠问题

    文章目录 1 问题现象 2 解决办法 3 Non Maximum Suppression 原理 3 1 什么是非极大值抑制 3 2 为什么要用非极大值抑制 3 3 如何使用非极大值抑制 3 4 效果 4 参考资料 1 问题现象 使用yolo
  • SpringBoot注解详解:从核心到Web,从数据到测试,一网打尽

    总结的了平时学习springboot常用的一些注解 方便以后开发时可以阅览回忆 springboot的常用注解可以分为以下几类 核心注解 这些注解是springboot的基础 用于启动 配置和管理springboot应用 Web MVC注解
  • 使用SurfaceView加载多张大分辨率图片做帧动画,解决OOM问题

    项目需求 动态背景 需求很简单 只是用帧动画做一个动态的背景而已 但若是70多张图片都是1920 1080 一张485k的话 传统意义上的帧动画就很难实现了 往往加载10张就开始OOM 一般来说 常用的实现动态背景的有效方式有三种 视频 果