作者归档:贺 利华

关于贺 利华

正在学习编程,享受编程 热爱文学,闲来读读《读库》 有思想,没理想 正在学会专注

Unity3D游戏在iOS上因为trampolines闪退的原因与解决办法

崩溃的情况

进入游戏一会儿,神马都不要做,双手离开手机,盯着屏幕看吧,游戏会定时从服务器那儿读取一些数据,时间一长,闪退了。尼玛问题是神马呢?完全没有头绪,不过大体猜测是因为网络请求导致的,那么好,先排查服务器返回结果是否有问题,最终确认每次客户端崩溃的时候,服务器都成功的返回了格式正确的数据,没有任何异常。那么可以确定问题是出在客户端部分了。 先检查代码,确认逻辑上没有任何问题之后,也倍感无力啊,问题依然在重现。肿么办呢?

确定具体原因

那么好吧,打一个测试版本再来看,然后再等着崩溃,查看崩溃日志吧,最终看到的崩溃日志中,崩溃线程输出信息如下:

Thread 27 Crashed:

0 libsystem_kernel.dylib 0x38e671fc __pthread_kill + 8

1 libsystem_pthread.dylib 0x38ecea4e pthread_kill + 54

2 libsystem_c.dylib 0x38e18028 abort + 72

3 gowonline 0x0178a0c0 mono_handle_native_sigsegv + 312

4 gowonline 0x01779a30 mono_sigsegv_signal_handler + 256

5 libsystem_platform.dylib 0x38ec9720 _sigtramp + 40

6 gowonline 0x00114f48 m_RestSharp_Http_ExecuteCallback_RestSharp_HttpResponse_System_Action_1_RestSharp_HttpResponse + 52

7 gowonline 0x001142b4 m_RestSharp_Http_RequestStreamCallback_System_IAsyncResult_System_Action_1_RestSharp_HttpResponse + 900

8 gowonline 0x00329c60 m_2be7 + 48

9 gowonline 0x00a39d08 m_System_Net_WebAsyncResult_DoCallback + 76

10 gowonline 0x00a29628 m_System_Net_HttpWebRequest_SetWriteStream_System_Net_WebConnectionStream + 536

11 gowonline 0x00a46f84 m_System_Net_WebConnection_InitConnection_object + 708

12 gowonline 0x0101ffac m_wrapper_runtime_invoke_object_runtime_invoke_dynamic_intptr_intptr_intptr_intptr + 200

13 gowonline 0x017792d4 mono_jit_runtime_invoke + 2152

14 gowonline 0x0181b324 mono_runtime_invoke + 132

15 gowonline 0x01820118 mono_runtime_invoke_array + 1448

16 gowonline 0x01820510 mono_message_invoke + 444

17 gowonline 0x018444a8 mono_async_invoke + 124

18 gowonline 0x01844174 async_invoke_thread + 312

19 gowonline 0x0184c580 start_wrapper + 496

20 gowonline 0x018695b4 thread_start_routine + 284

21 gowonline 0x01885750 GC_start_routine + 92

22 libsystem_pthread.dylib 0x38ecdc5a _pthread_body + 138

23 libsystem_pthread.dylib 0x38ecdbca _pthread_start + 98

好的,那么已经确定是在我们使用的一个第三方类库RestSharp中出现的问题,问题是出现在一个Action回调的地方。那么这种问题为什么会出现呢,那我们就得好好得来找找原因了。

关于如何查看iOS崩溃日志,让崩溃日志更加友好,我们可以参考这篇文章,iOS应用崩溃日志揭秘,主要就是要确保你的设备上跑着的这个App的编译和打包的二进制文件要在你用于查看日志的Mac上,这样的话,当我们查看崩溃日志的时候,Xcode会自动将那些无法阅读的函数调用的堆栈信息转化成可读性较强的日志信息,帮助还是很大的。

