Android-如何实现Apng动画播放

01

Apng是什么

Apng(Animated Portable Network Graphics)顾名思义是基于 PNG 格式扩展的一种动画格式,增加了对动画图像的支持,同时加入了 24 位图像和8位 Alpha 透明度的支持,并且向下兼容 PNG。

9cda84bfb1c0fbf0f84474f0d434653a.gifGoogle封面图 

02

Apng、Gif对比

1、图片质量

Gif最多支持8位颜色深度,图片边缘锯齿比较严重,Apng支持24位颜色深度色,抗锯齿效果更好。

7222d221b64f75727a21bd05f10f1520.gif 大象Gif、Apng图

2、透明度

Gif支持全透明或者不透明,不支持透明度渐变,而Apng支持8位Alpha透明通道。

2c7278bca6eb8ae53a844816227597ae.jpeg
带倒影彩色球

3、图片体积

GIF采用LZW压缩算法,而Apng采用Deflate压缩算法,在相同功能情况下如8位颜色深度,Apng文件体积会更小。9662aef865d0b7f79b648ee45888b4f1.gif

时钟、人物图

4、应用场景

Gif兼容性高,几乎所有浏览器都支持,移动端播放Gif动画控件都比较成熟。Apng兼容性较差,目前支持Apng的浏览器有 Chrome、Firefox、Safari 和 Opera,下面对Gif和Apng的应用场景做下说明:

1)图片质量要求不高的动画场景可以考虑使用Gif,图片质量要求比较高的动画场景可以考虑使用Apng,如动态表情等可以使用Gif,直播间礼物动效可以使用Apng;

2)对于有透明度渐变要求的动画场景,如要加载一个白天变黑夜的动画,使用Apng比较好,如果使用Gif,则会出现白色锯齿毛边;

3)对于像素点变化比较少的动画场景,使用Apng比较好,因为Apng前后像素相似点可以复用,文件体积相对比较小,如下面的城市夜景图:

c44ea83f4368d9bb4c5930fe9d87b1c4.gif城市夜景图

03

Android如何实现Apng动画播放

8253b6cfda8ac101934dd785a552f338.jpeg
彩色爆炸动画

1、Apng格式介绍

Apng的文件后缀名是.png,一张Apng图片包含一系列的png帧,每个png帧又包含当前png的相关信息,其结构图(图片来自维基百科,请点击查看)如下:17508754b795fc2e6a6b36c08b8dfc9f.jpeg

以下对上面的结构图进行说明:

1)Apng的IHDR(Image Head Chunk)用来包含Apng图片的宽、高等信息,acTL(Animation Control Chunk)包含动画播放的控制信息;

2)每一帧的fcTL(Frame Control Chunk)包含帧的一些控制信息,如Apng总的帧数,循环播放的次数,fdAT(Frame Data Chunk)包含每帧的图片数据信息,这跟png图片的IDAT块格式保持一致;

3)Apng的第一帧比较特殊,如果Apng的第一帧作为Apng默认显示的图片,则Apng结构跟上图的保持一致。如果Apng的第一帧不作为默认显示的图片,则Apng的默认显示图片从IDAT块取得,而Apng的结构图如下:

ef19d79411df9336f51814a09f970720.jpegApng结构图(第一帧非默认图)

Apng与正常的png数据结构差不多是一样的,只不过是多了三个chunk类型,分别是:acTL(Animation Control Chunk)fdAT(Frame Data Chunk)fcTL(Frame Control Chunk),chunk的格式一般如下:

a087049155aa5af3d3a475435efe5689.jpeg
Chunk的数据格式

Chunk由四部分组成,分别数据长度(4个字节),Chunk类型(4个字节),ChunkData,CRC(循环冗余校验,4个字节),下面分别对这三种chunk类型做具体说明。

  • acTL

acTL是Apng的动画控制Chunk,包含总的帧数num_frames,播放次数num_plays,num_frames和num_plays都占有4个字节,结构如下:

byte 含义 
0 num_frames:0~3字节表示该Apng总的播放帧数。
4 num_plays:4~7字节表示该Apng循环播放的次数。

num_frames一定大于等于1,如果等于1,表示Apng只有一帧,可以看成就是一张png图。num_plays一定大于等于0,如果等于0,表示Apng动画是循环播放的。 

