2021-07-29

开篇废话

很久不研究cocos2d-x了,也不知道如今发展如何了。先前写游戏时会分几块。

  • 主要功能代码都是用C++编写,编辑器用vs
  • android平台相关功能使用 Java 编写,编辑器用 Eclipse
  • ios平台相关功能使用 Object-C 编写,编辑器用 Xcode

基本流程就是先在 VS上开发主要功能,等测试通过了,然后再针对不同平台做接入。所以调试方面

  • win32工程,可以在VS中调试
  • Xcode工程,可以在Xcode中调试
  • Android工程,无法调试

集成so包的Android工程是无法在eclipse中调试的,这里的调试主要是指在c/c++端打断点调试。只能通过logcat看输出。比较难搞的bug是,win32工程运行正常,但是打包装到android手机上运行时就崩溃。基本崩溃输出如下:一堆的堆栈信息。

I/DEBUG   (   31): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG   (   31): Build fingerprint: 'generic/google_sdk/generic/:2.2/FRF91/43546:eng/test-keys'
I/DEBUG   (   31): pid: 351, tid: 351  %gt;%gt;%gt; /data/local/ndk-tests/crasher <<<
I/DEBUG   (   31): signal 11 (SIGSEGV), fault addr 0d9f00d8
I/DEBUG   (   31):  r0 0000af88  r1 0000a008  r2 baadf00d  r3 0d9f00d8
I/DEBUG   (   31):  r4 00000004  r5 0000a008  r6 0000af88  r7 00013c44
I/DEBUG   (   31):  r8 00000000  r9 00000000  10 00000000  fp 00000000
I/DEBUG   (   31):  ip 0000959c  sp be956cc8  lr 00008403  pc 0000841e  cpsr 60000030
I/DEBUG   (   31):          #00  pc 0000841e  /data/local/ndk-tests/crasher
I/DEBUG   (   31):          #01  pc 000083fe  /data/local/ndk-tests/crasher
I/DEBUG   (   31):          #02  pc 000083f6  /data/local/ndk-tests/crasher
I/DEBUG   (   31):          #03  pc 000191ac  /system/lib/libc.so
I/DEBUG   (   31):          #04  pc 000083ea  /data/local/ndk-tests/crasher
I/DEBUG   (   31):          #05  pc 00008458  /data/local/ndk-tests/crasher
I/DEBUG   (   31):          #06  pc 0000d362  /system/lib/libc.so
I/DEBUG   (   31):
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

看到这一堆输出,基本很无头绪。这时就要用到 ndk 中的 ndk-stack 了。

正文

想要搞懂本篇内容,需要了解或者熟悉的知识。

  • 熟悉ndk
  • 了解cocos2d-x使用ndk 生成so包的过程
  • 熟悉android开发中的logcat
  • 了解android系统

过程

简单说明和解释

在生成apk之前,会通过ndk的 ndk-build 命令将c/c++源码编译成一个libxxx.so命名的共享库,共享库是在 Linux系统中使用的。
工程中有一个jni的文件夹,这个是每个使用ndk编译都要有的目录,里面主要有两个文件 Android.mk Application.mk 和你的c++源码。具体要编译那个c++源码是在Android.mk配置的,编译的cpu类型,设置相关是在Application.mk中设置的。
当编译成功后,工程中会生成两个目录,一个是libs,一个是obj。
libs中就是最终生成的so文件,不同平台的会放到不同文件夹里,常见的如 armeabi-v7a,x86。
obj中是一堆.a库文件。我们需要的就是obj目录下的.a库文件。

操作过程

为了方便操作,可以把obj拷贝到一个独立目录里,比如在工程根目录新建一个debug目录,然后将obj文件夹拷贝进去。
接下来让程序崩溃一次,获取崩溃日志。这里拿cocos2d-x的Hello World工程做实验。最简便的方式是将HelloWorldScene.cpp 中读取的 HelloWorld图片名字改一下,比如将 HelloWorld.png 首字母小写改成 helloWorld.png。这个图位于工程根目录下的Resources中。如果你运行win32 的话,程序还是会正常运行,重新打包安装到真机上运行就会崩溃。这是因为Android中文件命名是区分大小写的。最终崩溃log如下。