那么这个时候我们可以通过将设备连接到Mac上,直接通过Xcode将程序编译并运行,多尝试着玩一段时间,当程序再次出现崩溃的时候,我们就能看到更清楚的函数调用关系了,同时也能看到更多的日志提示。

最终能确定每次崩溃的函数就是这个mono_convert_imt_slot_to_vtable_slot,这个看上去就是Mono Runtime在将接口声明的方法指针指向实际实现这个接口的对方的方法,我们可以找到mono_convert_imt_slot_to_vtable_slot这个方法所在的文件查看一下,这个方法就在Mono项目的目录mono/mini/mini-trampolines.c中可以找到。

在Xcode中崩溃时,会输出类似” SIGABRT (ERROR:mini-trampolines.c:183:mono_convert_imt_slot_to_vtable_slot: code should not be reached) “的日志,看着很像是原本是要执行某个方法,但是不知道因为什么原因这个方法就无法访问到了,好奇葩啊。

解决方案

现在虽然已经知道了问题出现的地方,但是貌似完全看不明白的样子,尼玛trampoline都还是第一次听说耶,那么先请教一个我大Google吧,我们总是相信自己不是那第一个吃螃蟹的人,所以我们找到了一位大神的解决方案就在这里,大神的文章写得非常言简意赅,大体意思就是如果你在做Unity3D开发时,特别是在针对iOS和Android平台的时候,你很有可能会碰到比较杯具的就是程序会莫名其妙地闪退哦,不过不要着急,这个通常就是因为你的程序编译的时候给trampoline分配的空间太小,而你的程序中又大量使用了泛型、泛型方法调用和接口实现导致的。然后给出了具体的解决方法,那就是在Unity3D的编译选项Player Setting中有一个AOT Compilation Options条目,在这个选项条目中加上以下编译参数就好了

nrgctx-trampolines=8192,nimt-trampolines=8192,ntrampolines=4096

然后再重新一下,多多测试吧,骚年。关于这三个参数的意思呢,大神也给出了解释,分别如下:

  1. nrgctx-trampolines=8192 这是留给递归泛型使用的空间,默认是1024
  2. nimt-trampolines=8192 这是留给接口使用的空间,默认是128
  3. ntrampolines=4096 这是留给泛型方法调用使用的空间,默认是1024

Mono Runtime AOT机制剖析

虽然问题貌似已经得到解决了,而且我们貌似也搞清楚了具体原因就是因为默认Mono Runtime在AOT编译的时候给的trampoline配置太小,不适合我们这种设计优良,大量使用interface,设计绝对遵照OO思想的稍大一些的项目呢。那么我们以后是不是在做Unity3D开发的时候就尽量少用接口呢?是不是我们就尽量少用泛型和泛型方法呢?

既然这么感兴趣,想问个究竟,那么我们就来好好看看这个AOT到底是个神马东西吧,尼玛为什么就这么复杂,这么隐蔽,这么折腾人,《铁血战神》在App Store上线都5个月了有木有,尼玛这个问题碰到也不是一次两次了有木有,作为程序猿的我们被玩家吐槽了很多次,我们的客服XDJM们为我们背了多少黑锅啊,我勒个去啊。

首先,还是先搞定这个trampoline吧,毕竟问题的根源是在它身上的,那么我们就好好来看看这是个神马东西。我们找到Mono Runtime的官方文档中关于trampoline的描述来看看吧。

Trampolines are small, hand-written pieces of assembly code used to perform various tasks in the mono runtime. They are generated at runtime using the native code generation macros used by the JIT. They usually have a corresponding C function they can fall back to if they need to perform a more complicated task. They can be viewed as ways to pass control from JITted code back to the runtime.

翻译一下吧:

Trampoline是一些手写的非常短小的用来在mono运行时中执行很多操作的组件代码。主要是通过JIT使用到的本地代码宏在运行时动态生成的。它们通常都有与之相对应的C方法,在某些较为复杂的场景中,当trampoline无法胜任时,mono运行时就会将这些复杂的操作交回给这些对应的C方法来执行。这也可以看作是将JIT代码的执行权交回给runtime的一种方式。

