前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >在Android中显示APNG动图

在Android中显示APNG动图

原创
作者头像
Clayman Twinkle
发布于 2019-04-04 10:16:46
发布于 2019-04-04 10:16:46
17.3K0
举报
文章被收录于专栏:Android原创Android原创

一、什么是APNG?

APNG(Animated Portable Network Graphics)是一个基于PNG(Portable Network Graphics)的位图动画格式,用途类似GIF,其诞生的目的是为了替代老旧的 GIF 格式。

二、与GIF对比

说了这么多,它替代GIF?那有什么优势呢?

总结下来有以下几点:

(1)GIF最多支持 8 位 256 色,而APNG支持24 位真彩色和alpha通道,不会出现像GIF的锯齿;

(2)APNG图通过优化,图片大小和GIF差不多,甚至小一点。

三、在Android中显示APNG动图

这里使用了一个开源库来解析加载APNG图,apng-view

使用示例:

代码语言:txt
AI代码解释
复制
String url = "http://xxx.png";
imageView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ApngDrawable apngDrawable = ApngDrawable.getFromView(v);
        if (apngDrawable == null) return;
        if (apngDrawable.isRunning()) {
            apngDrawable.stop();  // 停止播放动画
        } else {
            apngDrawable.setNumPlays(3); // 动画循环次数
            apngDrawable.start(); // 开始播放动画
        }
    }
});

ApngImageLoader.getInstance().displayImage(url, imageView);

效果图:

四、apng-view源码分析

实现过程

先看看apng-view实现过程:

实现过程
实现过程

(1)图片的下载/加载:通过图片加载开源库Android-Universal-Image-Loader进行图片的下载/加载;

(2)通过下载成功后的图片文件构造ApngDrawable对象;

(3)最后通过imageView.setImageDrawableApngDrawableImageView绑定到一起;

所以,这个apng-view库中,最核心的就是ApngDrawable这个类了。

那么这个ApngDrawable里面究竟做了什么骚操作呢?

源码解读

(1)prepare

先从图片文件读取这里说起,图片读取是在ApngDrawable这个prepare()方法中进行的;

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/ApngDrawable.java
private void prepare() {
 	// 1. 构造File对象
	String imagePath = getImagePathFromUri();
	if (imagePath == null) return;
	baseFile = new File(imagePath);
	if (!baseFile.exists()) return;
	// 2. 读取一个APNG文件并尝试将其拆分帧
	ApngExtractFrames.process(baseFile);
	// 3. 读取APNG文件信息
	readApngInformation(baseFile);
	isPrepared = true;
}

代码的步骤,说得云里雾里的,且看这个ApngExtractFrames.process()方法里具体实现吧;

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/assist/ApngExtractFrames.java
public static int process(final File orig) {
	PngReaderBuffered pngr = new PngReaderBuffered(orig); // 这里应该是在读取了这个图片
	pngr.end();
	return pngr.frameIndex + 1;
}

这里用到了一个可以用来读取PNG的开源库pngj,大概知道这是在读图片了,读的过程中做了什么操作呢?

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/assist/ApngExtractFrames.java
protected void postProcessChunk(ChunkReader chunkR) {
	//......
    try {
        String id = chunkR.getChunkRaw().id;
        PngChunk lastChunk = chunksList.getChunks().get(chunksList.getChunks().size() - 1);
        if (id.equals(PngChunkFCTL.ID)) { // FCTL代表,每一帧开头
            frameIndex++;
            frameInfo = ((PngChunkFCTL) lastChunk).getEquivImageInfo();
            startNewFile(); // 开始新建一个文件,进行输入
        }
        if (id.equals(PngChunkFDAT.ID) || id.equals(PngChunkIDAT.ID)) { // 图像数据块
            // 忽略这里的处理细节....
        }
        if (id.equals(PngChunkIEND.ID)) { // 这一帧结束
            if (fo != null)
                endFile(); // 结束这个文件输入,对应startNewFile方法
        }
    } catch (Exception e) {
        throw new PngjException(e);
    }
}

大概逻辑就是将APNG图片读取后,拆解生成多个帧文件,存放起来;

