博文

目前显示的是标签为“开发”的博文

记一个奇怪的 android bug

莫名其妙摇身成为 android 开发是个挺神奇的事情,有机会的话以后再细说。 今天的问题是发现自己的应用在三星的 Galaxy S4(GT-I 9505)上行为诡异: 应用启动之后界面没显示出来,屏幕仍保持启动前的样子 通过 Toast 可以看得出来应用还是启动了的,而且可以操作,也就是说,除了界面没显示,应用是可以操作的 查了一圈 log 也没看到什么有价值的信息,最后只能祭出二分法排查。最后元凶是这么一行: getWindow().setFlags(FLAG_HOMEKEY_DISPATCHED); 这行是为了拦截 home 键加上的。无论怎么看都跟前述故障八杆子打不着。去掉之后,应用行为一切正常——幸亏我这个应用里拦截 home 键不是硬需求,可以等以后绕不开了再深入研究背后的故事。 这几天的感受,三星的 android 手机适配起来问题比 MTK 还要多,特记此备忘。

c/c++:忽略项目代码以外的 warning

很久以前就曾经想让团队在编译的时候都加上 -Werror,无奈一旦碰到项目以外的头文件里有 warning 就很头疼而不得不放弃。 直到今天遇上了一个比较罕见的情况。 同事 F 的代码用到了 protobuf。之前在某台机器上编译的一直没啥问题,今天换了台机器编译就挂了,而且是挂在 protobuf 的头文件里面。查来查去,发现除了 protobuf 之前是装在系统路径下面,现在是装在用户目录空间里之外,别无区别。 可是如果 protobuf 的头文件里真的有 warning,那无论如何都不应该被编译器放过啊。以 google 在业界神一般的存在,难道也会发布出有 warning 的 c++ sdk 么。 资料不多,不过所幸还真有。 google 的 protobuf 2.3 还真就是这个德性 。有的人自己 patch 了有瑕疵的头文件了事(这种事鄙厂也干过),还有人提到了本文的核心——gcc 的 -isystem 选项。 以前只知道一个 -I。而 -isystem 的作用和 -I 类似,但是会给附带的目录以系统路径待遇。系统路径有啥待遇?而这个待遇,主要就是扔掉各种 warning(标准答案在 这里 )。 目前看来,有了 -isystem,用 -Werror 培养团队良好的编程习惯应该是没障碍了。

换行 bug

今天同事求助调试一段 bash 脚本。脚本甚是简单,只有十余行,但是执行起来却是莫名其妙的错误。 bash --debug ./xxx.sh 调试之,竟然两处空行有错误。 以十六进制查看源码,空行处见 0d0a。遂于同事 UE 中(同事惯于 windows UE 编写,putty 调试运行)点选菜单,将 CRLF 统换成 LF。竟然问题依旧。 我自然是 UE 新人。同事又操作一遍 CRLF -> LF,运行,依旧。 此时以十六进制再看源码,空行处竟有 0a0d0a 这等混乱不堪。 祭起 emacs,paste 源码到新文件,执行,无误。 CRLF vs LF 已是老生常谈,调试此 bug 前后也只十余分钟。只是不知用 UE 是如何造出 0a0d0a 这等换行符了,阿弥陀佛。

i18n 折腾感受

这个年注定不好过。也好,可以有更多的时间写“无用”的代码和博客了。 去年 9 月 接手 giplet ,初衷只是想自己用的更方便一点。没想到竟得到些许反馈。或许是因为这辈子为了糊口写了扔了太多无用的代码,只是零星几条反馈就让我很感动,觉得自己应该再做点什么。想来想去,决定加上 i18n 支持,没想到就这么一点点想法也折腾了好几天。 主要问题还是对工具链不熟。 之前为了上手 autoconf/automake,就花了不少精力。好在这方面中文入门资料质量尚可(比如 IBM 的这一篇 就不错),遇到更多问题时摘 gnu 官方文档的相关章节细细咀嚼即可。只剩最后一个疑难杂症时,所幸及时读到了 m4 文档中的这一段,便悬崖勒马,很 dirty 很 ugly 地应付了事,至今未知其所以然。 Some people find m4 to be fairly addictive. They first use m4 for simple problems, then take bigger and bigger challenges, learning how to write complex sets of m4 macros along the way. Once really addicted, users pursue writing of sophisticated m4 applications even to solve simple problems, devoting more time debugging their m4 scripts than doing real work. Beware that m4 may be dangerous for the health of compulsive programmers. 如今引入 i18n,实际上也就是要熟悉另外一条开源工具链,具而言之就是 gettext 和 intltool。却未料这类资料如此之少。在代码中应用 gettext 尚有几篇中文,但如何使用 intltool 在一个 autoconf/automake 的项目中加入 i18n 支持竟连英文都鲜可搜到。这里 推荐一篇 ,以期节省大家时间。其实这 I18N-HOWTO 本是 intltool 自己的文档,理应已在灯火阑珊处,只是不知为何 arc...