好吧,貌似还没有太明白,那么这个Trampoline为什么会导致出现闪退的问题的,这看起来明显是为了提高mono runtime在执行C#代码时候的效率啊。

那么我们再来看看官方文档关于JIT Trampolines和AOT Trampolines的介绍吧,杯具的IMT Trampolines介绍还在//TODO状态中。

JIT Trampolines These trampolines are used to JIT compile a method the first time it is called. When the JIT compiles a call instruction, it doesn’t compile the called method right away. Instead, it creates a JIT trampoline, and emits a call instruction referencing the trampoline. When the trampoline is called, it calls mono_magic_trampoline () which compiles the target method, and returns the address of the compiled code to the trampoline which branches to it. This process is somewhat slow, so mono_magic_trampoline () tries to patch the calling JITted code so it calls the compiled code instead of the trampoline from now on. This is done by mono_arch_patch_callsite () in tramp-.c.

好吧,再翻译一下吧。

JIT Trampolines 这些Trampoline主要是JIT在首次调用某个方法的时候编译方法用的。当JIT在编译一个方法调用指令时,它并不会立刻就编译这个被调用到的方法。实际上,它会先创建一个JIT Trampoline,同时创建一个指向这个trampoline的调用指令。当这个JIT Trampoline在调用到的时候,它会再调用mono_magic_trampoline()方法来编译这个trampoline实际指向的目标方法,然后将编译后的方法的指针地址返回给这个指向它的trampoline。这个过程呢稍微有点慢,所以呢,mono_magic_trampoline()方法会优化调用JIT代码的过程,它会先尝试调用已经通过JIT编译过的方法而不是立即通过trampoline直接进行调用。这些都是通过在tramp-.c文件中的mono_patch_callsiete()方法来完成的。

这就是JIT Trampolines的机制,接下来我们看看AOT Trampolines又是怎么一回事呢。

AOT Trampolines

These are similar to the JIT trampolines but instead of receiving a MonoMethod to compile, they receive an image+token pair. If the method identified by this pair is also AOT compiled, the address of its compiled code can be obtained without loading the metadata for the method.

再翻译一下。

AOT Trampolines AOT Trampolines和JIT Trampolines非常相似,但是AOT Trampolines接受的编译参数不是一个Mono方法而是一个image+token对。如果传入的用于编译的image+token对所指向的方法已经经过AOT编译过了,那么再次编译这个image+token对时,就会直接返回这个已编译方法的指针地址而不需要再次加载这个方法的元数据进行再次编译了。

好吧,看了这么多关于Trampoline相关的内容,貌似只是了解到了非常有限的内容,那就依然是Trampolines存在的价值就是为了减少C#代码在mono runtime中运行时的性能损耗,提高C#代码的执行效率。

还有那个没有出场的IMT Trampolines应该也就是用于优化接口调用效率的小『蹦床』吧。

那么我们在开发Unity3D游戏的时候通常都会发布到iOS设备和Android设备上,而Unity3D在iOS和Android设备上的发布都选择了使用AOT编译机制来实现。那么显然我们碰到的Trampolines问题都是跟AOT Trampolines有关,那么AOT又是神马呢?

AOT就是区别于JIT(Just In Time)的另一个编译机制,全称是Ahead Of Time,就是预先编译好,而不是在代码执行到了某个方法再进行编译,这样的话会有一些好处。

通过查看Mono官方AOT介绍文档,使用AOT编译的有点有以下优点: 1. 加快程序启动速度 2. 更强的内存共享机制 3. 潜在的性能提升

当然也会有一些限制,例如支持平台的有限,支持AOT的Mono版本有限等等,具体信息可以参考Mono官方AOT介绍文档