接下来看下ApngDrawable#prepare()中步骤三readApngInformation具体做了什么吧;

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/ApngDrawable.java
private void readApngInformation(File baseFile) {
	PngReaderApng reader = new PngReaderApng(baseFile); // 又读取了一次文件,这里和步骤二或许可以合并优化下
	reader.end();

	List<PngChunk> pngChunks = reader.getChunksList().getChunks(); // 拿到图片所有数据块
	PngChunk chunk;

	for (int i = 0; i < pngChunks.size(); i++) {
		chunk = pngChunks.get(i);
		if (chunk instanceof PngChunkACTL) {
			numFrames = ((PngChunkACTL) chunk).getNumFrames();  //获取总帧数
			if (numPlays > 0) {
				//......
			} else {
				numPlays = ((PngChunkACTL) chunk).getNumPlays(); // 获取循环播次数
			}
		} else if (chunk instanceof PngChunkFCTL) {
			fctlArrayList.add((PngChunkFCTL) chunk); // 收集帧动画控制的数据块
		}
	}
}

这个过程大体上就是在解析这个APNG文件的基本信息。

(2)start

那么到了这个动图的start阶段了

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/ApngDrawable.java
	public void start() {
		if (!isRunning()) {
			isRunning = true;
			currentFrame = 0;
			if (!isPrepared) {
				prepare();
			}
            if (isPrepared) {
                run();
				if (apngListener != null) apngListener.onAnimationStart(this);
            } else {
                stop();
            }
		}
	}

这个start方法里其实也没做什么,只是通过标志位去判断执行preparerunstop方法而已;

(3)run

动图播放的核心方法之一run

代码语言:txt
AI代码解释
复制
public void run() {
	if (showLastFrameOnStop && numPlays > 0 && currentLoop >= numPlays) {
		stop(); // 轮播次数用完且到最后一帧了就停止播放了
		return;
	}

	if (currentFrame < 0) {
		currentFrame = 0;
	} else if (currentFrame > fctlArrayList.size() - 1) {
		currentFrame = 0; // 因为没轮播完,所以当前帧序号从0开始
	}

	PngChunkFCTL pngChunk = fctlArrayList.get(currentFrame);

	int delayNum = pngChunk.getDelayNum();
	int delayDen = pngChunk.getDelayDen();
	int delay = Math.round(delayNum * DELAY_FACTOR / delayDen);

	scheduleSelf(this, SystemClock.uptimeMillis() + delay); // 定时器,循环走run
	invalidateSelf(); // 通知draw再一次了
}

(4)stop

暂停动图的方法

代码语言:txt
AI代码解释
复制
public void stop() {
	if (isRunning()) {
		currentLoop = 0;
		unscheduleSelf(this); // 停止定时器
		isRunning = false;
		if (apngListener != null) apngListener.onAnimationEnd(this);
	}
}

(5)draw

动图播放的核心方法之二draw

APNG图是怎么给绘制出来的呢?

代码语言:txt
AI代码解释
复制
public void draw(Canvas canvas) {
	if (currentFrame <= 0) { 
		drawBaseBitmap(canvas);
	} else {
		drawAnimateBitmap(canvas, currentFrame);
	}

	if (!showLastFrameOnStop && numPlays > 0 && currentLoop >= numPlays) {
		stop(); // 不轮播了就停止
	}

	if (numPlays > 0 && currentFrame == numFrames - 1) { // 最后一帧了
		currentLoop++; // 循环次数加一
		if (apngListener != null) apngListener.onAnimationRepeat(this);
	}
	currentFrame++;
}

绘制动图的核心代码在drawAnimateBitmap方法里:

代码语言:txt
AI代码解释
复制
private void drawAnimateBitmap(Canvas canvas, int frameIndex) {
	Bitmap bitmap = getCacheBitmap(frameIndex); // 这里对帧bitmap做了缓存
		if (bitmap == null) {
			bitmap = createAnimateBitmap(frameIndex); // 没缓存直接通过帧文件创建bitmap
			cacheBitmap(frameIndex, bitmap); // 缓存!
		}
		if (bitmap == null) return;
		RectF dst = new RectF(0, 0,mScaling * bitmap.getWidth(),mScaling * bitmap.getHeight());
		canvas.drawBitmap(bitmap, null, dst, paint); // 绘制
}