eclipse 的 svn 支持怎么这么差

毕业之前一直用的是 windows 平台,那时候除了写 delphi,拿 eclipse 写 java 也是很爽的事情。工作以来一直是 c++,主要平台是 linux,离开 eclipse 一晃就是两年多。两年的时间可以改变很多东西,delphi 已经物是人非,令人唏嘘不已了。 最近有机会重拾 java,才发现 linux 平台上 eclipse 对 svn 支持的是如此之差。有人或许会说,subclipse 不是很好用吗,装个插件就可以了啊——一开始我也是这么想的。 subclipse 的 主页 上,最新的版本是 1.4.x。这个版本的 subclipse 取消了 SVNKit 接口,只剩下了 JavaHL。而后者在 linux 平台上通常都是一个默认无效的选项,要使其生效需要手动安装 JavaHL 库(参见 subclipse 的官方 FAQ )。 然而,获得这个库并非很简单的事情。debian 系的可以直接加第三方源,RPM 系的可以拿搜到的 RPM 包碰碰运气,其他发行版的估计就得自行编译了。还好 FAQ 那页接下来就是一系列的编译、安装步骤,看起来真是够麻烦。 无意间搜到了 subversive ,号称是 eclipse 官方的 svn 支持插件。尝试了一下,堪称痛苦。网站提供的 update url 装倒是能装上,装上了却不能用。仔细扫一眼,原来还以来其他插件: Important: In order to start work with the Subversive you should install SVN Connectors distributed from external location. Such scheme of distribution caused by licensing requirements. 这个 “其他插件”的页面 或许就是 subversive 进入 eclipse 前的老东家吧,其 svn connector 被可耻的标成了“optional”,晕。尝试装这个 optional 的 svn connector,提示又缺依赖插件若干…… 这里给出一个能够快速解决问题的变通办法:安装 subclipse 1.2.x。即,将 subclipse 官网提供的 update url 改成 http://subclipse....

又犯了低级错误——这次是 vector

下午大部分时间都在调一段 c++ 代码。某线程共享 vector,用于存放对象的指针。创建对象之前先要检查一下是否已经存在符合某条件的对象,有则取消创建。对象创建时指针进入 vector,对象销毁时指针从 vector 中删除。 逻辑虽然简单,但是因为需要线程安全,所以加锁解锁也是步步小心。 但是跑起来之后还是出了问题。有时候明明 vector 应该已经空了,检查函数竟然返回存在的结果,导致某该被创建的对象创建失败。 根据日志输出,进行这个对象的创建前检查正巧位于某线程将某对象刚刚从 vector 中删除的时候,而检查出已存在的结果恰好是那个刚刚被删掉的指针,于是本能的怀疑是线程同步出了问题。 集中精神,走查代码(注意力都在加解锁上,这个过程耗时比较长)。一无所获。无奈,在日志输出中将每次 vector 操作时 vector 的状态进行了详细输出 。 令人瞠目的一幕出现了,vector 的 size 居然就从没有减少过! 赶紧检查 destructor,是这样写的: ... LockMutex(...) remove(foo_vector.begin(), foo_vector.end(), this); UnlockMutex(...) ... 如果你也看不出不对,那么恭喜,你的 c++ 水平和我一样不济。不用多解释,看 文档 ,好好补习一下 remove 到底是怎么用的。这个地方用 remove 其实并不合适。 将上述代码改成如下形式,问题解决。 ... LockMutex(...) vector ::iterator it=find(foo_vector.begin(), foo_vector.end(), this); if (it != foo_vector.end()) foo_vector.erase(it); UnlockMutex(...) ... 感受: 1、此例跟多线程一点关系也没有。“程序员的第一直觉几乎总是错的”说的就是这个。 2、“用非母语交谈,思维易受干扰”,用不熟悉的语言编程,也是一样。 3、不折不扣的 printf debug 流。 4、c++ 还是半吊子。

去掉 emacs 里面的 ^M