那么回到我们最开始的问题,为什么我们的游戏就会出现崩溃呢?好吧,现在一点点回顾吧。

我们出现的问题是偶尔会出现闪退,根据崩溃日志我们能定位到是mono_convert_imt_slot_to_vtable_slot这个方法导致的,然后我们再通过Xcode跟踪到了是trampoline无法被访问到的问题。

那么这么高端大气上档次的问题是肿么出现的呢?貌似Mono还算是个不错的产品啊,还是很活跃的啊,也有专门的公司Xamarin在支撑着,怎么就会出现这种问提呢?

好吧,程序都是人写的,有问题也是很正常的。上面的分析已经很清楚了,大体的原因就是因为Mono在iOS/Android等移动设备上使用了AOT这种机制,为什么选择这种机制?原因非常简单,那就是可以针对特定平台编译成在平台优化的字节码,在资源比较紧缺的移动平台上还是有着明显优势的。而使用AOT编译就需要为Trampolines这些小东西留足足够的空间,当然这个肯定是硬编码的某个常数啦,在整个程序加载成功运行之后,该常数就成为了Trampolines运行时的配置。AOT默认编译时给Trampolines的参数有点低:

nrgctx-trampolines 默认为1024

nimt-trampolines 默认为128

ntrampolines 默认为1024

这对于小一些的项目可能是够用的,因为整体项目的结构不会太复杂,使用到的接口、泛型、递归相对也不会太多,但是对于一个稍大一些的项目来说,特别是采用了某些设计良好的第三方库的项目来说,这就比较纠结了。

其实我们在项目中就使用了两个第三方的库,一个是CodeTitan.JSon库,一个是RestSharp,分别用于JSON解析和HTTP请求处理,可是这两个库实在是设计得太好了,各种使用接口,各种抽象,没个两三天我都没法说完全理解了整个库的结构。

就是因为这些设计良好,完全遵循OOP原则,高度抽象的类库将Mono默认的Trampolines的配置耗尽了,所以捏,我们就把这个编译选项开大就好了,解决方案就是上面咱们提到的咯。

Unity3D游戏AppStore审核因为ASIdentifierManager被拒的解决办法

上周末我们的游戏『铁血战神』提交AppStore审核已经快一周了还木有结果,很是着急啊。周六上午终于有消息了,只可惜不是什么好消息,我们被拒了,被拒绝的原因很简单:

“You and Your Applications (and any third party with whom you have contracted to serve advertising) may use the Advertising Identifier, and any information obtained through the use of the Advertising Identifier, only for the purpose of serving advertising. If a user resets the Advertising Identifier, then You agree not to combine, correlate, link or otherwise associate, either directly or indirectly, the prior Advertising Identifier and any derived information with the reset Advertising Identifier.”

Note: iAd does not use the AdSupport framework, ASIdentifierManager, or the Advertising Identifier. Therefore they are not required for iAd implementations and should not be included in your app for iAd support.

Please check your code – including any third-party libraries – to remove any instances of:

class: ASIdentifierManager

selector: advertisingIdentifier

framework: AdSupport.framework

就是我们的游戏中没有任何广告,同时我们也不帮其他的应用做推广,那么好吧,苹果认为我们不应该使用这个专门为广告而设定的接口(真是尼玛没有道理可以讲啊,据说是为了打击积分墙等类似的推广手段,防止刷榜等等)。

那么好吧,先把我们自己使用到ASIdentifierManager处的代码全部干掉,那么这样是不是就可以重新打包然后再提交呢?

貌似不是滴,咱们还是遍历查找一下整个工程的代码吧,看看还有没有别的地方调用到了这个类的某些方法,最终你会发现,Unity3D编译出来的iOS工程中的部分iOS代码使也用到了这个接口,这部分代码在编译出来的工程下,Classes/Unity/DeviceSettings.mm文件中。