至此,核心代码逻辑大致分析差不多。

总结下来ApngDrawable核心逻辑大致分三步:

(1)APNG拆分成多个帧文件:图片文件通过开源库pngjPngChunk数据结构读到内存,然后遍历数据块,将APNG每一帧数据保存到本地文件中;

(2)读取APNG基本图片信息;

(3)开启定时器逐帧读取文件(读完后缓存一次)生成Bitmap绘制到View上;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
一文读懂 WebSocket 通信过程与实现
WebSocket 是一种标准协议,用于在客户端和服务端之间进行双向数据传输。但它跟 HTTP 没什么关系,它是一种基于 TCP 的一种独立实现。
前端教程
2018/07/27
6660
一文读懂 WebSocket 通信过程与实现
Python 实现 WebSocket 通信
WebSocket 协议主要用于解决Web前端与后台数据交互问题,在WebSocket技术没有被定义之前,前台与后端通信需要使用轮询的方式实现,WebSocket则是通过握手机制让客户端与服务端建立全双工通信,从而实现了更多复杂的业务需求。
王瑞MVP
2022/12/28
1.9K0
php实现websocket实时消息推送
软件通信有七层结构,下三层结构偏向与数据通信,上三层更偏向于数据处理,中间的传输层则是连接上三层与下三层之间的桥梁,每一层都做不同的工作,上层协议依赖与下层协议。基于这个通信结构的概念。
OwenZhang
2021/12/08
2.2K0
php实现websocket实时消息推送
再聊一道面试题:Websocket
事情是这样shai儿的,早在很多年前老李曾经到一家公司去面试,面试官和老李之间产生了这样一段对话:
老李秀
2020/04/26
4.8K0
再聊一道面试题:Websocket
WebSocket从入门到精通,半小时就够!
本文原题“WebSocket:5分钟从入门到精通”,作者“程序猿小卡_casper”,原文链接见文末参考资料部分。本次收录时有改动。
JackJiang
2020/10/14
1.8K0
WebSocket从入门到精通,半小时就够!
WebSocket:5分钟从入门到精通
作者:程序猿小卡 https://segmentfault.com/a/1190000012709475 一、内容概览 WebSocket的出现,使得浏览器具备了实时双向通信的能力。本文由浅入深,介绍了WebSocket如何建立连接、交换数据的细节,以及数据帧的格式。此外,还简要介绍了针对WebSocket的安全攻击,以及协议是如何抵御类似攻击的。 二、什么是WebSocket HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。
企鹅号小编
2018/02/07
1.1K0
PHP实现WebSocket实例详解
WebSocket 协议是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
超级小可爱
2023/02/20
1K0
3分钟使用 WebSocket 搭建属于自己的聊天室(WebSocket 原理、应用解析)
👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.
Lorin 洛林
2023/11/22
3.4K1
3分钟使用 WebSocket 搭建属于自己的聊天室(WebSocket 原理、应用解析)
WebSocket相关
原文:http://www.cnblogs.com/jinjiangongzuoshi/p/5062092.html 前言 今天看了一些资料,记录一下心得。 websocket是html5引入的一个新特性,传统的web应用是通过http协议来提供支持,如果要实时同步传输数据,需要轮询,效率低下 websocket是类似socket通信,web端连接服务器后,握手成功,一直保持连接,可以理解为长连接,这时服务器就可以主动给客户端发送数据,实现数据的自动更新。 使用websocket需要注意浏览器和当前的
新人小试
2018/07/05
5410
WebSocket攻防对抗一篇通
在一次做项目的时候本来是想去点击Burpsuite的Proxy界面的HTTP History选项卡来查看HTTP历史请求记录信息并做测试的,但是在查看的时候却下意识的点击到了HTTP Proxy右侧的"WebSockets History"选项卡中,从界面的交互历史中发现网站有使用WebSocket进行通信,虽然之前有对Websocket有一些简单的了解(比如:跨越问题),但是未对此进行深入研究,这让我产生了需要深入研究一下的想法
Al1ex
2024/06/21
3870
WebSocket攻防对抗一篇通
【WebSocket】505- WebSocket 入门到精通
WebSocket的出现,使得浏览器具备了实时双向通信的能力。本文由浅入深,介绍了WebSocket如何建立连接、交换数据的细节,以及数据帧的格式。此外,还简要介绍了针对WebSocket的安全攻击,以及协议是如何抵御类似攻击的。
pingan8787
2020/03/02
1.9K0
什么是WebSocket协议?
WebSocket的出现,使得浏览器具备了实时双向通信的能力。本文由浅入深,介绍了WebSocket如何建立连接、交换数据的细节,以及数据帧的格式。此外,还简要介绍了针对WebSocket的安全攻击,以及协议是如何抵御类似攻击的。
技术从心
2019/08/06
1.5K0
什么是WebSocket协议?
php基于websocket的那些事儿
本文实例讲述了php基于websocket搭建简易聊天室实践。分享给大家供大家参考。具体如下:
php007
2019/08/05
8770
php基于websocket的那些事儿
websocket
短轮询(Polling)的实现思路就是 浏览器端 每隔几秒钟向 服务器端 发送http请求,服务端在收到请求后,不论是否有数据更新,都直接进行响应。 在服务端响应完成,就会关闭这个Tcp连接 ,如下图所示:
用户10106350
2022/10/28
2.7K0
掌握Linux网络设计中的WebSocket服务器
简介: 本文探索了在Linux环境下实现WebSocket服务器的网络设计,将WebSocket服务器作为连接世界的纽带,为读者介绍了如何掌握Linux网络设计中的关键技术。文章从实现WebSocket协议到优化服务器性能和稳定性等方面进行了深入讲解。通过学习本文,读者将能够全面了解WebSocket服务器的原理和工作机制,并获得构建高效、可靠的Linux WebSocket服务器的实用技巧和最佳实践。无论是初学者还是有经验的开发人员,都能从本文中获得宝贵的知识和启发,进一步提升在Linux网络设计中的能力。让我们一同打造连接世界的纽带,掌握Linux网络设计中WebSocket服务器的精髓。
Lion Long
2024/08/15
1150
掌握Linux网络设计中的WebSocket服务器
理论联系实际:从零理解WebSocket的通信原理、协议格式、安全性
WebSocket的出现,使得浏览器具备了实时双向通信的能力。本文由浅入深,介绍了WebSocket如何建立连接、交换数据的细节,以及数据帧的格式。此外,还简要介绍了针对WebSocket的安全攻击,以及协议是如何抵御类似攻击的。
JackJiang
2018/08/23
1.7K0
you-dont-know-websocket
本文阿宝哥将从多个方面入手,全方位带你一起探索 WebSocket 技术。阅读完本文,你将了解以下内容:
阿宝哥
2020/07/29
1.8K0
you-dont-know-websocket
websocket
这时启动django项目会报错CommandError: You have not set ASGI_APPLICATION, which is needed to run the server.
GH
2020/03/19
2.9K0
腾讯云CDN支持WebSocket
Websocket是用于服务端主动向客户端推送消息的技术。传统的HTTP/HTTPS只能由客户端向服务端发起请求,服务端对请求一一响应。在需要获取服务端状态变化的场景下,如:提交的后台任务是否执行成功,只能通过客户端轮询向服务端发起请求,不仅效率低,还浪费资源(HTTP1.0下每次轮询都需要经过TCP三次握手重新建立连接)。而WebSocket的出现较好的解决了这个问题,在TCP首次建立完连接之后,该连接不自动关闭,在有效期内客户端可以继续向服务端发送消息,服务端也能主动给客户端发送消息。
yaho
2020/05/02
23.9K0
腾讯云CDN支持WebSocket
PHP webSocket实现网页聊天室
http请求只能由客户端主动发起,服务器响应的模式, 服务器无法主动向客户端推数据,websocket的出现完美的解决了这一问题。 websocket和http处于同一层,都是基于TCP协议的,客户端和服务器使用websocket通讯的时候需要握手和传输数据两步, 握手借助http状态码101 switch protocol从http协议转换到websocket协议,之后便和http协议无关了。
用户3094376
2018/09/12
7.1K0
相关推荐
一文读懂 WebSocket 通信过程与实现
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档