五月的最后一个周末,和同事一起组队参加了我司举办的首届黑客马拉松大赛,经过几十个小时的奋战,我和队友们一起完成度较高地准备好了演示 Demo、ppt 等,在大家一致看好、觉得在首轮评审表现出色的情况下止步首轮,说不遗憾肯定是假的,想想和小伙伴们为了完成 Demo 的通宵达旦,四个人短时间内提交的上万行代码,三十多个小时不曾睡觉的情况下,听到评审结果宣判的那一刻,倦意趁我的一丝失落情绪铺天盖地地袭来。 时间仍然欢快地往前流淌,我在沉睡十多个小时后也满血复活,过去的就让它过去吧,收拾收拾心情,总结总结经验教训,继续前行。 比赛中有幸被小伙伴们推举为我们小组的 CTO,作为需要在技术方面掌控全局的角色,回想一下自己的表现,算是中规中矩,有亮点,也暴露了很多问题。 先臭屁一下自己的优点: 担负起了技术选择、模块划分、任务分配和接口制定等方面的工作。 认真负责,尽自己的努力试图去掌控开发进度、完成我们的 Demo。 在态度上应该算无可挑剔,对可能影响进度和体验的 Bug 从不轻易放过。 花了一些时间封装了一些使用到的基础类库,让小伙伴们用得舒心,写得放心。 然后说说自己暴露的问题: 模块划分与 UML 表达。 平时工作过程中较多地画一些小流程与逻辑的流程图,但是对整个 APP 的模块划分未涉足过,临时抱了一下佛脚之后做了一个类 MVC 的分层设计,自己感觉并不专业,用于指导开发也并不科学。 对此我倒没什么好找借口的,就是对此的理论基础太薄弱,后续需要大量的充电补全,自己平时做一些东西的过程中也最好将前期的总体设计工作重视起来,多实践。 任务分配。 这一点是否做好取决于上一点,模块划分不合理将会直接导致任务分配出现问题。比如本次的任务安排,早期版本出现了这样的情况: 人员 任务1 任务2 任务3 任务4 成员1 第一阶段 UI 第二阶段 UI 第三阶段 UI 第三阶段 逻辑 成员2 空闲 第一阶段 逻辑 继续 继续 成员3 空闲 空闲 第二阶段逻辑 继续 因为对 UI 的工作量评估不足,觉得由一个人来快速完成就行,但是实际上 UI 开发后来成为了明显的瓶颈,持续了整个开发周期。其它两个人有较长的时间处于等待 UI 出来之后才能开始往工程里接入逻辑的状态,浪费了大量的劳动力。 后来对此进行了一番思考,觉得相对来说这样可能会更好一点: 各人负责一段逻辑流程或者功能模块,顺带简单把对应的界面先搭起来,后来如果需要美化,可以仍然分工给各人,或者由一个相对工作量不那么紧的人统一来调整和美化。 前期做工作分配时需要有意识地提炼出不严格需要前置条件的任务,松耦合的任务,合理分配。比如可以先将任务做成粗细两种粒度的列表,然后理清它们的前置依赖关系,找出适合打包分给同一个人的集合。 对工期的评估结合我的评估和对应的负责人的评估来做。 当然这里头显然大有学问,后续还是要针对性地学习一些相关的知识,有理论支撑做起来才更科学更有底气。 量化的缺失。 在试图掌控进度的过程中,有较长的时间里对剩下多少活只能有个模糊印象,而不能明确地评估剩下百分之多少的工作量,究其原因,就是对工作任务的细分量化不够,若能做到以下两点想必会更好: 将模块细分为可以明确评估工作量的子任务。 定期沟通了解小伙伴的进展,更新任务完成情况列表。 总之,离 CTO 的水平还很遥远啊~骚年需努力!
工作远非简单的完成任务而已,如何成为一名合格的员工?如何成为一名令下属信服的领导?朋友圈里的鸡汤似乎经常试图告诉我答案,但是在这个看见鸡汤文就神烦的年代,还是自己稍作总结来得更靠谱一点。 合格员工 如何成为一名合格的员工? 面对这样一个问题,很多人的答案可能是「干好手上的活呗」,此话固然不错,但是如何干好,相信不同的人心里有不同的原则与好建议。 我的版本是: 不要只是提出问题,带着思考后的方案去。 对自己跟进的任务要随时心里有数,比如当前进度,预计完成时间,关键数据等。 敢于行权。一些在自己能力和职责范围内的事情,主动推动解决,将重要节点和结果向领导汇报即可。 抬高眼光层次,在自己的思考之外,尝试以你的直接领导的眼光来分析和解决问题。 图生存之外,还要谋发展。不能满足于现状,要有过上更好的生活的追求,同时要有居安思危的意识。 称职领导 作为一名长期处于基层的员工,对此当然并无实际经验,下面的内容只是根据自己的「以为」想当然胡绉。 当领导,很重要的两点能力就是看事与看人。 看事,是指能对事情进行比较准确的评估:复杂程度、需要的资源、合适的处理人选等;看人,是指对下属解决该问题的能力的把握。 在我心目中称职领导的版本: 有与职级相匹配的看事与看人的能力。 对外,要护犊子,事后再内部总结。 要懂得信任与放权。不用事必亲躬,找合适的人去做合适的事,把握一些重要节点和最终结果就行。 良好的期望管理。对下属的升职加薪等等诉求心里要有个预期,不纵容不合理的要求,但是对应该帮忙争取的利益决不犹豫。 一些细节: 公司内部的评优与奖励名额努力替团队成员争取。 公司内部的活动带领团队积极参与。 最后 啊哈,以上纯属瞎扯,有无异议,都欢迎探讨。
问题描述 使用 TortoiseSVN 从 GitHub 仓库 Update 时,弹出错误提示对话框: --------------------------- Subversion Exception! --------------------------- Subversion encountered a serious problem. Please take the time to report this on the Subversion mailing list with as much information as possible about what you were trying to do. But please first search the mailing list archives for the error message to avoid reporting the same problem repeatedly. You can find the mailing list archives at http://subversion.apache.org/mailing-lists.html Subversion reported the following (you can copy the content of this dialog to the clipboard using Ctrl-C): In file 'D:\Development\SVN\Releases\TortoiseSVN-1.8.11\ext\subversion\subversion\libsvn_wc\update_editor.c' line 1550: assertion failed (action == svn_wc_conflict_action_delete) --------------------------- 确定 --------------------------- 推测发生原因可能是 GitHub 认为本地的目录结构与服务器冲突,因为能看到本地之前 Checkout 出来的一个文件夹显示未纳入版本控制,但是实际上服务器上这个文件夹一直存在在。 查到在一个 邮件列表 里说这是由 GitHub 的实现有点问题导致,但是,也如其它地方能查到的信息一样,并没有给出解决方案。 经过各种尝试,包括 Clean up Clean up 本身能成功,但是再 Update 依然报错。 在一个新的文件夹 Checkout 没有问题。 删除可疑文件和文件夹,重新 Update。 依然报错。 无奈之下试了一下命令行,找到了解决办法。 解决方案 其实很简单,就是使用命令行 svn cleanup svn update 就可以了,再使用 GUI 的 Update 就不会报错了。 为啥就好了仍然原因不明。 最新情况 后来又出现了一次这样的情况,在命令行 svn update 也不好使了,提示 svn: E155010: The node 'a/folder/path/' was not found. 忍无可忍,切回使用 git。
小菜鸟才学习 Java 没多久,这天要写一个存储长整形的列表,于是这样写: List<long> listData = new ArrayList<long>(); 这时 Android Studio 不高兴了,在 long 下面打上红色波浪线,然后提示小菜鸟: Type argument cannot be of primitive type 赶紧进 List 的定义看了一下,发现类型参数必须是引用类型,不能用原始数值类型。 于是就改为: List<Long> listData = new ArrayList<Long>(); 这时 Android Studio 不说什么了,于是小菜鸟很开心地继续往下写,往 listData 里添加了一些 long 类型的值,并且给它们排了序,如果发现它们中有相邻并且不相等的元素后执行一些操作: int size = listData.size(); for (int i = 1; i < size; i++) { if (listData.get(i - 1) != listData.get(i)) { // do something } } 这时 Android Studio 貌似又不高兴了,在 != 上加上黄底色,指上去一看显示: Number objects are compared using '!=', not 'equals()' 小菜鸟不高兴了,我比较两个 long 类型都非得用方法,不能用操作符了吗?(他脑子里的定势一直以为 List 的类型参数还是 long 呢),Java 就是比 C++ 矫情。想想 IDE 这里只是警告,并不是错误,所以也不加理会继续完成他的代码去了。 但是到后来怎么运行结果都不太对,明明给 List 里添加的元素里有相等的,有些情况下应该不进入 if 才对,可是却每次比较完都进了 if。百思不得其解之后想起了 Android Studio 的警告,然后把 != 改成 !list.get(i -1).equals(listData.get(i)),立马就好了。 小菜鸟惭愧极了,基础不牢靠啊,赶紧翻出自己的 Java 入门书对应的章节看了一下,看完才恍然大悟,原来 Java 里的比较运算符里还有这么多小细节呢……不是把 C++ 里的经验直接照搬过来就行了的。 Java 比较运算符里的一些细节 >、>=、< 和 <= 只支持两边操作数都是数值类型。 == 和 != 两边的操作数可以都是数值类型,也可以都是引用类型,但必须是同一个类的实例。 当 obj1 和 obj2 引用同一个对象时,则 obj1 == obj2,否则 obj1 != obj2。 每种数值类型都有对应的包装类,比如 long 的包装类 Long。包装类的实例可以与数值型的值比较,是直接取出包装类实例所包装的数值来比较的。 涉及自动装箱后情况复杂了一些,比如 Integer ina = 18; Integer inb = 18; Integer inc = 188; Integer ind = 188; 这时 ina == inb 成立,而 inc == ind 不成立。 原因是在 java.lang.Integer 类里,对 -128~127 之间的整数自动装箱成 Integer 实例,并且缓存了起来,所以对此范围内的整数自动装箱时,实际上都指向了缓存里的对象,所以会出现上面的情况。 与此类似的是 String 类型也会对诸如 String stra = "Hello"; 这样的直接赋值创建的实例进行缓存。 最佳实践 引用类型实例之间,除非想要知道是否是引用同一个对象,否则它们之间的比较,总是使用 equals() 方法。 参考 《疯狂 Java 讲义》——李刚著 第 3.7.5 节 比较运算符。
作为一名在 Windows 下使用了多年 gVim 的少年,已然把它在我需要的地方都收拾得服服贴贴,可以说 Vim 经过配置配置,上得厅堂下得厨房,基本能满足我的所有幻想。 直到那天突然产生了新的需求——Lisp。我工作中倒并没有用得到 Lisp 的地方,但是最近眼前晃过的一些书,比如《计算机程序的构造和解释》、《码农》杂志第 13 期,都对这门古老的语言推崇备至,还有垠神也撰文《Lisp 已死,Lisp 万岁!》历数现代 Lisp 方言的先进性,再者我也一直有学习一门函数式编程语言的想法,看起来,Lisp 是不二之选。但是在用 Vim 配置 Lisp 开发环境时遇到些问题,虽然有 Slimv,可用起来还是感觉各种不便。 在搜索网友们对此的经验之谈的时候,自然而然地,目光逐渐聚集到 Emacs 这个使用 Emacs Lisp 作为扩展语言的神的编辑器上。作为一名 Vimer,对 Emacs 不是没动过心,也曾尝试着使用过两回,最大的印象就是快捷键相比 Vim 实在是太难按了。没有需求就没有驱动力,这回貌似有了,Let’s 折腾起。 给自己的忠告: 锤子再好,再牛逼,也只是个锤子,而不应该成为工匠的心魔。(from 知乎) 目录 文本编辑环境——Evil Common Lisp 开发环境 Python 开发环境 文件名、Buffer 和命令的渐进提示 Markdown 编辑环境 C/S 模式快速启动 Emacs 平滑滚动 文本编辑环境——Evil 从 Vim 转到 Emacs 最大的不适应就是以前那些用得飞起的编辑模式和简洁熟悉的快捷键不好使了,需要切换到频繁的 Ctrl+ Alt+ 各种组合键去,以前就听说过有 Evil 这么个东西,试了一下发现这货还真是强大,常用的编辑模式、快捷键、命令,甚至宏和正则表达式等等操作都模拟得很好,跟在 gVim 里感觉一模一样,平移编辑习惯的坡度一下没了。 安装 Evil。 M-x list-packages <RET> 找到 Evil 并安装。 在 ~/.emacs 文件里加上: (require 'evil) (evil-mode t) 这样就能在大部分地方(除了 eshell 等外)默认进入 Evil 模式使用 Vim 的编辑习惯了。 Common Lisp 开发环境 学习 Common Lisp 是使用 Emacs 最主要的任务,配好了文本编辑,当然得先把 Common Lisp 开发环境配置好了。 本来什么也不用配置的情况下 M-x ielm 就能进入一个 Emacs Lisp 的 REPL,但是它与 Common Lisp 毕竟还是有区别,所以还是另配置一个。 我使用网友们力荐的 Emacs + slime + sbcl 的组合,配置步骤如下: 去 http://www.sbcl.org/ 下载安装 sbcl 的最新版。 在 Windows 下推荐安装到默认路径。我尝试过更改路径安装,比如安装到 D 盘,但是运行时会提示在 C 盘的某个路径下找不到 sbcl.core 文件。 Update: 后来经验证,发现实际上是 sbcl 在安装过程中写的环境变量没有生效导致的,sbcl 在安装过程中会设置两个环境变量,新建一个 SBCL_HOME 值为 sbcl 安装路径,在 PATH 中添加 sbcl 安装路径。这两个变量必须是生效的(即任意开启 CMD 运行 sbcl 命令能正常进入 REPL),不然到安装目录下 CMD 运行 sbcl 会提示 can't find core file at C:Program Files (x86)/sbcl/lib/sbcl//sbcl.core 而此时即使完成了后面的步骤,在 Emacs 中运行 slime 会提示 apply: Searching for program: no such file or directory, sbcl 安装 slime 和 slime-company 插件。 M-x list-packages <RET> 找到 slime 和 slime-company 并安装。 在 ~/.emacs 文件里加上: (setq inferior-lisp-program "sbcl") (require 'slime-autoloads) (slime-setup '(slime-fancy)) (slime-setup '(slime-company)) M-x slime 就可以进入到 REPL 进行 Common Lisp 的学习了。 Python 开发环境 虽然是一名很业余的 Python 选手,但是既然切了编辑器,自然也得在里面配好 Python 的开发环境。在网上找到一段简洁有效的配置步骤: 安装 virtualenv 和 jedi 插件。 pip install virtualenv M-x package-install jedi M-x package-install exec-path-from-shell restart emacs M-x exec-path-from-shell-initialize M-x jedi:install-server 在 ~/.emacs 文件里添加: (require 'jedi) (autoload 'jedi:setup "jedi" nil t) (setq jedi:setup-keys t) (add-hook 'python-mode-hook 'jedi:setup) (setq jedi:complete-on-dot t) 这样自动补全之类的就没有问题了。 然后在如何运行当前 py 文件这件事上遇到些问题。在 Vim 中我一直是用 :!python % 来运行的,但是在 Emacs 里,包括 Evil、eshell、shell 里,如果有等待用户输入的语句如 input 或 raw_input 等,因为 Emacs 只重定向了输出,无法重定向输入,会提示如下错误: EOFError: EOF when reading a line 在网上寻觅良久未果后找到两种方法(其实都是利用 start 命令): 在 Evil 里 :!start python % 使用 Emacs 的运行外部命令的方法 M-! start python test.py 文件名、Buffer 和命令的渐进提示 编程环境下需要自动补全,在非编程环境下,比如切换 Buffer,打开文件,输入函数命令等,同样需要。 使用 ido-mode 和 projectile 自动定位/提示/补全文件名、Buffer 名。 安装 projectile 插件,然后在 ~/.emacs 文件里添加: (ido-mode t) (require 'projectile) (projectile-global-mode) (setq projectile-require-project-root nil) 这个两个货带给我太多惊喜了……个人感觉已经超越使用 Vim 时用得倍爽的 CtrlP 了,赶紧打开文件(C-x C-f)或打开工程里的文件(C-c p f)试试吧。 自动显示匹配的文件名。 不用输入全路径,输入文件名能自动定位到文件。 切换 Buffer 终于可视化了。 …… 使用 smex 自动提示 M-x 后的内容。 安装 smex 插件,然后在 ~/.emacs 文件里添加: (global-set-key [(meta x)] (lambda () (interactive) (or (boundp 'smex-cache) (smex-initialize)) (global-set-key [(meta x)] 'smex) (smex))) (global-set-key [(shift meta x)] (lambda () (interactive) (or (boundp 'smex-cache) (smex-initialize)) (global-set-key [(shift meta x)] 'smex-major-mode-commands) (smex-major-mode-commands))) 这样就能在输入命令的时候享受和打开文件、切换 Buffer 同样的体验了。 Markdown 编辑环境 使用 Github Pages 来搭建博客,自然离不开 Markdown。 配置这个倒是简单,安装 Markdown-mode 插件,然后在 ~/.emacs 中添加: (autoload 'markdown-mode "markdown-mode" "Major mode for editing Markdown files" t) (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode)) (add-hook 'markdown-mode-hook '(lambda () (local-set-key (kbd "RET") 'comment-indent-new-line))) 打开 .md 后缀名的文件时将自动使用 markdown-mode,在 markdown-mode 下回车时执行 comment-indent-new-line,采用与当前行相同注释标记和缩进的换行,主要为了插入代码时方便。 C/S 模式快速启动 Emacs Emacs 的启动速度是相比 Vim 来说被诟病吐槽得很多的一个点,我们在 Windows 下可以利用 C/S 模式来实现 Emacs 快速打开文件。 在 ~/.emacs 里添加 (server-start) 这样 Emacs 将在打开时启动一个 Server。Server 启动后会读取和加载配置文件,使用 emacsclientw.exe 打开文件时就不用再读取加载配置文件了,而是直接作为 Client 连接到 Server,这样基本能实现文件秒开。 添加 Edit with Emacs 到系统右键菜单。 将以下内容复制之后粘贴到一个 .reg 文件里,运行即可(将 exe 路径换为你自己的)。 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\*\shell\Edit with Emacs] [HKEY_CLASSES_ROOT\*\shell\Edit with Emacs\command] @="\"D:\\emacs\\bin\\emacsclientw.exe\" -a \"D:\\emacs\\bin\\runemacs.exe\" \"%1\"" -a 参数表示如果使用 emacsclientw.exe 打开失败,那么使用 -a 指定的替代的编辑器打开。如果没有 -a 参数,那么在 Server 没有启动的情况下,右键打开文件将弹框提示: emacsclientw.exe: No socket or alternate editor. Please use: --server-file (or environment varible EMACS_SERVER_FILE) --alternate-editor (or environment varible ALTERNATE_EDITOR) 参考: How do I associate files with Emacs? For use with Internet Explorer 平滑滚动 Emacs 默认的滚动方式和 Vim 不一样,是光标移到屏幕上或者下边缘时突然跳动半屏,比较不符合我目光跟着光标走的习惯,这个可以使用一个插件来解决。 安装 smooth-scrolling 插件。 在 ~/.emacs 文件中添加 (require 'smooth-scrolling) (setq smooth-scroll-margin 3) 这个 3 表示在距离屏幕上下边缘还有 3 行的时候再移动光标即自动滚屏,方便随时能看到当前编辑行的上下文,可以根据自己的使用习惯调整。
这里记录的都是一些我曾经反复思考的问题,可能我某一天已经做好了决定,但是后来忘了这个决定,然后再纠结一遍,这耗费了大量的脑力却没有任何帮助。当再次因为一个事情纠结的时候,看一看这里,是不是这个问题已经纠结过了,有结论了。 一本书该不该看 虽然我们需要高效地安排自己的时间,用有限的时间换来最大的收益,但是既然会纠结,就说明至少这本书要么是兴趣所在,要么是内心觉得看了会有用。即如此,那就看吧,因为可以记住一点: 投入地看一本书,即使只是利用工作之余的时间,一般有几天也就看完了。 即使这本书以后并没有给你带来什么,但是你也并没有失去什么。 Emacs 还是 Vim 用合适的工具正确地干活,它俩各有优点,所以我选择都用。在需要快速启动编辑配置文件的几个字段时 ,显然 Vim 更合适,打算长时间写点文字写点代码了,可能 Emacs 加上 Evil 配合里面自带的 eshell、ido 和 smex 等会更顺手。 引用知乎网友的话: 锤子再好,再牛逼,也只是个锤子,而不应该成为工匠的心魔。(from 知乎) 多说还是 Disqus 多说的优点: 国内友好,加载速度快。 支持的社交账号多。 几个好朋友的博客都用多说,互动起来比较流畅。 界面简洁大方。 多说的缺点: 邮件与网页提醒已经名存实亡。 缺乏维护者。 Disqus 的优点: 国际接轨,如果以后考虑练习英文写博客用它准没错。 服务稳定,不愁商业模式与维护。 邮件提醒。 Disqus 的缺点: 国外友好,加载速度一般,有被墙的风险。 仅支持 Google、Twitter、Facebook 和 Disqus 账号。 将语言调整为 Chinese 后中英文夹杂,不伦不类。 界面效果一般。 综上,最近正在实践「回调式生活」,即能够让它通知我的,我就不主动去查阅,所以有评论后的邮件提醒是我最看重的,我选 Disqus。 至于很多网友顾虑的它们会不会倒闭和之后怎么办的问题,我反倒不担心,毕竟他们都是有导入导出功能的,只要在收到服务关停通知后能导出数据,到时候迁移不是问题。 Update 2017-03-16:Disqus 已经被墙很久了,中文博客读者群应该主要还是国内用户,还是主要用多说,稳定性的问题其实不用太在意,访问量不大的网站一会儿评论不了算不得什么事,只要它还活着就好。 Update 2017-03-22:duoshuo 宣布 2017-06-01 将关闭服务,切回 Disqus。 Update 2017-04-04:改为采用云跟帖,能延迟加载 Disqus。
打开 Android Studio 卡在「Fetching Android SDK component information」界面。 如图: Android Studio First Run 检测 Android SDK 及更新,由于众所周知的原因,我们会「Unable to access Android SDK add-on list」,而且大家一般也已经提前配置好了 Android SDK,真正需要更新的时候手动去 SDK Manager 更新就好了。 解决方案: 在 Android Studio 安装目录 bin/idea.properties 文件最后追加一句 disable.android.first.run=true 参考: http://ask.android-studio.org/?/article/14 新建工程后构建时提示找不到 appcompat-v7 Error:Failed to find: com.android.support:appcompat-v7:22.+ 解决方案: 打开 SDK Manager,然后安装 Extras 下的 Android Support Repository: Rebuild 工程。 aidl 文件的放置 按以前 Eclipse 的方式,将 aidl 及其包目录层级放置在与自己的顶级包同级的目录下,即如下的 android/content/pm: app/src/main ├─assets ├─java │ ├─android │ │ └─content │ │ └─pm │ └─org │ └─mazhuang │ └─easycleaner └─res ├─drawable ├─layout ├─menu ... 然而这样在调用处一直报错: Cannot resolve symbol 'IPackageStatsObserver' 解决方案: 将 aidl 文件放置在与 app/src/main/java 目录同级的 app/src/main/aidl 文件夹下。 app/src/main ├─aidl │ └─android │ └─content │ └─pm ├─assets ├─java │ └─org │ └─mazhuang │ └─easycleaner └─res ├─drawable ├─layout ├─menu ... 在 Android Studio 里编译通过之后,命令行使用 gradlew build 为什么还是会重新下载 Gradle? Gradle 的版本在 Android Studio 工程里有三处: 一、gradle/wrapper/gradle-wrapper.properties 文件的 distributionUrl 字段里指定的。 #Wed Oct 21 11:34:03 PDT 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip 比如这里指定的是 2.8 版本。 二、Android Studio 的 File > Project Structure > Project 里显示的。 这个实际上就是显示的「一」里的版本。 三、Android Studio 的 File > Settings > Build, Execution, Deployment > Build Tools > Gradle 里选择的是「Use default gradle wrapper (recommended)」还是「Use local gradle distribution」。 出现题目里的问题一般是由于「三」中选择的是「Use local gradle distribution」,这个选项下的「Gradle home」路径一般是指向 Android Studio 安装目录下的 Gradle 目录,比如 C:/Program Files/Android/Android Studio/gradle/gradle-2.8,而 gradlew 脚本是独立于 Android Studio 的,所以并不受其配置的影响,它是使用「一」里指定的版本,会到 ~/.gradle/wrapper/dists 目录下去寻找对应版本的 Gradle 是否已经存在,如果没有话就会去重新下载。 模拟器启动失败 PANIC: ANDROID_SDK_HOME is defined but could not find Nexus_5_API_23.ini file in $ANDROID_SDK_HOME/.android/avd (Note: avd is searched in the order of $ANDROID_AVD_HOME,$ANDROID_SDK_HOME/.android/avd and $HOME/.android/avd) 实际上文件存在于 $HOME/.android/avd 目录下,但看样子如果设置了 $ANDROID_SDK_HOME 环境变量,Android Studio 在 $ANDROID_SDK_HOME/.android/avd 下找不到模拟器文件将直接报错,而不会再去找 $HOME 目录下的文件。 解决方案: 添加 $ANDROID_AVD_HOME 环境变量,值为 $HOME/.android/avd 的展开全路径。 debug.keystore 的存放位置 在使用高德地图 SDK 时,需要 key 与 keystore 文件的 sha1 校验通过,而我将 debug.keystore 拷贝到 $HOME/.android 目录下后发现一直提示 key 校验失败,也就是没有使用我拷贝到 $HOME/.android 目录下的 debug.keystore 来做 debug 签名。 原因是 debug.keystore 的默认存储路径是 $HOME/.android,但是如果配置了 $ANDROID_SDK_HOME,则会将 debug.keystore $ANDROID_SDK_HOME/.android 目录下。 解决方案: 将 debug.keystore 文件拷贝到 $ANDROID_SDK_HOME/.android 目录下。 BTW: 关于给 App 签名的手动、自动方法参考 Signing Your Applications。 Android Studio 自动生成的 debug.keystore 的信息: Keystore password: android Key alias: androiddebugkey Key password: android 一直提示 Please configure Android SDK 这是在一次电脑断电后出现的,试了一些方法,更新 Android Studio,将 SDK Platforms 删除了重新下,都不行,后来发现 Build Tools 可以更新,更新完后就好了。 More than one file was found with OS independent path 比如,在 netty-buffer-4.1.5.Final.jar 与 netty-common-4.1.5.Final.jar 中都有 META-INF/io.netty.version.properties,所以编译时报错: More than one file was found with OS independent path 'META-INF/io.netty.versions.properties' 解决方案: 在 app/build.gradle 里添加如下内容: android { packagingOptions { pickFirst 'META-INF/*' } } 表示只保留一份该文件。 参考:Android Studio: Duplicate files copied in APK META-INF/DEPENDENCIES when compile 打开 uiautomatorviewer 报错 打开 uiautomatorviewer 报错,提示: Unable to connect to adb. Check if adb is installed correctly. 实际 adb 命令是可正常使用的。 解决方法: 打开 uiautomatorviewer.bat 文件(Windows 下,其它系统可能是 .sh),找到下面这行(一般是最后一行): call "%java_exe%" "-Djava.ext.dirs=%javaextdirs%" "-Dcom.android.uiautomator.bindir=%prog_dir%" -jar %jarpath% %* 将其中的 %prog_dir% 改为 Android SDK 的 tools 目录路径,比如: call "%java_exe%" "-Djava.ext.dirs=%javaextdirs%" "-Dcom.android.uiautomator.bindir=D:\Android\sdk\tools" -jar %jarpath% %* 参考:In UI automator viewer Error Obtaining Device screenshot, Reason : Error Unable to connect to adb. Check if adb is installed correctly is not translated in “zh” 报错信息: Error: "app_name" is not translated in "zh" (Chinese) [MissingTranslation] 在引用了 Umeng 的 SDK 后编译报错,疑是 Umeng 包里的 values-zh 导致。 解决方案 1: <?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation" > 解决方案 2: 在主项目的 build.gradle 里添加如下代码: android { lintOptions { checkReleaseBuilds false abortOnError false } } 参考: http://blog.csdn.net/cxc19890214/article/details/39120415 https://segmentfault.com/a/1190000000599530 一直卡在 Gradle:Resolve dependencies’app:debugCompile’ 这种情况一般是 Gradle 去拉取某个 dependencies 的时候连不上导致。 一种方法是如果本地有能编译通过的其它工程,修改 compileSdkVersion 和 buildToolsVersion 及 dependencies 里的版本为能编译通过的工程的版本;另一种方法是将对应的依赖包 jar 下载到本地放到 libs 里面;还有一种思路是修改 jcenter() 为其它可用的源。 更新到 Android Studio 3.0 后报错 提示信息: Unable to resolve dependency for ':internal@packagingOptions/compileClasspath': Could not resolve project :commonlib. Could not resolve project :commonlib. Required by: project :internal > Unable to find a matching configuration of project :commonlib: - Configuration 'debugApiElements': - Required com.android.build.api.attributes.BuildTypeAttr 'packagingOptions' and found incompatible value 'debug'. - Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' and found compatible value 'Aar'. - Found com.android.build.gradle.internal.dependency.VariantAttr 'debug' but wasn't required. - Required org.gradle.api.attributes.Usage 'java-api' and found compatible value 'java-api'. - Configuration 'debugRuntimeElements': - Required com.android.build.api.attributes.BuildTypeAttr 'packagingOptions' and found incompatible value 'debug'. - Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' and found compatible value 'Aar'. - Found com.android.build.gradle.internal.dependency.VariantAttr 'debug' but wasn't required. - Required org.gradle.api.attributes.Usage 'java-api' and found incompatible value 'java-runtime'. - Configuration 'releaseApiElements': - Required com.android.build.api.attributes.BuildTypeAttr 'packagingOptions' and found incompatible value 'release'. - Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' and found compatible value 'Aar'. - Found com.android.build.gradle.internal.dependency.VariantAttr 'release' but wasn't required. - Required org.gradle.api.attributes.Usage 'java-api' and found compatible value 'java-api'. - Configuration 'releaseRuntimeElements': - Required com.android.build.api.attributes.BuildTypeAttr 'packagingOptions' and found incompatible value 'release'. - Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' and found compatible value 'Aar'. - Found com.android.build.gradle.internal.dependency.VariantAttr 'release' but wasn't required. - Required org.gradle.api.attributes.Usage 'java-api' and found incompatible value 'java-runtime'. 情况是有一个叫 internal 的 project 依赖一个叫 commonlib 的 module,最后查到原因如下: internal project 的 build.gradle 文件里写了这么一段: android { ... buildTypes { debug { signingConfig signingConfigs.release } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } packagingOptions { exclude 'META-INF/INDEX.LIST' exclude 'log4j.xml' } } } 实际 packagingOptions 应该放到 buildTypes 之外,改成这样就 OK 了: android { ... packagingOptions { exclude 'META-INF/INDEX.LIST' exclude 'log4j.xml' } buildTypes { debug { signingConfig signingConfigs.release } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } } } 以前曾经通过这样修改临时掩盖了问题,让编译能过: dependencies { ... implementation project(path: ':commonlib', configuration: 'default') // 临时方案 // implementation project(':commonlib') // 原来的样子 } 这样修改编译能通过,但是会有个问题,就是在 internal project 工程里调用 commonlib 的方法的地方,Ctrl + 鼠标左键,或者右键 Go To Declaration 时会跳到 ~/.gradle/caches/transforms-1/files-1.1/commonlib-release.aar 目录里的 .class 文件,而非 .java 源文件。 回答在 StackOverflow 的一个问题 下。 启动模拟器提示 Intel HAXM is required to run this AVD your CPU does not support VT-x 我使用 Win10 系统,换主板之后遇到的,之前模拟器是能正常运行的。 我遇到的原因是 Hyper-V 的影响,导致无法安装 HAXM,虽然在 msconfig 里查看我的 Hyper-V 服务都已经停止,我在 BIOS 里也已经 Enable 了 Virtualization Technology 相关的选项,仍然报相同的错误。 解决步骤参考 http://blog.csdn.net/WangZuoChuan/article/details/54620016: 打开管理员权限的 CMD 运行 bcdedit /copy {current} /d “Windows10 no Hyper-V 这时会输出一个 UUID 执行 bcdedit /set {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} hypervisorlaunchtype OFF 将 XXX 部分换成步骤 2 里输出的 UUID 在 msconfig 的「引导」里将 Windows 10 no Hyper-V 设为默认 重启 Android Studio 里对所有 Activity 显示警告 警告信息: methods findViewById(int) from android.app.Activity and findViewById(int) from android.support.v7.app.AppCompatActivity are inherited with the same signature 解决方案: 项目里有几个模块,有的 compileSdkVersion 和 targetSdkVersion 是 25,有的是 26,全部改成 26 并把 appcompat-v7 等 dependencies 也改成 26 对应版本后问题消失。(但诡异的是我后来改回 25 想复现一下,问题却不再出现了) Plugin with id ‘com.android.application’ not found 导入一个别人做的工程的时候遇到报错: Error:(1, 0) Plugin with id 'com.android.application' not found 怀疑是使用比较老的版本的 Android Studio 创建,该工程只有一个 build.gradle 文件——我们平时创建的工程应该是有两个,一个 Project 级别的,一个 Module 级别的。 它是只有一个 Project 级别的 gradle 文件,但是内容却是 Module 级别 gradle 文件的内容。 后来在 StackOverflow 上找到 解决方案: 在 build.gradle 文件顶部添加如下代码(注意 Gradle 版本与 Gradle Plugin 的版本对应): buildscript { repositories { jcenter() google() } dependencies { classpath 'com.android.tools.build:gradle:3.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() google() } } 升级后 Gradle sync 出错 从 Android Studio 3.0.1 升级到 3.1 的时候,Check for Updates... 提示超时,于是挂了代理升级。但是升级完成之后,打开以前能顺利构建的工程,提示 Cause: jcenter.bintray.com:443 failed to respond,怀疑是代理的原因,于是在 Settings 里将 HTTP Proxy 选项改为以前的 no proxy,报错变成 Connection refused: connect,搜索了一番之后找到 这个,依提示打开 ~/.gradle/gradle.properties,发现里面果然还存在代理的设置信息,删除之后重启 Android Studio,问题解决。 Generate JavaDoc 提示“错误: 编码GBK的不可映射字符” 所有相关文件的编码都是 UTF-8,在 Android Studio 里没有找到设置 JavaDoc 相关的编码设置项,于是在 Generate JavaDoc 时弹出的 Specify Generate JavaDoc Scope 对话框的 Other command line arguments 一项里填入 -encoding utf-8 -charset utf-8,问题解决。 升级 Gradle Plugin 版本后报错 The SourceSet 'instrumentTest' is not recognized by the Android Gradle Plugin. Perhaps you misspelled something? 将 android { sourceSets { } } 里的 instrumentTest.setRoot(...) 改为 androidTest.setRoot(...) 后问题解决。 编译报错 Error:Execution failed for task ‘:app:transformClassesWithDexForRelease’ 我这里的原因是一个 APP 依赖一个 Module,这两个使用了相同的包名,将 APP 的包名改了之后问题解决。 升级到 3.1 后编辑 Gradle 文件卡顿 不止是卡顿……基本上就是整个 Android Studio 卡住几十秒没办法动的那种。在网上搜索之后发现遇到这种问题的网友还挺多,果然是垃圾软件毁我青春 :-P。 参考 https://blog.csdn.net/wangluotianxi/article/details/79757558,卡顿原因是编辑 Gradle 文件过程中一直在请求下面两个接口: http://search.maven.org/solrsearch/select?q=g:%22com.google.android.support%22+AND+a:%22wearable%22&core=gav&rows=1&wt=json http://search.maven.org/solrsearch/select?q=g:%22com.google.android.gms%22+AND+a:%22play-services%22&core=gav&rows=1&wt=json 而且,结果返回之前会卡住界面,而我们的网络访问这俩网址基本只能等到超时返回了,所以,临时解决方案是在 hosts 文件里添加如下内容,让这俩请求快速失败返回: 127.0.0.1 search.maven.org 暂未发现对正常功能有影响。 编译报错 You should manually set the same version via DependencyResolution Android dependency ‘org.mazhuang:commonlib’ has different version for the compile (0.0.4) and runtime (0.0.5) classpath. You should manually set the same version via DependencyResolution Project A 使用了 Module B,A 依赖 commonlib(0.0.4),而 Module B 里引用了 commonlib(0.0.5),将 A 也改为依赖 commonlib(0.0.5) 即可。 编译报错 Please use JDK 8 or newer Gradle Sync 的时候无法成功,报错 Gradle sync failed: Please use JDK 8 or newer 尝试 Rebuild 报错 Supplied javaHome is not a valid folder. 原因是我在 Project Settings 的 SDK Location 里手动指定了 JDK 的版本,但是后来升级了 JDK,所以原有路径失效了。 解决方法是将 Project Settings 的 SDK Location 里 JDK 的路径改为正确路径,或者省事起见可以直接勾选 Use embedded JDK 即可。 编译报错 Caused by: java.io.IOException: Cannot run program 提示找不到 NDK 工具链里的 mips64el-linux-android-strip,导致 Caused by: java.io.IOException: error=2。 原因是 NDK r17 移除了对 ARMv5(armeabi)、MIPS 和 MIPS64 的支持,所以对应的工具链也没有了。 解决办法有几种: 检查 Gradle Plugin 的版本,即 project 级别的 build.gralde 文件里 dependencies { classpath 'com.android.tools.build:gradle:3.1.3' ... } com.android.tools.build:gradle 的版本改为 3.1 以上。 将 NDK 版本退回 16b,或将 16b 以下的对应 mips 工具链的文件夹拷贝到 r17 的对应目录下。 报错 NDK not configured Caused by: org.gradle.api.InvalidUserDataException: NDK not configured. 经检查发现是 local.properties 里配置的 NDK 路径不存在,修改为正确的就好了。 修改代码后,Run / Debug 都不生效 在 Run / Debug Configurations 里,勾选 Always install with package manager (disables deploy optimizations on Android 11 and later)。
在学习 Android UI 开发的初期,经常被一些常用概念如 dp、sp 和它们与 px 的换算等虐,要避免被虐,最好的方法当然是知其所以然,再见到它们就胸中有料心不慌了。 背景知识 参考 http://developer.android.com/guide/practices/screens_support.html#terms 屏幕大小(Screen size) 屏幕对角线的实际物理大小。通常以英寸(inch)为单位。 屏幕密度(Screen density) 每英寸上的像素个数。通常被称作多少 dpi(dots per inch)或多少 ppi(pixels per inch)。 比如,LG Nexus 5 的屏幕尺寸为 4.95 英寸,分辨率为 1920*1080,则它的对角线上的像素数为 sqrt((sqr(1920)+sqr(1080)),所以它的屏幕密度为 sqrt((sqr(1920)+sqr(1080))/4.95=445.03,约为 445dpi。 分辨率(Resolution) 屏幕上的物理像素个数。 比如 LG Nexus 5 的分辨率为 1920*1080,这里的 1920 和 1080 就是屏幕长和宽上的像素个数。 尺寸 参考 http://developer.android.com/guide/topics/resources/more-resources.html#Dimension dp(Density-independent Pixels) 在不同大小、密度和分辨率的屏幕上的物理大小都近似相等的虚拟尺寸单位。 约为 1/160 英寸(为什么是约为?稍后讲解)。 sp(Scale-independent Pixels) 基于首选字体大小的缩放像素。 与 dp 类似,但是会根据用户的首选字体大小缩放。 pt(Points) 1/72 英寸。 px(Pixels) 像素。 mm(Millimeters) 毫米。 in(Inches) 英寸,约 2.539999918 厘米。 换算 dp 转 px 参考http://developer.android.com/guide/practices/screens_support.html#dips-pels 为了简单起见,Android 将屏幕密度概括为 6 种: ldpi(low) ~120dpi mdpi(medium) ~160dpi hdpi(high) ~240dpi xhdpi(extra-high) ~320dpi xxhdpi(extra-extra-high) ~480dpi xxxhdpi(extra-extra-extra-high) ~640dpi 但是并非表示所有 Android 手机只有这几个屏幕密度,比如上面举例的 LG Nexus 5 的屏幕密度是 445dpi,近似地归于 xxhdpi,Android 在内部进行 dp 到 px 的换算时将采用 480dpi 而非 445dpi。 简而言之,dp 数 x 换算成 px 数 y 的公式: // 向上取整 y = x * generalizedDensity / 160 这里的 generalizedDensity 就是以上 6 种中的一个值,比如 LG Nexus 5 的实际屏幕密度为 445dpi,但是归于 xxhdpi,所以它的 generalizedDensity 就是 480dpi。 官方文档给出的转换 dp 到 px 的代码示例为: // The gesture threshold expressed in dp private static final float GESTURE_THRESHOLD_DP = 16.0f; // Get the screen's density scale final float scale = getResources().getDisplayMetrics().density; // Convert the dps to pixels, based on density scale mGestureThreshold = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f); // Use mGestureThreshold as a distance in pixels... 在 mdpi(160dpi)上 1dp=1px(还记得前面讲过的 1dp 约为 1/160 英寸吗?在 160dpi 的屏幕上,1px=1/160 英寸),这里的getResources().getDisplayMetrics().density实际上就等于我们的generalizedDensity / 160,表示将 dp 转换为 px 的一个转换因子。 然后来理解一下为何 dp 是约为 1/160 英寸。 还是以 LG Nexus 5 举例,比如 160dp,若在一个屏幕密度恰好是 480dpi 的机器上,那它会是准确的 1 英寸,但是 LG Nexus 5 的屏幕密度是 445dpi,根据上面的公式计算得出:160dp = 160 * 480 / 160 px = 480px,则它的实际显示尺寸为 480/445=1.07865 英寸。所以原因是dp 换算成 px 是使用 Android 概括的六种屏幕密度之一,而非实际屏幕密度,所以在不同的手机上相同数量的 dp 显示尺寸会有轻微差异。 sp 转 px 在http://developer.android.com/reference/android/util/DisplayMetrics.html#scaledDensity中可以看到scaledDensity就是控制字体尺寸的缩放因子。于是猜想 sp 数 x 换算成 px 数 y 的公式: y = x * scaledDensity 这里的 scaledDensity 获取方式为getResources().getDisplayMetrics().scaledDensity。 这个猜想是否正确呢?看看下面一张图就明白啦!(Nexus 4 模拟器截图) 根据分辨率和屏幕密度求屏幕尺寸 使用 adb 命令 adb shell wm size 可以得到设备分辨率,adb shell wm density 可以得到设备屏幕密度,但貌似没有办法直接得到屏幕尺寸,只能拿到这两个数据之后换算。 公式: sqrt(sqr(width)+sqr(height))/density
Date 和 Calendar 转 String 借助 SimpleDateFormat 类的 format 方法,Calendar.getTime() 返回 Date,最终 Calendar 也是转化为 Date 后转 String。 // see SimpleDateFormat in Java API String format = new String("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US); // Date String strDate = sdf.format(new Date()); Calendar cal = Calendar.getInstance(); // Calendar String strCalendar = sdf.format(cal.getTime()); String 转 Date、Calendar String strDate = "2015-04-04 00:33:00"; Date date = null; Calendar cal = Calendar.getInstance(); try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); date = sdf.parse(strDate); cal.setTime(date); } catch (Exception e) { e.printStackTrace(); } 设置 Calendar 到某年某月某日 注意点: Month 要设为比实际小 1。 除显式设置的几个字段外,其它字段之前的值不变;如果不是期待的,可以先调用 clear() 清除。 Calendar cal = Calendar.getInstance(); // set to 2015-03-01 cal.set(2015, 3 - 1, 1); 复制 Calendar Calendar calDst = (Calendar)calSrc.clone(); 求本周、上周、下周的起始时间 做了一个工具类来处理此事(注意:这里计算的一周是从周一到周日,如果要算周天到周六,把获取本周一时括号里的 +1 去掉): static class DateCalcUtil { public static final int GET_PREVIOUS_MONDAY = 0; public static final int GET_PREVIOUS_SUNDAY = 1; public static final int GET_THIS_MONDAY = 2; public static final int GET_THIS_SUNDAY = 3; public static final int GET_NEXT_MONDAY = 4; public static final int GET_NEXT_SUNDAY = 5; public static Calendar calc(Calendar base, int calcType) { // 获取这一周开始基准 int min = base.getActualMinimum(Calendar.DAY_OF_WEEK); // 获取当天在这一周内天数 int current = base.get(Calendar.DAY_OF_WEEK); Calendar calendar = (Calendar)base.clone(); // 获取本周一 int nCount = (current == min) ? -6 : (min - current + 1); switch (calcType) { case GET_PREVIOUS_MONDAY: nCount -= 7; break; case GET_PREVIOUS_SUNDAY: nCount -= 1; break; case GET_THIS_MONDAY: break; case GET_THIS_SUNDAY: nCount += 6; break; case GET_NEXT_MONDAY: nCount += 7; break; case GET_NEXT_SUNDAY: nCount += 13; break; default: break; } calendar.add(Calendar.DAY_OF_WEEK, nCount); return calendar; } } 使用示例如下: // 今天 Calendar base = Calendar.getInstance(); // 本周 Calendar thisMonday = DateCalcUtil.calc(base, DateCalcUtil.GET_THIS_MONDAY); Calendar thisSunday = DateCalcUtil.calc(base, DateCalcUtil.GET_THIS_SUNDAY); // 下周 Calendar nextMonday = DateCalcUtil.calc(base, DateCalcUtil.GET_NEXT_MONDAY); Calendar nextSunday = DateCalcUtil.calc(base, DateCalcUtil.GET_NEXT_SUNDAY); // 上周 Calendar previousMonday = DateCalcUtil.calc(base, DateCalcUtil.GET_PREVIOUS_MONDAY); Calendar previousSunday = DateCalcUtil.calc(base, DateCalcUtil.GET_PREVIOUS_SUNDAY); 获取两个日期相差天数 注意点: getTimeInMillis 返回的是 0 时区时间,所以有可能把你的时间加减了几个小时,造成计算天数有误,这种方法必须考虑时区因素再运算。 long daysCount1 = (calendar1.getTimeInMillis() + calendar1.get(Calendar.ZONE_OFFSET)) / (24 * 3600 * 1000); long daysCount2 = (calendar2.getTimeInMillis() + calendar2.get(Calendar.ZONE_OFFSET)) / (24 * 3600 * 1000); long dayDiffer = daysCount1 - daysCount2;
方法一:共享静态数据。 此方法参见《Windows 核心编程》第 5 版 17.1.2 章节《在同一个可执行文件或 DLL 的多个实例间共享静态数据》。 实现原理: 创建一个自己命名的段,将其属性改为 READ|WRITE|SHARED,其中 SHARED 属性表示该段的内容为多个实例所共享(实际上关闭了写时复制机制),将变量放在该段内若值被改变,多个实例间都会受到改变的影响。 注意点: 最好使用 volatile 修饰变量。 对变量的增减推荐使用原子操作函数 InterlockedExchangedAdd。 g_lInstances 的值在第一个实例运行时总为 0,其它实例中取到的值以先于它运行的实例中改变后的值为准。 示例代码: #include <Windows.h> #pragma data_seg("Shared") volatile long g_lInstances = 0; #pragma data_seg() #pragma comment(linker, "/Section:Shared,RWS") int main(int argc, char *argv[]) { if (g_lInstances != 0) { return -1; } InterlockedExchangeAdd(&g_lInstances, 1); // do something here // ... InterlockedExchangeAdd(&g_lInstances, -1); return 0; } 方法二:使用 Mutex。 理论上能用于进程间同步的内核对象比如事件和互斥量等都能用于实现此功能,此处使用互斥量 Mutex 举例。 实现原理: 使用操作系统范围内可见的命名内核对象,不同实例间检测同一个内核对象的状态来判断是否为当前唯一实例。 注意点: 内核对象要使用全局命名,比如此处使用 Global 开头。 示例代码: #include <Windows.h> int main(int argc, char *argv[]) { HANDLE hMutex = NULL; do { hMutex = CreateMutex(NULL, FALSE, "Global\\73E21C80-1960-472F-BF0B-3EE7CC7AF17E"); DWORD dwError = GetLastError(); if (ERROR_ALREADY_EXISTS == dwError || ERROR_ACCESS_DENIED == dwError) { break; } // do something here // ... } while (false); if (NULL != hMutex) { CloseHandle(hMutex); } return 0; }