不得不说Unity3D的实现还是比较好的,最起码没有包含AdSupport.framework,没有直接引用头文件,也没有直接调用ASIdentifierManager这个类的advertisingIdentifier方法,而是通过动态的查找这个类库同时动态的找到方法再调用,不过貌似这样也木有办法绕过苹果的审核哦。最终还是会现形的,那么最终如何解决呢?

Unity3D的官方论坛上已经有了Unity3D的开发人员给出的Quick Fix了,大家自行前往查看即可,传送门在这里.

大体的思路还是将使用到的代码都删掉,不过为了避免以后每次重新编译Unity3D项目到Xcode工程时,都得手动修改一次,Unity3D的官方人员也提到最好还是一次性搞定Unity3D使用到的模板文件,这个文件就在/Applications/Unity/Unity.app/Contents/PlaybackEngines/iPhonePlayer/iPhone-Trampoline/Classes/下,找到DeviceSettings.mm文件,按照那个Unity3D官方的Quick Fix来修改,以后每次编译出来的新工程中这个文件就不需要再次手动修改了。

修改完了之后,重新打包测试提交,顺利通过审核,感谢所有TV啊。

要开始想些事情了

很多的时候,我们总以为还可以继续天真下去,可是现实会无情而直接地告诉你这不是你想要就能要,尼玛天真就留在那些可以天真的岁月吧。

是时候开始想些事情了,生活不会因为你天真而变得美好。

谈谈理想吧

离开校园快5年了,记得还在学校的时候,我们不时地会畅想一下未来,虽未必在谈理想,起码也算是年少时美好的憧憬吧。

曾经跟宿舍好友们,坐在学校旁边小吃街的小餐馆里头,喝着冰啤酒,吃着长沙火辣的家常菜,不少个夜晚,跟不同面孔的同学,纷纷表达过自己对未来的各种憧憬,也有对未来未知的一些恐慌。

如今,回头看看,还是蛮有意思的。曾经讨厌写程序的GM在毕业之后加入南方测绘公司某旗下子公司,在两年之内成为了研发部门的中坚力量,每日与代码厮磨。我们最可爱的班长–猪头,在成功完成了保送的硕士研究生学业之后,如愿以偿地回到了福建厦门,虽然离他回到老家罗源还有一点距离,但是也算是圆满了。我们的亢芬同学,更是在一年之内终于成功嫁给了一个好老公,成为了光荣的军属。赵姐和赵胖的爱情结晶更是健健康康地来到我们身边,虎头虎脑的甚是可爱。偶像思麦如愿以偿地成为了自己一心想成为的游戏开发者,不知到偶像现在是否依然热爱和享受着制作游戏的那份快感。老比终于打破了这四年来我们一直的疑惑,选择了离开GameLoft前往深圳再次创业去也。刘琪最终没能持续做游戏,与我在喜讯相伴两年之余后,离开加入了Tencent大家庭,工作之余从未放弃为我国GIS行业的互联网化做着实际的工作和思想上的探索。而我,依然还在喜讯这个集体中,辛苦并坚持着。

曾经很多次跟同学聊天,有大学的同学,也有高中的同学,甚至老家的发小,都曾经问过我类似的问题,你们怎么这么辛苦?都这么些年了,你图什么,得到了什么?

首先,我在喜讯这几年确实很辛苦,工作时长平均每周70个小时以上,这没有什么值得炫耀的,说句难听的话,这说起来都是泪啊。
其次,我在喜讯这几年,目前得到了所有我应该得到的,公司给予我的跟我为公司做的,我认为是对等的,甚至公司给予我的多于我目前所创造的,未来会更加平衡一些。
最后,我图什么,其实我也不是特别清楚自己图的是什么,只是这一路走来,自然而然就成了现在的状态。

回到标题,谈谈理想。

在我们曾经年轻的日子里(貌似我现在也还没老哦),我们都曾美好地憧憬过未来,寄予美妙的幻想。我也曾多次问过自己,我的理想是什么?回过头看自己这26年里,自己一直想做的事情,貌似除了写作还真没有什么其他的想法。

