android so库导致的闪退及tombstone分析
android中有3种crash情况:未捕获的异常、ANR和闪退。未捕获的异常一般用crash文件就可以记录异常信息,而ANR无响应表现就是界面卡着无法响应用户操作,而闪退则是整个app瞬间退出,个人感觉对用户造成的体验最差。闪退一般是由于调用so库出错导致,像类似非法地址访问等。
闪退发生时在logcat中将日志过滤条件选为“No Filters”就可以看到完整的闪退日志,或者叫tombstone(墓碑)文件。
tombstone(墓碑)是当系统 crash 的时候,会保存一个 tombstone 文件到/data/tombstones目录下(Logcat中也会有相应的信息),文件就像墓碑一样记录了死亡了的进程的基本信息(例如进程的进程 号,线程号),死亡的地址(在哪个地址上发生了 Crash),死亡时的现场是什么样的(记录了一系列的堆栈调用信息)等等。
闪退(tombstone)主要日志如下,中间太长部分用省略号省略。
--------- beginning of crash
2020-01-19 15:22:07.194 14414-14414/com.android.dj.crash.test A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xfffffffd in tid 14414 (dj.crashtest), pid 14414 (dj.crashtest)
2020-01-19 15:22:07.249 14414-18253/com.android.dj.crash.test V/IOTCAPIS: [io_recv_proc][297]:
2020-01-19 15:22:07.249 14414-18253/com.android.dj.crash.test V/IOTCAPIS: get remote packet ICE_SES_MSG_HIT
2020-01-19 15:22:07.303 18260-18260/? I/crash_dump32: obtaining output fd from tombstoned, type: kDebuggerdTombstone
2020-01-19 15:22:07.309 883-883/? I//system/bin/tombstoned: received crash request for pid 14414
2020-01-19 15:22:07.311 18260-18260/? I/crash_dump32: performing dump of process 14414 (target tid = 14414)
2020-01-19 15:22:07.313 1313-1416/system_process W/ActivityTaskManager: Activity pause timeout for ActivityRecord{567b991 u0 com.android.dj.crash.test/com.android.dj.crash.view.CrashTestActivity t2302 f}
···
2020-01-19 15:22:07.330 18260-18260/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2020-01-19 15:22:07.330 18260-18260/? A/DEBUG: Build fingerprint: 'HUAWEI/VOG-AL00/HWVOG:10/HUAWEIVOG-AL00/10.0.0.185C00:user/release-keys'
2020-01-19 15:22:07.330 18260-18260/? A/DEBUG: Revision: '0'
2020-01-19 15:22:07.330 18260-18260/? A/DEBUG: ABI: 'arm'
2020-01-19 15:22:07.331 18260-18260/? A/DEBUG: SYSVMTYPE: MapleAPPVMTYPE: Art
2020-01-19 15:22:07.331 18260-18260/? A/DEBUG: Timestamp: 2020-01-19 15:22:07+0800
2020-01-19 15:22:07.331 18260-18260/? A/DEBUG: pid: 14414, tid: 14414, name: dj.crashtest >>> com.android.dj.crash.test <<<
2020-01-19 15:22:07.331 18260-18260/? A/DEBUG: uid: 10218
2020-01-19 15:22:07.331 18260-18260/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xfffffffd
2020-01-19 15:22:07.331 18260-18260/? A/DEBUG: r0 fffffffd r1 ff99f161 r2 80000000 r3 00660e9d
2020-01-19 15:22:07.331 18260-18260/? A/DEBUG: r4 ffffffff r5 b9f33000 r6 867cb252 r7 ff99f108
2020-01-19 15:22:07.331 18260-18260/? A/DEBUG: r8 00000000 r9 eb283e00 r10 ff99f7a0 r11 eb283e00
2020-01-19 15:22:07.331 18260-18260/? A/DEBUG: ip 20000000 sp ff99f0c8 lr 00000002 pc e9097000
2020-01-19 15:22:07.419 2378-10947/? D/HwRecentsTaskUtils: refreshToCache
2020-01-19 15:22:07.419 2378-10947/? D/HwRecentsTaskUtils: searchFromDate
2020-01-19 15:22:07.427 2302-10215/? E/HsmCoreServiceImpl: onTransact in code is: 103
2020-01-19 15:22:07.427 2302-10215/? I/MediaProcessHandler: playingUids:
···
2020-01-19 15:22:07.581 14414-18186/com.android.dj.crash.test I/IPCSDK: 1
2020-01-19 15:22:07.582 14414-18186/com.android.dj.crash.test V/IOTCAPIS: [p2p_global_thread][94]:
2020-01-19 15:22:07.582 14414-18186/com.android.dj.crash.test V/IOTCAPIS: p2p_global_thread GLOBAL_EVENT_MSG_CB_EVENT_SESSION_CONNECT_SUCCESS_P2P end
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: backtrace:
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: #00 pc 0004f000 /apex/com.android.runtime/lib/bionic/libc.so (__memcpy_a15+200) (BuildId: f2470da1a22265f8104ce6bb9bcaf63e)
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: #01 pc 00023d6f /data/app/com.android.dj.crash.test-dA-LA_OmHFay2acWJnDeJA==/lib/arm/libipcsdk.so (LoopBuffWrite+138) (BuildId: cf6a63bc513797ba7582c510f377f11c44a12970)
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: #02 pc 00031f75 /data/app/com.android.dj.crash.test-dA-LA_OmHFay2acWJnDeJA==/lib/arm/libipcsdk.so (CP2PSessionData::p2p_session_data_write(char*, int, unsigned char)+264) (BuildId: cf6a63bc513797ba7582c510f377f11c44a12970)
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: #03 pc 0002a561 /data/app/com.android.dj.crash.test-dA-LA_OmHFay2acWJnDeJA==/lib/arm/libipcsdk.so (IOTC_Session_WriteData+66) (BuildId: cf6a63bc513797ba7582c510f377f11c44a12970)
···2020-01-19 15:22:07.693 18260-18260/? A/DEBUG: #134 pc 0000202f /system/bin/app_process32 (_start_main+38) (BuildId: 9979c215af59ed821fac6ea4f956225d)
2020-01-19 15:22:07.693 18260-18260/? A/DEBUG: #135 pc 00004456 <anonymous:eb84e000>
可以看到
tombstone文件如何分析
主要看backtrace下面的函数调用,backtrace下的函数调用是从下往上的顺序执行的,所以在最上面的函数是最后执行的。
最后几行的函数调用如下:
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: backtrace:
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: #00 pc 0004f000 /apex/com.android.runtime/lib/bionic/libc.so (__memcpy_a15+200) (BuildId: f2470da1a22265f8104ce6bb9bcaf63e)
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: #01 pc 00023d6f /data/app/com.android.dj.crash.test-dA-LA_OmHFay2acWJnDeJA==/lib/arm/libipcsdk.so (LoopBuffWrite+138) (BuildId: cf6a63bc513797ba7582c510f377f11c44a12970)
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: #02 pc 00031f75 /data/app/com.android.dj.crash.test-dA-LA_OmHFay2acWJnDeJA==/lib/arm/libipcsdk.so (CP2PSessionData::p2p_session_data_write(char*, int, unsigned char)+264) (BuildId: cf6a63bc513797ba7582c510f377f11c44a12970)
2020-01-19 15:22:07.691 18260-18260/? A/DEBUG: #03 pc 0002a561 /data/app/com.android.dj.crash.test-dA-LA_OmHFay2acWJnDeJA==/lib/arm/libipcsdk.so (IOTC_Session_WriteData+66) (BuildId: cf6a63bc513797ba7582c510f377f11c44a12970)
我们需要记住最后几个函数调用的地址00023d6f、00031f75、0002a561,后面用到的工具分析以及反汇编后的文件分析都会用到这几个偏移地址。
分析工具
android的ndk中提供了多个工具可以进行so库导致的crash的分析。
1)addr2line
addr2line 是 用来获得指定动态链接库文件或者可执行文件中指定地址对应的源代码信息的工具
D:\DJ_Software\Android\Ndk_Download\android-ndk-r10e-windows-x86_64\android-ndk-r10e\toolchains\arm-linux-androideabi-4.8\prebuilt\windows-x86_64\bin>arm-linux-androideabi-addr2line -f -e D:\flash_anr_pc\libipcsdk.so 00023d6f
LoopBuffWrite
??:?D:\DJ_Software\Android\Ndk_Download\android-ndk-r10e-windows-x86_64\android-ndk-r10e\toolchains\arm-linux-androideabi-4.8\prebuilt\windows-x86_64\bin>arm-linux-androideabi-addr2line -f -e D:\flash_anr_pc\libipcsdk.so 00031f75
_ZN15CP2PSessionData22p2p_session_data_writeEPcih
??:?
addr2line 命令及执行结果如上所示,-e参数指定文件名,-f参数显示函数名。只是得到了函数入口,详细运行信息没有。再使用objdump工具看下。
2)objdump
objdump可以将so库进行反汇编,反汇编后得到重定向文件,然后根据偏移地址得到更详细的函数调用上下文信息。
我实际使用ndk 20版本发现addr2line 运行可以,objdump总有一些错误,所以最后使用ndk 10命令执行成功。
D:\DJ_Software\Android\Ndk_Download\android-ndk-r10e-windows-x86_64\android-ndk-r10e\toolchains\arm-linux-androideabi-4.8\prebuilt\windows-x86_64\arm-linux-androideabi\bin>objdump -S -D D:\flash_anr_pc\libipcsdk.so > D:\flash_anr_pc\deassmble_libipc.log
命令执行结果就是将反汇编后的结果写入到D盘对应目录的deassmble_libipc.log文件里。
反汇编后结果分析
仍是重点分析前面提到的那3个函数。实际发现反汇编后不知为何得到的函数偏移地址总是比crash日志中的偏移地址小1。
IOTC_Session_WriteData函数
打开deassmble_libipc.log文件,搜索偏移地址“2a560”。
0002a51e <IOTC_Session_WriteData>:2a51e: b5b0 push {r4, r5, r7, lr}2a520: af02 add r7, sp, #82a522: b08a sub sp, #40 ; 0x282a524: 469c mov ip, r32a526: 4696 mov lr, r22a528: 460c mov r4, r1
···2a560: f7ea ef62 blx 15428 <_ZN15CP2PSessionData22p2p_session_data_writeEPcih@plt>2a564: 9009 str r0, [sp, #36] ; 0x242a566: e7ff b.n 2a568 <IOTC_Session_WriteData+0x4a>2a568: 9809 ldr r0, [sp, #36] ; 0x242a56a: b00a add sp, #40 ; 0x282a56c: bdb0 pop {r4, r5, r7, pc}
可以看到IOTC_Session_WriteData函数在2a560行(crash日志中是0002a561)调用了p2p_session_data_write函数,p2p_session_data_write函数是C++类中的成员函数,所以在汇编中的函数名与纯C函数的函数名有所区别。
p2p_session_data_write函数
00031e6c <_ZN15CP2PSessionData22p2p_session_data_writeEPcih>:31e6c: b5f0 push {r4, r5, r6, r7, lr}31e6e: af03 add r7, sp, #1231e70: f84d bd04 str.w fp, [sp, #-4]!31e74: b098 sub sp, #96 ; 0x6031e76: 469c mov ip, r331e78: 4696 mov lr, r2
···31f66: d923 bls.n 31fb0 <_ZN15CP2PSessionData22p2p_session_data_writeEPcih+0x144>31f68: e7ff b.n 31f6a <_ZN15CP2PSessionData22p2p_session_data_writeEPcih+0xfe>31f6a: 9808 ldr r0, [sp, #32]31f6c: f500 7067 add.w r0, r0, #924 ; 0x39c31f70: a914 add r1, sp, #80 ; 0x5031f72: 2209 movs r2, #931f74: f7e2 ea0c blx 14390 <LoopBuffWrite@plt>31f78: 9910 ldr r1, [sp, #64] ; 0x4031f7a: 2901 cmp r1, #131f7c: 9005 str r0, [sp, #20]31f7e: db09 blt.n 31f94 <_ZN15CP2PSessionData22p2p_session_data_writeEPcih+0x128>
可以看到实际的LoopBuffWrite函数调用在31f74行(crash日志中的00031f75)。
LoopBuffWrite函数
00023ce4 <LoopBuffWrite>:23ce4: b5d0 push {r4, r6, r7, lr}23ce6: af02 add r7, sp, #823ce8: b08c sub sp, #48 ; 0x3023cea: 4613 mov r3, r223cec: 468c mov ip, r123cee: 4686 mov lr, r0
···23d64: 3a01 subs r2, #123d66: 4010 ands r0, r223d68: 4408 add r0, r123d6a: 990a ldr r1, [sp, #40] ; 0x2823d6c: 9a08 ldr r2, [sp, #32]23d6e: f7f0 ecde blx 1472c <__aeabi_memcpy@plt>23d72: 990b ldr r1, [sp, #44] ; 0x2c23d74: 6809 ldr r1, [r1, #0]23d76: 9a0a ldr r2, [sp, #40] ; 0x2823d78: f8dd e020 ldr.w lr, [sp, #32]
中间部分代码省略,可以看到在23d6e行(crash日志中为00023d6f),也是差一行。LoopBuffWrite在此处执行了__aeabi_memcpy,估计是执行数据拷贝时发生了错误,再继续深入就需要对汇编语言有所了解。
最后,还有一个工具ndk-stack可以简化分析过程。使用参见
https://blog.csdn.net/u010144805/article/details/80763956
相关文章:
android so库导致的闪退及tombstone分析
android中有3种crash情况:未捕获的异常、ANR和闪退。未捕获的异常一般用crash文件就可以记录异常信息,而ANR无响应表现就是界面卡着无法响应用户操作,而闪退则是整个app瞬间退出,个人感觉对用户造成的体验最差。闪退一般是由于调用…...
图结构基本知识
图 1. 相关概念2. 图的表示方式3. 图的遍历3.1 深度优先遍历(DFS)3.2 广度优先遍历(BFS) 1. 相关概念 图G(V,E) :一种数据结构,可表示“多对多”关系,由顶点集V和边集E组成;顶点(ve…...
Hibernate 的多种查询方式
Hibernate 是一个开源的 ORM(对象关系映射)框架,它可以将 Java 对象映射到数据库表中,实现对象与关系数据库的映射。Hibernate 提供了多种查询方式,包括 OID 检索、对象导航检索、HQL 检索、QBC 检索和 SQL 检索。除此…...
FreeRTOS 任务调度及相关函数详解(一)
文章目录 一、任务调度器开启函数 vTaskStartScheduler()二、内核相关硬件初始化函数 xPortStartScheduler()三、启动第一个任务 prvStartFirstTask()四、中断服务函数 xPortPendSVHandler()五、空闲任务 一、任务调度器开启函数 vTaskStartScheduler() 这个函数的功能就是开启…...
飞桨paddlespeech语音唤醒推理C实现
上篇(飞桨paddlespeech 语音唤醒初探)初探了paddlespeech下的语音唤醒方案,通过调试也搞清楚了里面的细节。因为是python 下的,不能直接部署,要想在嵌入式上部署需要有C下的推理实现,于是我就在C下把这个方…...
04-Mysql常用操作
1. DDL 常见数据库操作 # 查询所有数据库 show databases; # 查询当前数据库 select databases();# 使用数据库 use 数据库名;# 创建数据库 create database [if not exits] 数据库名; # []代表可选可不选# 删除数据库 drop database [if exits] 数据库名; 常见表操作 创建…...
TensorFlow 2 和 Keras 高级深度学习:1~5
原文:Advanced Deep Learning with TensorFlow 2 and Keras 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象&#x…...
UML类图
一、UML 1、什么是UML? UML——Unified modeling language UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果。UML本身是一套符号的规定,就像数学符号和化学符号一样&…...
【Python】【进阶篇】二十六、Python爬虫的Scrapy爬虫框架
目录 二十六、Python爬虫的Scrapy爬虫框架26.1 Scrapy下载安装26.2 创建Scrapy爬虫项目1) 创建第一个Scrapy爬虫项目 26.3 Scrapy爬虫工作流程26.4 settings配置文件 二十六、Python爬虫的Scrapy爬虫框架 Scrapy 是一个基于 Twisted 实现的异步处理爬虫框架,该框架…...
PyTorch 深度学习实用指南:6~8
原文:PyTorch Deep Learning Hands-On 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象,只关心如何实现目…...
数据湖 Hudi 核心概念
文章目录 什么是 Hudi ?Hudi 是如何对数据进行管理的?Hudi 表结构Hudi 核心概念 什么是 Hudi ? Hudi 是一个用于处理大数据湖的开源框架。 大数据湖是指一个大规模的、中心化的数据存储库,其中包含各种类型的数据,如结构化数据、半结构化…...
爬虫请求头Content-Length的计算方法
重点:使用node.js 环境计算,同时要让计算的数据通过JSON.stringify从对象变成string。 1. Blob size var str 中国 new Blob([str]).size // 6 2、Buffer.byteLength # node > var str 中国 undefined > Buffer.byteLength(str, utf8) 6 原文…...
Open Inventor 2023.1 Crack
发行说明 Open Inventor 2023.1(次要版本) 文档于 2023 年 4 月发布。 此版本中包含的增强功能和新功能: Open Inventor 10 版本编号更改体积可视化 单一分辨率的体绘制着色器中与裁剪和 ROI 相关的新功能MeshVizXLM 在 C 中扩展的剪辑线提…...
【华为OD机试真题】查找树中元素(查找二叉树节点)(javaC++python)100%通过率
查找树中元素 知识点树BFSQ搜索广搜 时间限制:1s空间限制:256MB限定语言:不限 题目描述: 已知树形结构的所有节点信息,现要求根据输入坐标(x,y)找到该节点保存的内容 值;其中: x表示节点所在的层数,根节点位于第0层,根节点的子节点位于第1层,依次类推; y表示节…...
常用设计模式
里氏替换原则:子类可以扩展父类的功能,但是不要更改父类的已经实现的方法子类对父类的方法尽量不要重写和重载。(我们可以采用final的手段强制来遵循)创建型模式 单例模式:维护线程数据安全 懒汉式 public class Test{ 饿汉式 private static final Test…...
时序分析 49 -- 贝叶斯时序预测(一)
贝叶斯时序预测(一) 时序预测在统计分析和机器学习领域一直都是一个比较重要的话题。在本系列前面的文章中我们介绍了诸如ARIMA系列方法,Holt-Winter指数平滑模型等多种常用方法,实际上这些看似不同的模型和方法之间都具有千丝万缕…...
从传统管理到智慧水务:数字化转型的挑战与机遇
概念 智慧水务是指利用互联网、物联网、大数据、人工智能等技术手段,将智能化、信息化、互联网等技术与水务领域相结合,通过感知、传输、处理水质、水量、水价等数据信息,对水资源进行全面监测、综合管理、智能调度和优化配置的智能化水务系…...
ROS学习第十八节——launch文件(详细介绍)
1.概述 关于 launch 文件的使用已经不陌生了,之前就曾经介绍到: 一个程序中可能需要启动多个节点,比如:ROS 内置的小乌龟案例,如果要控制乌龟运动,要启动多个窗口,分别启动 roscore、乌龟界面节点、键盘控制节点。如果…...
javaweb在校大学生贷款管理系统ns08a9
1系统主要实现:学生注册、填写详细资料、申请贷款、学校审核、银行审核、贷后管理等功能, (1) 学生注册:学生通过注册用户,提交自己的详细个人资料,考虑现实应用中的安全性,资料提交后不可修改;…...
分布式之搜索解决方案es
一 ES初识 1.1 概述 ElasticSearch:是基于 Lucene 的 Restful 的分布式实时全文搜索引擎,每个字段都被索引并可被搜索,可以快速存储、搜索、分析海量的数据。是ELK的一个组成,是一个产品,而且是非常完善的产品,ELK代表…...
终极免费Switch模拟器yuzu:解决电脑玩任天堂游戏的5大痛点
终极免费Switch模拟器yuzu:解决电脑玩任天堂游戏的5大痛点 【免费下载链接】yuzu 任天堂 Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu 想在电脑上畅玩Switch游戏却总是遇到各种问题?yuzu模拟器作为全球最受欢迎的开源任…...
Arm Neoverse CMN-700互连架构与协议寄存器配置指南
1. Arm Neoverse CMN-700一致性互连架构解析在现代多核处理器设计中,一致性互连网络如同城市交通系统般重要。Arm Neoverse CMN-700作为第二代Coherent Mesh Network解决方案,其架构设计充分考虑了数据中心和边缘计算的严苛需求。与传统的总线或环形拓扑…...
Godot卡牌游戏框架终极指南:3小时从零构建专业级卡牌游戏
Godot卡牌游戏框架终极指南:3小时从零构建专业级卡牌游戏 【免费下载链接】godot-card-game-framework A framework which comes with prepared scenes and classes to kickstart your card game, as well as a powerful scripting engine to use to provide full r…...
如何在10分钟内搭建个人游戏流媒体服务器:Sunshine跨平台游戏串流完全指南
如何在10分钟内搭建个人游戏流媒体服务器:Sunshine跨平台游戏串流完全指南 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 您是否梦想过在任何设备上畅玩PC游戏&#x…...
从0到1:手把手教你搭建VSCode(附避坑指南,拒绝报错),全程复制粘贴即可
🔥个人主页:北极的代码(欢迎来访) 🎬作者简介:java后端学习者 ❄️个人专栏:苍穹外卖日记,SSM框架深入,JavaWeb ✨命运的结局尽可永在,不屈的挑战却不可须臾或…...
Shell脚本加固实战:用shellguard提升脚本健壮性与安全性
1. 项目概述:一个为Shell脚本穿上“防弹衣”的守护者 在运维开发、自动化部署乃至日常的系统管理工作中,Shell脚本是我们最忠实、最高效的伙伴。从简单的日志清理到复杂的CI/CD流水线,Shell脚本无处不在。然而,脚本的安全性、健壮…...
基于RAG的智能知识库问答系统:从原理到部署实战
1. 项目概述:当AI大模型遇见知识库,一个开源的智能问答解决方案 最近在折腾一个很有意思的开源项目,叫 zhimaAi/chatwiki 。光看名字,你大概能猜到它的核心: chat 代表对话, wiki 代表知识库。没错&a…...
ElevenLabs情绪驱动API实战手册(2024企业级部署全链路):从F0曲线调制到微表情时序对齐
更多请点击: https://intelliparadigm.com 第一章:ElevenLabs情绪驱动API核心架构与演进脉络 ElevenLabs 的情绪驱动 API 并非简单叠加情感标签的语音合成增强层,而是构建在多模态表征学习与实时声学参数调控双引擎之上的闭环系统。其核心架…...
基于RAG与向量数据库的智能信息管理系统(IIMS)架构与实现
1. 项目概述:当AI成为你的“第二大脑”最近在折腾一个挺有意思的项目,叫“IIMS-By-AI”。乍一看这个标题,可能有点摸不着头脑,但拆解一下就能明白它的野心:IntelligentInformationManagementSystem, By AI。…...
开源大模型推理引擎Takeoff部署指南:从原理到生产实践
1. 项目概述:一个让大模型推理“起飞”的开源引擎 如果你正在为如何将那些动辄几十GB、几百亿参数的大语言模型(LLM)部署到生产环境而头疼,或者厌倦了为每一次API调用支付高昂的费用,那么今天聊的这个项目,…...