备注:acTL一定是在IDAT前面

  • fdAT

fdAT是每帧的数据Chunk,与png的IDAT结构保持一致,只是额外增加了一个序列号,fdAT的结构如下:

byte 含义 
0 sequence_number:0~3字节表示动画帧的编号,从0开始。
4 frame_data :帧的数据信息。

这里需要说明的是,Apng的每一帧至少包含一个fdAT,而每一个fdAT是一张png的像素压缩数据,从该fdAT中,我们可以读取数据获得一张png图片。

  • fcTL

fcTL是每帧的控制信息块,fcTL一定是出现在fdAT或者IDAT的前面(第一帧为默认图片),fcTL的结构如下:

byte 含义 
0 sequence_number:控制帧的序号,从0开始。
4 width:帧的宽度。
8 height:帧的高度。
12 x_offset:在x方向的偏移。 
16 y_offset:在y方向的偏移。 
20 delay_num:帧动画时间间隙的分子 
22 delay_den:帧动画时间间隙的分母
24 dispose_op:在显示该帧之前,需要对前面缓冲输出区域做何种处理。
25 blend_op:具体显示该帧的方式。

下面对fcTL控制块包含的内容做具体说明:

1)x_offset,y_offset表示显示当前帧时,x方向和y方向需要偏移的距离,如果第一帧为默认显示图片,则x_offset,y_offset为0;

2)width、height、x_offset、y_offset、Apng的宽高(包含在IHDR块中)之间存在下面的约束条件:

x_offset >= 0
y_offset >= 0
width > 0
height > 0
x_offset + width<= IHDR width
y_offset + height <= IHDR height

3)Apng动画每帧的显示时长可以通过delay_num * DELAY_FACTOR / delay_den来获得,时间单位是ms,其中DELAY_FACTOR是一个常量,为1000,下面对delay_num、delay_den为0的情况做下说明。delay_num = 0 :得到帧动画时间间隙为0,当前帧播放完了,立即播放下一帧。delay_den = 0:delay_den为0,默认把其当做100来计算帧动画时间间隙,计算公式是:delay_num * DELAY_FACTOR / 100

4)dispose_op有三种类型,如下:

value 含义 
0     APNG_DISPOSE_OP_NONE:不做任何处理。
1     APNG_DISPOSE_OP_BACKGROUND:前一帧的x方向的偏移、y方向的偏移和当前帧的宽、高做一个剪裁,并将剪裁的区域抠成全黑色透明。
2     APNG_DISPOSE_OP_PREVIOUS:将当前缓冲输出区域恢复到先前的内容区域。

5)blend_op有两种类型,如下:

value 含义 
0  APNG_BLEND_OP_SOURCE:通过dispose处理后得到的bitmap,在该bitmap对当前帧的x方向的偏移、y方向的偏移和当前帧的宽、高做一个剪裁,并将剪裁的区域抠成全黑色透明,最后将当前帧写到该剪裁区域上。
1  APNG_BLEND_OP_OVER:当前帧覆盖到当前的缓冲区域并显示。

2、Apng动画播放整体设计

1e28030b307fb493b0efbc13a13fbe57.jpeg

Apng动画播放流程图

Apng动画播放实现分为如下几个步骤:

step1:通过MappedByteBuffer加载Apng文件,读取Apng的acTL(控制帧),保存播放次数等信息;

step2:使用SurfaceView容器,在子线程中从内存中读取fdAT数据帧)、fcTL(数据控制帧),假设第N帧,读取完后经过消除、合成处理,最后在SurfaceView的Canvas进行绘制;

step3:不停地重复步骤2的操作,直至Apng动画循环播放结束。 

3、Apng动画播放详细设计

Apng动画播放流程包括Apng解析和Apng渲染两个过程,Apng解析主要有两种方法,下面我们将会介绍,而Apng渲染主要包括三个步骤:消除(dispose)、合成(blend)、绘制(draw),由此得到Apng动画播放流程图如下:ce126c3fd03e3621434be9aba9334bcf.jpeg

Apng动画播放流程

消除dispose:指定了下一帧绘制之前对缓冲区的操作

1)不清空画布,直接把新的图像数据渲染到画布指定的区域