这是 \r\n 和 \n 之间的故事。故事很古老,有兴趣的可以翻墙啃啃 wikipedia 上的 这篇文章 。 emacs 里面打开 windows 的文本,每行行尾都会显示一个 ^M,有伤大雅,看着别扭,影响思维等等罪名不一而足。如果这个事情和 emacs 无关,linux 下面专门有工具干这个事情,叫 dos2unix。 既然已经用 emacs 打开了,就懒得外部处理。可是 emacs 似乎没有专门为此设置的 function。 Google 搜出 某邮件列表里的一封信 ,试了一下可用,于是写此短文以方便只看中文的懒人 :P M-x replace-string C-q C-m RET

svn 引发的一系列麻烦

零、前言 先简单介绍一下问题发生的环境。手头有很多测试机,windows/linux/solaris 俱全。在为了方便协同工作,拿其中一台 solaris 测试机设置了共享目录。这样 linux 机器可以直接 sshfs 挂载;windows 机器可以用 samba 挂成虚拟驱动器。这些测试机本来都是用公司公共 nfs 服务的 ,后来测试环境隔离出来之后,就搭成了这个样子。原因也没别的,solaris 不熟,用起来很不顺手,也没有现成的 sshfs 和 smbfs 可用,干脆就把共享目录扔在 solaris 机器上了,本地目录访问起来不需要任何配置。 一、不稳定的 sshfs 最近做 svn 更新(当然是在其他机器上对 sshfs 挂载过来的目录进行更新,solaris 那么难用),居然两次令 sshfs 失去了响应。看来这 sshfs 还是不够稳定(感觉还没有以前的 nfs 稳定)。而且失去响应了之后,umount 不掉,只得重启机器,很是麻烦。看来高强度 IO 的时候还是不要用 sshfs 共享的办法,不要给自己找麻烦。 二、svn: Can't convert string from 'UTF-8' to native encoding 于是转而 ssh 到 solaris 上作本地 svn up。更新到一半,就出了标题上的那个错误。看了看,是个名字带日文的文件。上网搜上述错误信息,答案很清楚,执行 svn co 环境的 locale 无法表达某些字符。 解决办法倒是不难。如 linux/solaris 这样的系统,可以用 locale 命令看当前 locale 设置——这 solaris 居然一直是光秃秃的 zh,之前一直都没注意过,汗——赶紧把下面这一行扔到了 .bashrc 里面 declare -x LANG=zh_CN.UTF-8 三、还是 svn: Can't convert string from 'UTF-8' to native encoding 终于可以继续了。只是这 solaris 在诸多测试机中配置最老,硬盘也最小。更新未完居然磁盘空间不足。看了一下,已经 8.4G 的数据了,难怪 sshfs 吃不消。反正这次要更新的项目跟 linux/solaris 也没什么关系,于是准备把这个 svn...

模板定义和实现不能分开

虽然项目需要,写了半年多的 c++,但是之前一直也没接触过,属于边学边用,很不系统。最近才发现这个可能算是很基本的常识。 如题,类似这样的写法是不行的: foo.h template std::string to_string(T value); foo.cpp template std::string to_string(T value) {     std::stringstream result;     result     return result.str(); } 这样做之所以不行,是因为必须要让编译器能够 在同一文件中找到模板函数的实现部分 ,否则会连接错误。 至于原因的细节有很多书或者讨论结果可供参考,这里不再赘述。 解决办法,比较常见的是将所有涉及模板的实现都写在头文件里。据说其他使用到模板函数的文件中将 include 头文件改成直接 include .cpp 文件也是一种办法,我没有作实验,但感觉还不如前者妥当。 c++ 果然是种易学难用的语言,林林总总的历史问题,造成了太过复杂的语法体系。写 c++ 代码,如履薄冰。

J2ME 开发上手指南