在自己还是个小孩子的时候,就曾经梦想自己有一天能著书立作给别人看,除了这个一直保存在内心的小愿望,还有就是有一颗较为强烈的成为有钱人的原始欲望的心。也许是因为从小,家中家境一直不算好,在老家父母二人就是普通的农民,只是父亲自己年轻时候比较愿意学,也确实学过几年木工,所以父亲在老家还算是有一技之长的木匠,家中有我和弟弟两个孩子,父母辛苦大半辈子还算顺利地让我俩都大学毕业了。但是家境并没有因为我和弟弟二人的毕业加入工作之后顿时好转起来,只是已经不再像之前那么捉襟见肘了。正是出于对现实生活很多不确定因素的恐慌,以及多年来看着父母因为物质条件从未能真正地享受属于他们自己的生活,即便如今,二老在家依然劳作,说是趁着自己还能干得动,需要给我们攒点钱,能给我们减轻一点负担算一点。当我每次听到父母这番话时,作为长子长孙的我,我只有一个想法,我要让这个家庭不再那么穷。穷是一个很搞笑的字,看起来就像一个人在天底下,很用力的干活。穷人的姿态也许就是这般吧,我的爷辈,父辈,包括我自己,也许都会一直在天底下以这种姿态长时间地存在着。

不过,我跟我父亲一样,我们相信只要自己努力付出,总会有所回报,我的父母通过自己勤劳的双手,将我跟我弟弟送入大学,而且从未让我和我弟弟在生活上感觉到非常的困窘,他们真的很了不起。我想我起码应该在我父母给我创造的基础上,延续我父母的成就吧,也就是确保我能给我的后辈一个更好些的基础。多么原始和简单的欲望,纯粹就是出于本能的欲望。但是,不可否认的是这个欲望支撑了我很久,直到现在这个依然是我愿意付出我所有努力来达成的首要目的。

作为小学生的我,初中生的我,高中生的我,大学生的我,从未想过自己将来会成为科学家,警察,军人等等,唯独曾经梦想过的就是成为一个可以写作给很多人看的人。如今作为一个程序员的我,过得也非常开心。我有一个非常理解我的女友,有一个非常支持我的家庭,有一份我觉得满意的工作,有一群我觉得可以托付的同伴,真的很难得。

理想很多的时候感觉像是奢侈品,可望而不可及。跟身边很多的人都聊过类似的问题,大部分人的第一反应是“哪儿来的理想啊?”,大家普遍都会谈到很多现实的问题,然后最终得出一个结论就是自己即便是有理想,那都是空想。其实从侧面来看,其实每个人都是有理想的,只是大家通常认为因为现实的各种压力,我们已经没有机会实现自己的理想了。当然我也不能免俗,我也经常这么认为,认为自己不可能实现理想,甚至都认为自己根本就没有理想。只是在随着社会大流,一步一步地走到今天,成为一个社会最基层的建设者,默默地在自己的岗位上发光发亮,兴许会被人发现,当做一颗璀璨明星,兴许就如此慢慢黯淡下来。

现实诚然如此,在成长的路上,我们看到了很多风景,总是梦想有一天自己也能如此享受此般美好,殊不知时间的美好就只有那么多,人人都想拥有,谈何容易。所以我们说要奋斗,只有奋斗才能改变自己的命运,才能让自己站到人生的塔尖,看到更多更美的风景。于是,就有了很多个奋斗之后享受成功喜悦的梦。我自己也有过这样的梦,很直接很直接的梦,梦想有一天自己所在的创业公司能上市,自己作为初创团队的一员,在公司上市之际直接晋升到塔尖。WTF,多美美妙的一刻啊,此时站在塔尖望着下面还在为我此时享受的这一刻奋斗的人们,多么的惬意啊。