2)在渲染下一帧前将当前帧的区域内的画布清空为默认背景色

3)在渲染下一帧前将画布的当前帧区域内恢复为上一帧绘制后的结果

合成blend:指定了绘制当前帧之前对缓冲区的操作

1)表示清除当前区域再绘制

2)表示不清除直接绘制当前区域,图像叠加

渲染:生成Bitmap图片并在画布上进行绘制

  • Apng的解析

Apng的解析主要是将Apng文件转化成Apng序列帧Frame-n,从上面的流程图可知,Apng文件的解析列出了两种方案,下面来分别说说:

1)Apng文件首先经过一个解压(ApngExact)的过程,生成png序列帧保存在本地,然后经过加载(LoadPng)处理生成序列帧Frame-n。假设Apng动画文件总共有90帧,那么经过ApngExact处理后,会生成90张png序列帧保存在本地,每帧通过LoadPng处理生成Bitmap并供后面的Apng渲染使用。 

2)Apng是一个独立的文件,我们自己编写读取Apng文件的代码类:ApngReader,当渲染第i帧时,通过ApngReader直接获取第i帧的Bitmap。

比较: 

1)方案一是将Apng文件全部解压成png序列图片保存在本地,方案二是把Apng文件当做一个整体去处理,需要第几帧直接读取第几帧,并将该帧以Bitmap的形似保存到内存。

2)方案一解压得到的png图片在后面的渲染中需要转化成Bitamp,而方案二直接就获取了第几帧的Bitmap,相比于方案一,方案二减少了一个从SD卡读取png文件的操作。

  • Apng的渲染 

方案一的具体实现大家可以参考github上面的一个项目apng-view,下面我们来讲讲方案二的具体实现,即ApngReader的具体实现。

step1:解析Apng的每一帧

我们是将整个文件放到一个buffer里面,并且通过RandomAccessFile、MappedByteBuffer来读取Apng的每一帧,ApngReader的构造函数如下:

public ApngReader(String apngFile) throws IOException, FormatNotSupportException {RandomAccessFile f = new RandomAccessFile(apngFile, "r");mBuffer = f.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, f.length());f.close();if (mBuffer.getInt() != PNG_SIG&& mBuffer.getInt(4) != PNG_SIG_VER&& mBuffer.getInt(8) != CODE_IHDR) {throw new FormatNotSupportException("Not a png/apng file");}mChunk = new ApngMmapParserChunk(mBuffer);reset();}

下面来看看读取每一帧的方法:

/*** get next frame control info & bitmap** @return next frame control info, or null if no next FCTL chunk || no next IDAT/FDAT* @throws IOException*/public ApngFrame nextFrame() throws IOException {// reset read pointers from previous frame's lockmPngStream.clearDataChunks();mPngStream.resetPos();mChunk.unlockRead();// locate next FCTL chunkboolean ihdrCopied = false;while (mChunk.typeCode != CODE_fcTL) {switch (mChunk.typeCode) {case CODE_IEND:return null;case CODE_IHDR:mPngStream.setIHDR(mChunk.duplicateData());break;case CODE_acTL:handleACTL(mChunk);ihdrCopied = true;break;default:handleOtherChunk(mChunk);}mChunk.parseNext();}// located at FCTL chunkApngFrame frame = new ApngFrame();mChunk.assignTo(frame);// locate next IDAT or fdAt chunkmChunk.parseNext();// first move next from current FCTLwhile (mChunk.typeCode != CODE_IDAT && mChunk.typeCode != CODE_fdAT) {switch (mChunk.typeCode) {case CODE_IEND:return null;case CODE_IHDR:mPngStream.setIHDR(mChunk.duplicateData());ihdrCopied = true;break;case CODE_acTL:handleACTL(mChunk);break;default:handleOtherChunk(mChunk);}mChunk.parseNext();}// located at first IDAT or fdAT chunk// collect all consecutive dat chunksboolean needUpdateIHDR = true;int dataOffset = mChunk.getOffset();while (mChunk.typeCode == CODE_fdAT || mChunk.typeCode == CODE_IDAT) {if (needUpdateIHDR && (!ihdrCopied || mChunk.typeCode == CODE_fdAT)) {mPngStream.updateIHDR(frame.getWidth(), frame.getHeight());needUpdateIHDR = false;}if (mChunk.typeCode == CODE_fdAT) {mPngStream.addDataChunk(new Fdat2IdatChunk(mChunk));} else {mPngStream.addDataChunk(new ApngMmapParserChunk(mChunk));}mChunk.parseNext();}// lock position for this frame's image as OutputStreammChunk.lockRead(dataOffset);frame.imageStream = mPngStream;return frame;}

step2:Apng的消除操作

Apng的消除操作是在ApngFrameRender的render方法做的,方法如下:

/*** 渲染当前帧画面** @param frame apng中当前帧* @return 渲染合成后的当前帧图像*/public Bitmap render(ApngFrame frame, Bitmap frameBmp) {// 执行消除操作dispose(frame);// 合成当前帧blend(frame, frameBmp);return mRenderFrame;}

dispose(ApngFrame frame)方法如下:

/*** 帧图像析构消除 - 提交结果*/private void dispose(ApngFrame frame) {// last frame dispose opswitch (mLastDisposeOp) {case APNG_DISPOSE_OP_NONE:// no opbreak;case APNG_DISPOSE_OP_BACKGROUND:// clear rectmRenderCanvas.clipRect(mDisposeRect);mRenderCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);mRenderCanvas.clipRect(mFullRect, Region.Op.REPLACE);break;case APNG_DISPOSE_OP_PREVIOUS:// swap work and cache bitmapBitmap bmp = mRenderFrame;mRenderFrame = mDisposedFrame;mDisposedFrame = bmp;mRenderCanvas.setBitmap(mRenderFrame);mDisposeCanvas.setBitmap(mDisposedFrame);break;}// current frame dispose opmLastDisposeOp = frame.getDisposeOp();switch (mLastDisposeOp) {case APNG_DISPOSE_OP_NONE:// no opbreak;case APNG_DISPOSE_OP_BACKGROUND:// cache rect for next clear disposeint x = frame.getxOff();int y = frame.getyOff();mDisposeRect.set(x, y, x + frame.getWidth(), y + frame.getHeight());break;case APNG_DISPOSE_OP_PREVIOUS:// cache bmp for next restore disposemDisposeCanvas.clipRect(mFullRect, Region.Op.REPLACE);mDisposeCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);mDisposeCanvas.drawBitmap(mRenderFrame, 0, 0, null);break;}}

step3:Apng的合成操作

Apng的合成操作是在每一帧经过dispose之后做的,具体方法是blend(ApngFrame frame, Bitmap frameBmp),代码如下:

/*** 帧图像合成*/private void blend(ApngFrame frame, Bitmap frameBmp) {int xOff = frame.getxOff();int yOff = frame.getyOff();mRenderCanvas.clipRect(xOff, yOff, xOff + frame.getWidth(), yOff + frame.getHeight());if (frame.getBlendOp() == APNG_BLEND_OP_SOURCE) {mRenderCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);}mRenderCanvas.drawBitmap(frameBmp, xOff, yOff, null);mRenderCanvas.clipRect(mFullRect, Region.Op.REPLACE);}

step4:Apng的绘制

Apng的每一帧经过消除、合成操作之后,就可以在View上面draw,具体代码如下:

/*** draw the appointed frame*/private void drawFrame(AnimParams animItem, ApngFrame frame, Bitmap frameBmp) {if (surfaceEnabled && !isInterrupted()) {//start to draw the frametry {Matrix matrix = new Matrix();matrix.setScale(mScale, mScale);Bitmap bmp = mFrameRender.render(frame, frameBmp);//saveBitmap(bmp, index);index ++;Canvas canvas = getHolder().lockCanvas();//anti-aliasingcanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);float[] tranLeftAndTop = ApngUtils.getTranLeftAndTop(canvas, bmp, animItem.align, mScale, animItem.percent);canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));matrix.postTranslate(tranLeftAndTop[0], tranLeftAndTop[1]);canvas.drawBitmap(bmp, matrix, null);getHolder().unlockCanvasAndPost(canvas); //  unlock the canvas} catch (Exception e) {Log.e(TAG, "draw error msg:" + Log.getStackTraceString(e));}}}

04

最后

要完成Apng的播放,就必须实现Apng解析和渲染两个步骤,实现Apng解析后,在Android控件的Canvas不断绘制png图片就实现png图片的连续播放,达到动图的播放效果,该方案比传统的Apng播放实现有如下两个优点:

1)Apng的每一帧图片都是从内存加载,不是从磁盘加载,无IO操作,动画播放不会卡顿;

2)Apng的每一帧渲染是在子线程里面完成的,不会阻塞UI主线程,不会出现ANR。

除此之外,Apng相比Gif,能够在提高动画质量的同时,较容易实现动画Alpha渐变效果。

如果你想了解更多Apng,如Apng的制作,Apng对比WebP等相关知识,可以参见Apng

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/476611.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux下Intel编译器oneAPI安装和链接MKL库编译

参考: https://blog.csdn.net/qq_44263574/article/details/123582481 官网下载: https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit-download.html?packagesoneapi-toolkit&oneapi-toolkit-oslinux&oneapi-linoffline 填写邮件和国家,…

【Python系列】浅析 Python 中的字典更新与应用场景

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Matlab科研绘图:自定义内置多款配色函数

在Matlab科研绘图中&#xff0c;自定义和使用内置的多款配色函数可以极大地增强图表的视觉效果和数据的可读性。本文将介绍配色函数&#xff0c;共计带来6套配色体系&#xff0c;而且后续可以根据需要修改&#xff0c;帮助大家自定义和使用配色函数。 1.配色函数 可以根据个…

网络安全的学习方向和路线是怎么样的?

最近有同学问我&#xff0c;网络安全的学习路线是怎么样的&#xff1f; 废话不多说&#xff0c;先上一张图镇楼&#xff0c;看看网络安全有哪些方向&#xff0c;它们之间有什么关系和区别&#xff0c;各自需要学习哪些东西。 在这个圈子技术门类中&#xff0c;工作岗位主要有以…

JAVA八股与代码实践----JDK代理和CGLIB代理的区别

当spring发现该代理的类实现了接口会使用JDK代理&#xff0c;如果该类没有实现接口&#xff0c;会使用CGLIB代理 如果在springboot中要强制使用CGLIB代理&#xff0c;需要加上 EnableAspectJAutoProxy(proxyTargetClass true) // 强制使用 CGLIB SpringBootApplication Ena…

环境背景文本到语音转换

目录 概述演示效果核心逻辑使用方式 概述 本文所涉及的所有资源的获取方式&#xff1a;https://www.aspiringcode.com/content?id100000000027&uid2f1061526e3a4548ab2e111ad079ea8c 论文标题&#xff1a; 本文提出了 VoiceLDM&#xff0c;这是一种旨在生成准确遵循两种…

mac安装Pytest、Allure、brew

安装环境 安装pytest 命令 pip3 install pytest 安装allure 命令&#xff1a;brew install allure 好吧 那我们在安装allure之前 我们先安装brew 安装brew 去了官网复制了命令 还是无法下载 如果你们也和我一样可以用这个方法哦 使用国内的代码仓库来执行brew的安装脚本…

【Linux】重定向,dup

目录 文件描述符分配规则 重定向 dup ​编辑 输出重定向 追加重定向 输入重定向。 重定向会影响后面的程序替换吗&#xff1f; 1号文件和2号文件 2号文件输出重定向 下标之间的重定向 文件描述符分配规则 重定向 把显示器文件关闭后&#xff0c;本来应该写给显示器…

Vue实训---1-创建Vue3项目

1.创建项目&#xff08;项目名为my-vue-project&#xff09; npm create vitelatest my-vue-project -- --template vue 运行命令npm -v&#xff0c;查看npm版本号&#xff0c;如果是npm 7或更高版本运行以上命令即可。如果是npm 6或更低版本&#xff0c;使用npm create vite…

智慧社区方案提升居民生活质量与管理效率的创新实践

内容概要 智慧社区方案的背景与发展趋势指向了一个日益重要的方向&#xff0c;随着城市化进程的加快&#xff0c;传统的社区管理模式逐渐显得力不从心。在这个时候&#xff0c;智慧社区应运而生&#xff0c;它通过将现代信息技术与社区管理深度结合&#xff0c;为提升居民生活…

【IDER、PyCharm】免费AI编程工具完整教程:ChatGPT Free - Support Key call AI GPT-o1 Claude3.5

文章目录 CodeMoss 简介CodeMoss 的模型集成如何安装和配置 CodeMossIDER 插件安装步骤 CodeMoss 的实战使用AI 问答功能代码优化与解释优化这段代码解释这段代码 文件上传与对话联网查询与 GPT 助手联网查询GPT 助手 提升开发效率的最佳实践结语更多文献 CodeMoss 简介 CodeM…

Java安全—JNDI注入RMI服务LDAP服务JDK绕过

前言 上次讲到JNDI注入这个玩意&#xff0c;但是没有细讲&#xff0c;现在就给它详细地讲个明白。 JNDI注入 那什么是JNDI注入呢&#xff0c;JNDI全称为 Java Naming and Directory Interface&#xff08;Java命名和目录接口&#xff09;&#xff0c;是一组应用程序接口&…

HarmonyOS笔记5:ArkUI框架的Navigation导航组件

ArkUI框架的Navigation导航组件 在移动应用中需要在不同的页面进行切换跳转。这种切换和跳转有两种方式&#xff1a;页面路由和Navigation组件实现导航。HarmonyOS推荐使用Navigation实现页面跳转。在本文中在HarmonyOS 5.0.0 Release SDK (API Version 12 Release)版本下&…

YOLOv11来了,使用YOLOv11训练自己的数据集和预测 (保姆级无代码操作版)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言性能表现数据集准备1.数据标注2.数据标签转换 YOLO模型训练教程1.模型安装2.YOLO11 模型训练3.YOLO11 预测结果 总结 前言 YOLOv11是由Ultralytics团队于2024年…

彻底理解消息队列的作用及如何选择

一.为什么要使用消息队列&#xff1f; 使用消息队列&#xff0c;其实是需要根据实际业务场景来的&#xff0c;一般都是实际开发中&#xff0c;遇到了某种技术挑战&#xff0c;如果不使用MQ的话&#xff0c;业务实现起来比较麻烦&#xff0c;但是通过MQ就可以更快捷高效的实现业…

在 Taro 中实现系统主题适配:亮/暗模式

目录 背景实现方案方案一&#xff1a;CSS 变量 prefers-color-scheme 媒体查询什么是 prefers-color-scheme&#xff1f;代码示例 方案二&#xff1a;通过 JavaScript 监听系统主题切换 背景 用Taro开发的微信小程序&#xff0c;需求是页面的UI主题想要跟随手机系统的主题适配…

飞凌嵌入式T113-i开发板RISC-V核的实时应用方案

随着市场对嵌入式设备的功能需求越来越高&#xff0c;集成了嵌入式处理器和实时处理器的主控方案日益增多&#xff0c;以便更好地平衡性能与效率——实时核负责高实时性任务&#xff0c;A核处理复杂任务&#xff0c;两核间需实时交换数据。然而在数据传输方面&#xff0c;传统串…

SpringCloud实用-OpenFeign 调用三方接口

文章目录 前言正文一、项目环境二、项目结构2.1 包的含义2.2 代理的场景 三、完整代码示例3.1 定义FeignClient3.2 定义拦截器3.3 配置类3.4 okhttp配置3.5 响应体3.5.1 天行基础响应3.5.2 热点新闻响应 3.6 代理类3.6.1 代理工厂3.6.2 代理客户端3.6.3 FeignClient的建造器 四…

《Object类》

目录 一、Object类 1.1 定义与地位 1.2 toString()方法 1.3 equals()方法 1.4 hashcode()方法 一、Object类 1.1 定义与地位 Object类是Java语言中的根类&#xff0c;所有的类&#xff08;除了Object类&#xff09;都直接或间接继承自Object。这就意味着在Java中&#xf…

单头蜗杆铣刀计算——记录一下

前面介绍过一期蜗杆的一些常用的加工方式《蜗杆的加工方式》&#xff0c;其中铣削加工也是非常常见的一种加工方式&#xff0c;下面来看看蜗杆铣刀的由来过程&#xff1a; 首先拿到蜗杆参数之后&#xff0c;需要将蜗杆准确的描述出来。渐开线蜗杆的参数与齿轮基本一致&#xf…