12-02 06:08:08.638 1193-1193/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
12-02 06:08:08.643 1193-1193/? A/DEBUG: Build fingerprint: 'Android/sdk_google_phone_x86_64/generic_x86_64:6.0/MASTER/2872745:userdebug/test-keys'
12-02 06:08:08.643 1193-1193/? A/DEBUG: Revision: '0'
12-02 06:08:08.644 1193-1193/? A/DEBUG: ABI: 'x86'
12-02 06:08:08.644 1193-1193/? A/DEBUG: pid: 2886, tid: 2906, name: GLThread 37  >>> com.good.xb <<<
12-02 06:08:08.646 1193-1193/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
12-02 06:08:08.665 1193-1193/? A/DEBUG:     eax 00000000  ebx e3c92524  ecx 00000000  edx 00000001
12-02 06:08:08.666 1193-1193/? A/DEBUG:     esi e3c92524  edi e3426f60
12-02 06:08:08.666 1193-1193/? A/DEBUG:     xcs 00000023  xds 0000002b  xes 0000002b  xfs 0000005f  xss 0000002b
12-02 06:08:08.666 1193-1193/? A/DEBUG:     eip e33f7c35  ebp e2eb2378  esp e2eb2210  flags 00210286
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #00 pc 00346c35  /data/app/com.good.xb-2/lib/x86/libMyGame.so (HelloWorld::init()+1861)
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #01 pc 00346432  /data/app/com.good.xb-2/lib/x86/libMyGame.so (HelloWorld::create()+210)
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #02 pc 00346330  /data/app/com.good.xb-2/lib/x86/libMyGame.so (HelloWorld::createScene()+48)
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #03 pc 003461c1  /data/app/com.good.xb-2/lib/x86/libMyGame.so (AppDelegate::applicationDidFinishLaunching()+977)
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #04 pc 003494cf  /data/app/com.good.xb-2/lib/x86/libMyGame.so (cocos2d::Application::run()+47)
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #05 pc 003505f3  /data/app/com.good.xb-2/lib/x86/libMyGame.so (Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit+307)
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #06 pc 000b1e76  /data/app/com.good.xb-2/oat/x86/base.odex (offset 0x67000) (void org.cocos2dx.lib.Cocos2dxRenderer.nativeInit(int, int)+122)
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #07 pc 000b3244  /data/app/com.good.xb-2/oat/x86/base.odex (offset 0x67000) (void org.cocos2dx.lib.Cocos2dxRenderer.onSurfaceCreated(javax.microedition.khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig)+88)
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #08 pc 72989440  /data/dalvik-cache/x86/system@framework@boot.oat (offset 0x1eb2000)
12-02 06:08:08.706 1193-1193/? A/DEBUG:     #09 pc 000f6cb7  <unknown>
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

截图如下
这里写图片描述

我是用AndroidStudio中模拟器跑的程序。如果使用Eclipse的话,这段log应该是绿色的。仔细看这段log,会发现有两个特征

  1. Tag 名字是 DEBUG
  2. 以* * * * * * 开头

为了方便操作,将这段log复制到一个txt文本中。回到我们先前的debug目录,新建一个log.txt文本,将崩溃log复制进来,保存。
此时我们就有了查找bug的两个必备材料了,接下来就可以使用ndk-stack命令了。

ndk-stack 命令使用格式

$NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi
    
    
  • 1
$NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi -dump foo.txt
    
    
  • 1

解释一下:
$NDK代表ndk所在的路径。
$PROJECT_PATH 代表你工程路径
obj/local/armeabi 代表你编译的cpu类型,针对不同的cpu可以换成armeabi-v7a,x86等等。这些名字都可以在obj/local/下看到。

可以在命令行模式下执行ndk-stack命令。第一个命令是用于log在cmd面板的情况。即使用
adb logcat 命令启动命令行打印log的情况。这时使用ndk-stack 命令,可以将获取的结果显示到cmd面板。这个操作意义不大,忽略。
第二个命令是读取存有log的文本文件,这个是我们要使用的。

为了方便操作,可以将ndk的目录配置到path环境变量中,这样就可以在任意目录使用ndk-stack命令了。
P.S.在Path中配置ndk路径时,如果失败,可以在最后加一个斜线 \,比如我的 E:\android-ndk-r10c\

执行命令

因为我们会在obj所在的根目录执行 ndk-stack 命令,所以最后的命令要简单一些。
这里写图片描述

ndk-stack -sym obj/local/x86 -dump log.txt
    
    
  • 1

因为我的模拟器是x86的,所以我local下选的是x86 。
执行成功后就会看到一大串输出。这个看起来比较费劲,我们还是让他输出到一个文件吧。稍微修改一下。

ndk-stack -sym obj/local/x86 -dump log.txt >trace.txt
    
    
  • 1

这样执行成功后,就可以在当前目录下看到一个trace.txt,里面就是我们想要的内容。
这里写图片描述

从上往下看,注意出现的代码行数,像第一个 HelloWorldScene.cpp:71,就表示这个类的71行。查看源码,发现71行代码是

// position the sprite on the center of the screen
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    
    
  • 1
  • 2

在这里崩溃了,嗯,这个sprite可能是空指针,那为啥是空指针呢,巴拉巴拉,就是查找原因的节奏了,最后发现,居然是图片命名大小写的问题,扑街,谁命名的,拉出来,打一顿。。。。。。

至此基本可以定位程序哪里出错了。

最后懒人大法。每次写这命令多麻烦呀,还是写个批处理好了,把上面的命令写到一个批处理文件里,每次执行一下就ok。