概述 J2ME 是 Java 2 Micro Edition 的缩写,最新的官方名称是 JavaME(Sun 似乎很喜欢改名字,从 Oak 到 Java,从 Java 到 J2?E,再到现在 Java?E),可 以认为是 Java 为移动设备(手机、PDA 以及其它计算能力和能源供应都受限的设备)剪裁的一套 API。基本上,如果有 J2SE 的开发经验,上手 J2ME 会非常快。除了个别类或方法,J2ME 基本上是 J2SE 的一个子集。Java 在保持语言体验统一性这一点上,做得确实非常好。 J2ME 采用比较混乱的方式来描述自身的版本。整个 J2ME API 被划分为 Configuration 和 Profile。而 Configuration 和 Profile 又各自拥有其版本。关于混乱现象的解释,一两句话难以说清,有兴趣的同学可以随便抓本 J2ME 的书过来,第一章必然有大篇文字解释这些匪夷所思的现象[1]。 J2ME 最吸引人的地方(或者说是吸引我的地方),就在于其针对的平台计算能力有限。这并不是受虐。运算速度、可用内存、以及最终生成字 节码尺寸的限制使得 J2ME 应用通常比较小巧玲珑。以早期支持 J2ME 的设备为例,可用的 Heap不过 200k,最终生成的代码(包含各种资源文件,如图片)不得超过 64k,这就使得面向这种平台开发的 J2ME 应用规模基本上不会超过一个人的能力范围。这样可以有效避免协作、过程等等令人不胜其烦的软件工程概念的引入,从而使开发人员重新回归到编写代码的乐趣中去。 开发环境 抛开感情因素,Windows 是进行 J2ME 开发的首选平台。为什么呢? 首先,开发 J2ME 所必须的开发包,只有 Sun 官方的 WTK(Wireless ToolKit)[2]对 Linux 提供了良好的支持。其它如 Nokia,与 Windows 版本的更新速度来看,其 Linux 版本更新相当慢且陈旧,而其它如 SonyEricsson[3] 和 Motorola[4] 则根本没有 Linux 版本的开发包。 其次,数据线。J2ME 的开发是离不开真机测试的。模拟器上在完美的代码到了真机上还是有可能运行得一塌糊涂。因此开发人员应该要有一个比较便捷的将 J2ME 部署到手机的途径。这些途径当中,数据线显然首选。而众多的数据线中,提...

不要把非项目文件扔在workspace里面

workspace,当然就是eclipse的工作区。之前一直都这么做的,资源、其它脚本,统统都在workspace里面,尽管其中相当一部分根本不受eclipse管理。 一次偶然的机会,eclipse在build workspace的时候(这种操作eclipse会在很多情况下进行,比如refresh某个项目的时候)死掉了。进度永远是0%,而且无法正常退出,只能强制杀掉进程。 最开始以为是windows几天没关,又犯病了,重启之后,故障依旧。 于是留意eclipse在build workspace时候给出的提示。原来是说几个非项目的文件夹 的路径和eclipse记录的不符。eclipse还善意的提示我将这几个“项目”按正确路径重新导入即可…… 一不做二不休,把几个非项目文件夹请出了workspace。重启eclipse,故障解决。 看来,还是不要太相信IDE!!

手机 Java 之怪现象

ok,我承认题目是用来吸引眼球的。但是,不能不承认的是,这篇文章会很有用,很有用……尽管目前可能只是对我有用……因为我记性不够好…… 下面记载的都是手机 java 实现中各种奇怪的毛病,bug,或者……特性,是根据某项目的开发经验总结出来的。但是涵盖的手机型号还是有限。因此很有可能某些“特性”会存在于更多的采用了相同 JVM(比如平台相同、生产厂商)的手机上。 == 早期 S60 的内存泄漏 == 这个 bug 可以上溯至 2003 年,甚至更早。表现为 java 应用中如果使用了 Class.getResourceAsStream("本地文件") 无法释放其占用的内存,是的,没有任何办法,无论是调用获得的的 InputStream 实例的 close() 或将其设为 null,甚至显式强制 System.gc(),都没有效果。结果就是至少和本地文件同尺寸的内存成为了无法回收的垃圾。这个问题还影响到以 Class.getResourceAsStream() 为基础的 Image.createImage()(这个是最要命的,如何能够不使用图片资源呢!)。 这个 bug 据说在新的 S60 上已经解决了。但是 Nokia 3230(4.0526.2ch)、Nokia 7610(6.0525.0ch)都存在这个问题。对于这些个有问题的机型,在 java 程序中是无法完美解决这个问题的,只能尽量避免。比如集中、统一载入资源,永不释放(也就是说,尽量控制泄漏的次数)。当然,这会对已有代码造成很大影响。毕竟手机 java 应用是内存受限系统的典型,大多数情况下,珍贵的内存中应该只保留需要的资源。 == 键盘响应事件 == 在 MIDP1 中,获取键盘事件只能自己实现 Canvas.keyPressed()。但是 Motorola E398 和 SonyEricsson K700c 的实现却很奇怪。表现为左右软键有可能在这个方法中捕获不到。而是否能够成功捕获,取决于 keyPressed() 方法中代码的行数…… 我承认我没彻底搞清楚这其中的玄机。鬼知道 Motorola 和 SonyEricsson 是怎么实现的 JVM。我只知道把 keyPressed 中的所有代码提取到另外一个函数中,在 keyPressed 只把参数传递给新函数,问题就消失了…… =...