成功变成了我们的梦,我们的理想呢?我想即便到了那一时刻,其实离我儿时唯一的理想还是很远,我完成了自己的一个期望也完成了一个作为家族长子所背负的期望。也许那个时候我也会很快乐,可是谁知道呢。

在喜讯创业已经3年有余了,成功的影子慢慢显现,但是能否最终成真没有人能说得清。只有靠自己的努力,靠一众兄弟姐妹们的努力,兴许可以成功。喜讯的成功是不是我的理想?我想在目前的阶段里,这肯定算是我的一个很重要的目标,但是远谈不上理想。创业成功对于有些人来说是理想,对于我而言是手段,这是我摆脱经济困境的唯一手段,我为此会付出所有我能付出的东西。获得成功能让自己获得经济自由,是否我就会开始寻找儿时的理想呢?其实我也不知道,儿时的理想如果真的有如此强烈的话,也许我现在已经开始朝着这个方向前进了,可是我没有。

所以有的时候我就在想,其实理想可能真的跟初恋很像。很刻骨铭心,但未必会是最终的相守,她永远会占据你内心的某个角落,偶尔还会跳出来给你带来一阵阵的瘙痒,让你阵阵地感怀。时间一过,所有一切如常。

也许我真的没有什么理想,有的还就是短时间之内的各种目标,结婚,生子,赚钱,养家,等等。坚持自己有一个理想,无非是想偶尔能让自己显得不是那么的俗不可耐,给自己脆弱而又自负的内心些许救赎性的安慰。告诉自己,这些丑陋的社会现实,这些完全不能理解我的人们,他们不知道我内心想要的是什么,我做不好这个事情其实是因为我的理想根本就不是做这个事情,我要做的是更美好更高尚的事情。多美好的理由啊,还是允许像我这般渺小的人类拥有如此可笑的理由吧。

放下那个远大而崇高的理想吧,回到地球上来,为现在能努力做到的事情多尽一份心,多出一点力,踏实走每一步路,也许哪天真的会跟那个理想再次重逢,就跟结婚多年以后与初恋重逢那种释然的感觉,两者相视而会心一笑。

来喜讯三年了

刚刚来喜讯的时候,老大跟我聊得很多,差不多每周都会有一次独立的对话,会聊的事情会比较宽泛,包括如何做产品,技术人员如何成长,跟设计人员如何配合,如何跟家人相处,如何做好一个朋友等等。其中经常出现的一个话题是关于个人职业规划与成长的,有一句话是这样的“三年一个坎,五年一个坎,十年一个坎”。大体的意思就是,当我们开始进入职业岗位上,三年时间会是一个阶段,五年又是一个阶段,十年呢又是另一个阶段。

我来喜讯已经三年了,再此之前我在超图待了一年的时间,也就是说我的三年这个坎是在喜讯度过的,如今已经是在喜讯的第四个年头了。

==== 时隔一周继续开始写 ====

在喜讯待的这三年里头,学到了很多的东西,技术上的大部分都是靠自己一点点摸索尝试,也有很多是跟同事们一起讨论争执中明白的,关于产品和项目的,大部分都是老大手把手教的,慢慢地开始需要承担一些项目管理和产品设计上的工作。

在这三年里头,做了几个项目,黄金矿工喜讯特别版,喜讯天天,画说,MARK,铁血战神。带了3位小兄弟,但是目前3位小兄弟均已离开了。目前项目中程序有4个客户端程序,1个服务端程序,客户端程序这边由我来推动,整个项目包括美术组与程序组这边的协调,策划和运营与程序这边的沟通,我也多少有些参与。

目前碰到了最大的问题就是我还没有学会如何在项目管理和程序开发中找到一个平衡点,在自己不愿意放弃程序开发的前提下,自己总会陷入到技术问题的解决和执行中,时常会忽略了项目中各种节点的确认和资源的协调,项目成员会出现工作量不平衡,经常出现大家在等某个资源而没有事情做,某些人又积压着过多的工作。

做个记录吧!