注意点

  • 每个版本程序输出的崩溃log要和对应版本的obj一起使用,你不能拿其他版本的obj来解析崩溃log,否则会解析失败。也就是 obj -> so -> log,等同是一个版本的。

  • 如果嫌麻烦,也可以仔细观察那段崩溃信息。里面显示的内容和调用顺序是相反的。仔细看的话也大概可以确定那个方法出错了,只是不能确定是哪行代码。

其实每次android程序崩溃,系统都会将崩溃信息写入到一个文件。

09.387 1532-1549/system_process I/BootReceiver: Copying /data/tombstones/tombstone_00 to DropBox (SYSTEM_TOMBSTONE)
    
    
  • 1

看 log 输出,这有一个tombstones,翻译成中文就是墓碑,这下就懂了吧。
网上有研究tombstones的,请自行前去围观。

转载: 原文链接: https://blog.csdn.net/change_from_now/article/details/53436581
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个可能的Java实现: ```java import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; public class RentPlanGenerator { private static final double RENT_INCREASE_RATE = 0.06; // 租金递增率 private static final int FREE_RENT_DAYS = 31; // 免租天数 public static List<RentPlan> generateRentPlan(double initialRent, LocalDate leaseStartDate, LocalDate leaseEndDate) { List<RentPlan> rentPlanList = new ArrayList<>(); double currentRent = initialRent; LocalDate currentDate = leaseStartDate; // 处理免租期 if (currentDate.isBefore(leaseStartDate.plusDays(FREE_RENT_DAYS))) { currentDate = leaseStartDate.plusDays(FREE_RENT_DAYS); } while (currentDate.isBefore(leaseEndDate)) { LocalDate nextIncreaseDate = currentDate.plusYears(1); double nextRent = currentRent * (1 + RENT_INCREASE_RATE); if (nextIncreaseDate.isBefore(leaseStartDate.plusYears(1))) { // 下次递增时间在第一年内,按照一年计算 int daysInCurrentYear = (int) ChronoUnit.DAYS.between(currentDate, nextIncreaseDate); rentPlanList.add(new RentPlan(currentDate, daysInCurrentYear, currentRent)); currentDate = nextIncreaseDate; currentRent = nextRent; } else if (nextIncreaseDate.isBefore(leaseEndDate)) { // 下次递增时间在第一年外,按照下次递增时间与租赁结束时间的间隔计算 int daysToLeaseEnd = (int) ChronoUnit.DAYS.between(currentDate, leaseEndDate); rentPlanList.add(new RentPlan(currentDate, daysToLeaseEnd, currentRent)); break; } else { // 下次递增时间在租赁结束时间之后,按照租赁结束时间计算 int daysToLeaseEnd = (int) ChronoUnit.DAYS.between(currentDate, leaseEndDate); rentPlanList.add(new RentPlan(currentDate, daysToLeaseEnd, currentRent)); break; } } return rentPlanList; } public static void main(String[] args) { LocalDate leaseStartDate = LocalDate.of(2021, 3, 1); LocalDate leaseEndDate = LocalDate.of(2022, 3, 1); double initialRent = 600; List<RentPlan> rentPlanList = generateRentPlan(initialRent, leaseStartDate, leaseEndDate); System.out.printf("%-12s%-12s%-12s%n", "时间", "天数", "租金"); for (RentPlan rentPlan : rentPlanList) { System.out.printf("%-12s%-12d%-12.2f%n", rentPlan.getStartDate(), rentPlan.getDays(), rentPlan.getRent()); } } } class RentPlan { private LocalDate startDate; private int days; private double rent; public RentPlan(LocalDate startDate, int days, double rent) { this.startDate = startDate; this.days = days; this.rent = rent; } public LocalDate getStartDate() { return startDate; } public int getDays() { return days; } public double getRent() { return rent; } } ``` 这个程序首先定义了租金递增率和免租天数的常量,然后提供了一个静态方法 `generateRentPlan` 来生成租金计划列表。该方法接受三个参数:初始月租金、租赁开始时间和租赁结束时间。 具体实现时,我们使用循环来逐月生成租金计划。在每次循环中,我们首先计算下次递增租金的时间和金额。然后根据下次递增时间与租赁开始时间的间隔,决定本次循环处理的天数和租金金额。最后将这些信息保存到一个 `RentPlan` 对象中,并添加到租金计划列表中。 在主函数中,我们使用 `generateRentPlan` 方法生成租金计划列表,并以表格形式输出。输出结果如下: ``` 时间 天数 租金 2021-04-01 30 600.00 2021-05-01 31 636.00 2021-06-01 30 674.16 2021-07-01 31 713.57 2021-08-01 31 754.29 2021-09-01 30 796.39 2021-10-01 31 840.94 2021-11-01 30 887.02 2021-12-01 31 934.72 2022-01-01 31 984.12 2022-02-01 28 1035.30 ``` 可以看到,程序正确地根据递增周期和递增率生成了每个月的租金计划,并且考虑了免租期的影响。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值