<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; 思考</title>
	<atom:link href="http://tonybai.com/tag/%e6%80%9d%e8%80%83/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Sun, 12 Apr 2026 22:30:28 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>AI 是让你忘掉如何编程的最快方式</title>
		<link>https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code/</link>
		<comments>https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code/#comments</comments>
		<pubDate>Thu, 01 Jan 2026 00:26:30 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[ArchitectureReview]]></category>
		<category><![CDATA[Author]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[consumer]]></category>
		<category><![CDATA[Copilot]]></category>
		<category><![CDATA[CopyAndPaste]]></category>
		<category><![CDATA[Cursor]]></category>
		<category><![CDATA[EdgeCases]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GPSEffect]]></category>
		<category><![CDATA[GPS效应]]></category>
		<category><![CDATA[MentalModels]]></category>
		<category><![CDATA[Reviewer]]></category>
		<category><![CDATA[RubberDuck]]></category>
		<category><![CDATA[SDD]]></category>
		<category><![CDATA[SoftwareEngineer]]></category>
		<category><![CDATA[SpecDrivenDevelopment]]></category>
		<category><![CDATA[Teacher]]></category>
		<category><![CDATA[Tradeoffs]]></category>
		<category><![CDATA[Typist]]></category>
		<category><![CDATA[UnitTests]]></category>
		<category><![CDATA[代笔者]]></category>
		<category><![CDATA[单元测试]]></category>
		<category><![CDATA[心智模型]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[搬运工]]></category>
		<category><![CDATA[权衡]]></category>
		<category><![CDATA[架构评审]]></category>
		<category><![CDATA[消费者]]></category>
		<category><![CDATA[深度思考工作流]]></category>
		<category><![CDATA[盲区]]></category>
		<category><![CDATA[磨刀石]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[规范驱动开发]]></category>
		<category><![CDATA[认知保留]]></category>
		<category><![CDATA[认知升级]]></category>
		<category><![CDATA[认知肌肉萎缩]]></category>
		<category><![CDATA[边缘情况]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5643</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code 大家好，我是Tony Bai。 在 Copilot、Cursor、Claude Code等普及的这两年，编程似乎变得前所未有的轻松。 Tab 键一按，十行代码倾泻而出；回车一敲，整个函数自动补全；一个Prompt发出，一个项目的框架代码便完成了。那种多巴胺分泌的快感是真实的，效率提升的数据也是真实的。我们仿佛一夜之间都变成了“十倍工程师”。 但在这种虚幻的快感背后，一种隐秘的焦虑正在资深开发者群体中蔓延：离开 AI 提示词，你还能流畅地写出一个复杂的递归，或者手撸一个带有完整错误处理的 HTTP Client 吗？ 最近，我在技术社区看到一段发人深省的论述，它像一盆冷水，浇在了在这个狂热的 AI 时代： “AI is the fastest way to forget how to code and how to think.” （AI 是让你忘掉如何编程、忘掉如何思考的最快方式。） 这句话听起来很刺耳，但很真实。 如果我们习惯了让 AI 替我们思考，我们的大脑正在经历一场无声的“认知肌肉萎缩”。在 AI 时代，写下每一行代码依然重要。这不是一种复古的情怀，而是关乎我们职业生存的“认知保留”。 警惕“GPS 效应”：你是在驾驶，还是在被运送？ 心理学中有一个著名的“GPS 效应”：习惯了使用导航的人，海马体（负责空间记忆的脑区）活跃度会降低，久而久之，他们会逐渐丧失方向感，甚至在自家小区门口也会迷路。 编程也是一样。 学习和成长的本质，发生在“挣扎”的过程中。 当你为了设计一个类结构而绞尽脑汁，当你为了修复一个“竞态条件”而彻夜排查，你的大脑正在构建复杂的神经连接，正在建立对系统的“心智模型”。 如果你跳过了这个“挣扎”的过程，直接向 AI 索要答案： AI 变成了“代笔者（Author）”：它替你构建了心智模型。 你变成了“消费者（Consumer）”：你只负责 Copy [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-is-the-fastest-way-to-forget-how-to-code-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code">本文永久链接</a> &#8211; https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code</p>
<p>大家好，我是Tony Bai。</p>
<p>在 Copilot、Cursor、Claude Code等普及的这两年，编程似乎变得前所未有的轻松。</p>
<p>Tab 键一按，十行代码倾泻而出；回车一敲，整个函数自动补全；一个Prompt发出，一个项目的框架代码便完成了。那种多巴胺分泌的快感是真实的，效率提升的数据也是真实的。我们仿佛一夜之间都变成了“十倍工程师”。</p>
<p>但在这种虚幻的快感背后，一种隐秘的焦虑正在资深开发者群体中蔓延：<strong>离开 AI 提示词，你还能流畅地写出一个复杂的递归，或者手撸一个带有完整错误处理的 HTTP Client 吗？</strong></p>
<p>最近，我在技术社区看到一段发人深省的论述，它像一盆冷水，浇在了在这个狂热的 AI 时代：</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-is-the-fastest-way-to-forget-how-to-code-2.png" alt="" /></p>
<blockquote>
<p><strong>“AI is the fastest way to forget how to code and how to think.”</strong><br />
  <strong>（AI 是让你忘掉如何编程、忘掉如何思考的最快方式。）</strong></p>
</blockquote>
<p>这句话听起来很刺耳，但很真实。</p>
<p>如果我们习惯了让 AI 替我们思考，我们的大脑正在经历一场无声的“认知肌肉萎缩”。在 AI 时代，<strong>写下每一行代码依然重要</strong>。这不是一种复古的情怀，而是关乎我们职业生存的<strong>“认知保留”</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/google-adk-in-action-qr.png" alt="" /></p>
<h2>警惕“GPS 效应”：你是在驾驶，还是在被运送？</h2>
<p>心理学中有一个著名的<strong>“GPS 效应”</strong>：习惯了使用导航的人，海马体（负责空间记忆的脑区）活跃度会降低，久而久之，他们会逐渐丧失方向感，甚至在自家小区门口也会迷路。</p>
<p>编程也是一样。</p>
<p><strong>学习和成长的本质，发生在“挣扎”的过程中。</strong></p>
<p>当你为了设计一个类结构而绞尽脑汁，当你为了修复一个“竞态条件”而彻夜排查，你的大脑正在构建复杂的神经连接，正在建立对系统的<strong>“心智模型”</strong>。</p>
<p>如果你跳过了这个“挣扎”的过程，直接向 AI 索要答案：</p>
<ul>
<li><strong>AI 变成了“代笔者（Author）”</strong>：它替你构建了心智模型。</li>
<li><strong>你变成了“消费者（Consumer）”</strong>：你只负责 Copy &amp; Paste。</li>
</ul>
<p>结果是：代码虽然跑通了，但你对系统组件之间的连接、潜在的边缘情况（Edge Cases）一无所知。你不再是代码的<strong>“作者”</strong>，你只是代码的<strong>“搬运工”</strong>。</p>
<p>一旦 AI 遇到它没见过的深水区，或者系统出现了一个隐蔽的 Bug，你会发现自己束手无策——因为你从未真正拥有过这段代码。</p>
<h2>重构契约：把 AI 当做“磨刀石”，而非“枪手”</h2>
<p>那么，我们要因噎废食，扔掉 AI 吗？当然不。</p>
<p>关键在于<strong>重构你与 AI 的协作契约</strong>。</p>
<p>核心原则只有一条：</p>
<p><strong>Use AI as a Reviewer, a Rubber Duck, a Teacher. Not as an Author.</strong><br />
（把它当作审查者、橡胶鸭、导师。绝不要把它当作代笔者。）</p>
<p>如果 AI 在替你思考，你在退步；如果 AI 在<strong>逼迫</strong>你思考得更深，你在进步。</p>
<p>以下是基于这个原则的 4 个<strong>深度思考工作流</strong>：</p>
<h3>1. 解释意图，而非索要实现</h3>
<p>不要直接丢一句“帮我写个鉴权中间件”。</p>
<p><strong>试着这样做：</strong> 你自己写出核心逻辑，然后对 AI 说：</p>
<blockquote>
<p>“这是我写的鉴权逻辑。请解释我为什么在这里使用了 Context 传递用户信息？这种写法符合 Go 语言的惯用范式吗？有没有更好的风格？”</p>
</blockquote>
<p><strong>收益：</strong> 强迫自己理清思路，利用 AI 验证你的设计直觉。</p>
<h3>2. 索要权衡(trade off)，而非标准答案</h3>
<p>不要问“在这个场景下我该用 Redis 还是 Memcached？”</p>
<p><strong>试着这样做：</strong></p>
<blockquote>
<p>“我倾向于使用 Redis，因为我们需要持久化。但在这个高并发场景下，使用 Redis 会带来哪些潜在的性能瓶颈或运维风险？请列出 Trade-offs。”</p>
</blockquote>
<p><strong>收益：</strong> AI 不再是给你喂饭，而是在陪你进行架构评审（Architecture Review）。</p>
<h3>3. 寻找盲区，挑战假设</h3>
<p>当你写完一段代码，觉得完美无缺时，把它扔给 AI：</p>
<blockquote>
<p>“这段代码在什么极端输入下会崩溃（Edge Cases）？我是否遗漏了某些并发安全问题？请像一个最挑剔的 Tech Lead 一样 Review 它。”</p>
</blockquote>
<p><strong>收益：</strong> 利用 AI 广博的知识库，填补你的认知盲区。</p>
<h3>4. 生成测试，而非生产代码</h3>
<p>这是一个最高阶的玩法。<strong>你自己写业务代码，让 AI 写测试用例。</strong></p>
<blockquote>
<p>“这是我实现的订单状态机。请为它编写一套覆盖率 100% 的单元测试，特别是针对状态回滚的异常场景。”</p>
</blockquote>
<p><strong>收益：</strong> 如果 AI 生成的测试跑通了，说明你的逻辑是自洽的；如果跑不通，或者 AI 根本理解不了你的代码，说明<strong>你</strong>没想清楚。</p>
<h2>小结：不要温和地走进那个良夜</h2>
<p>在 AI 时代，能够熟练调用 API 生成代码的人多如牛毛。</p>
<p>但能够<strong>独立构建复杂系统心智模型</strong>，并能驾驭 AI 进行<strong>深度架构推演</strong>的人，将变得极度稀缺。</p>
<p><strong>Writing code matters.</strong></p>
<p>写代码的过程，强迫你思考，强迫你大脑建立连接，强迫你理解系统是如何像齿轮一样咬合的。</p>
<p>请继续亲自写下那些核心的、关键的代码。</p>
<p>把 AI 当作你的<strong>磨刀石</strong>，让你的思维在与它的碰撞中变得更加锋利，而不是让它锈蚀你的大脑。</p>
<hr />
<p><strong>深度实战：构建“以人为本”的 AI 工作流</strong></p>
<p>道理大家都懂，但在高压的项目交付期，我们很容易滑向“让 AI 全自动生成”的舒适区。</p>
<p>如何建立一套<strong>强制性</strong>的工作流，既利用 AI 的效率，又保留人类的深度思考？</p>
<ul>
<li>如何在 Spec 文档中通过<strong>“伪代码”</strong>保留思考过程？</li>
<li>如何配置 <strong>Claude Code</strong>，让它默认扮演 Reviewer 而不是 Coder？</li>
<li>如何利用 <strong>SDD (Spec-Driven Development)</strong> 迫使自己在 Coding 前先进行完整的思维推演？</li>
</ul>
<p>如果你想掌握这套<strong>“不降智、反内卷”</strong>的高阶开发心法，欢迎关注我的极客时间专栏《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》。</p>
<p>在这个专栏里，我不教你如何偷懒，我教你如何进化。我们将一起探索，如何在 AI 的加持下，成为更强大的<strong>Software Engineer</strong>，而不是更快的<strong>Typist</strong>。</p>
<p><strong>扫描下方卡片，开启你的认知升级之旅。</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 的“简单”幻象：易于上手，难于精通</title>
		<link>https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master/</link>
		<comments>https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master/#comments</comments>
		<pubDate>Fri, 07 Nov 2025 06:28:23 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[append]]></category>
		<category><![CDATA[Bug]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Channelmisuse]]></category>
		<category><![CDATA[cloudnativeapplications]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Context]]></category>
		<category><![CDATA[coreconcepts]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[do-while]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[fanin]]></category>
		<category><![CDATA[fanout]]></category>
		<category><![CDATA[for]]></category>
		<category><![CDATA[foreach]]></category>
		<category><![CDATA[GeekTime]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.24]]></category>
		<category><![CDATA[Goexpert]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoLanguageAdvancedCourse]]></category>
		<category><![CDATA[GoLanguageFirstCourse]]></category>
		<category><![CDATA[Gophilosophy]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[GoroutineLeak]]></category>
		<category><![CDATA[GoSkilledWorker]]></category>
		<category><![CDATA[httpserver]]></category>
		<category><![CDATA[iferrnil]]></category>
		<category><![CDATA[implicitdependency]]></category>
		<category><![CDATA[Interfaces]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[keywords]]></category>
		<category><![CDATA[LLMassistedcoding]]></category>
		<category><![CDATA[memorymodel]]></category>
		<category><![CDATA[monkeypatching]]></category>
		<category><![CDATA[net/http]]></category>
		<category><![CDATA[newbook]]></category>
		<category><![CDATA[nilinterface]]></category>
		<category><![CDATA[operationalcomplexity]]></category>
		<category><![CDATA[Orchestrator]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[pathtomastery]]></category>
		<category><![CDATA[productionsystem]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[race]]></category>
		<category><![CDATA[RaceConditions]]></category>
		<category><![CDATA[racedetector]]></category>
		<category><![CDATA[redditgolangforum]]></category>
		<category><![CDATA[rollyourown]]></category>
		<category><![CDATA[slices]]></category>
		<category><![CDATA[SoftwareEngineering]]></category>
		<category><![CDATA[standardlibrary]]></category>
		<category><![CDATA[standardlibrarysourcecode]]></category>
		<category><![CDATA[sync.Mutex]]></category>
		<category><![CDATA[synchronizationprimitives]]></category>
		<category><![CDATA[TonyBai]]></category>
		<category><![CDATA[unbufferedchannel]]></category>
		<category><![CDATA[underlyingbehavior]]></category>
		<category><![CDATA[while]]></category>
		<category><![CDATA[互斥锁]]></category>
		<category><![CDATA[共享底层数组]]></category>
		<category><![CDATA[内存]]></category>
		<category><![CDATA[减法哲学]]></category>
		<category><![CDATA[可靠]]></category>
		<category><![CDATA[团队]]></category>
		<category><![CDATA[基础设施]]></category>
		<category><![CDATA[外部系统]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[密码学工具]]></category>
		<category><![CDATA[并发编程]]></category>
		<category><![CDATA[幻象破灭]]></category>
		<category><![CDATA[异常]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[性能下降]]></category>
		<category><![CDATA[所见即所得]]></category>
		<category><![CDATA[易于上手]]></category>
		<category><![CDATA[易于维护]]></category>
		<category><![CDATA[易于阅读]]></category>
		<category><![CDATA[极简语法]]></category>
		<category><![CDATA[死锁]]></category>
		<category><![CDATA[画图]]></category>
		<category><![CDATA[第三方库]]></category>
		<category><![CDATA[简单幻象]]></category>
		<category><![CDATA[简单性]]></category>
		<category><![CDATA[精通]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[继承体系]]></category>
		<category><![CDATA[设计]]></category>
		<category><![CDATA[调试]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[进程]]></category>
		<category><![CDATA[重新分配]]></category>
		<category><![CDATA[错误处理]]></category>
		<category><![CDATA[隐式魔法]]></category>
		<category><![CDATA[难于精通]]></category>
		<category><![CDATA[韧性]]></category>
		<category><![CDATA[高可用]]></category>
		<category><![CDATA[高效]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5362</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master 大家好，我是Tony Bai。 “Go 语言看起来如此简单，我的这种假设是错的吗？” 近日，一位刚接触 Go 几个月的新手在reddit golang论坛发出了这样一个真诚的提问。他感觉 Go “超级简单”，并好奇自己是否因为初学者的身份，而忽略了语言中那些“疯狂的复杂性”。 这个问题，立刻引发了社区关注。数百条评论从四面八方涌来，汇成了一场关于 Go 语言简单性本质的深度辩论。最终，社区的集体智慧凝聚成一个经典而又充满辩证性的共识：Go 的简单，是刻意为之的设计；而通往精通之路，则隐藏在简约表象之下的深邃之处。 本文将带你深入探索这座“简单”的冰山，从其光彩照人的水上部分，一直潜入其复杂深邃的水下世界。 “蜜月期”——为什么 Go 语言感觉如此简单？ 对于初学者而言，Go 带来的“简单”感受是真实且强烈的。这并非巧合，而是源于 Go 设计者们一系列深思熟虑的“减法”哲学。 极简的语法与关键字 “25 个关键字，宝贝！” 一位评论者这样感叹道。Go 有意地限制了语言的表面积，仅保留了构建大型系统所必需的核心元素。它只有一个循环结构 for，没有 while、do-while 或 foreach 的变体。这种极简主义，让学习者可以快速掌握语言的全貌，而不必记忆大量特殊语法。 “所见即所得”的代码 一位来自 Java/Python 背景的开发者分享道：“Go 给你的玩具可能更少，但至少你可以相信，它们不会在调试时反咬你一口。” Go 缺乏猴子补丁 (monkey patching)、复杂的继承体系和隐式的魔法，这意味着代码的行为更加可预测。“代码读起来就像它实际运行的样子，即便这意味着多写几行。” “电池自带”的强大标准库 “标准库太棒了，” 社区普遍赞同，“你需要花些时间才能理解，在不引入单个依赖的情况下，你能做多少事情。” 从 HTTP 服务器到密码学工具，Go 的标准库提供了构建现代网络服务所需 90% 的功能，让初学者可以立即开始构建有价值的应用，而无需在茫茫的第三方库中选择和配置。 幻象的破灭——“简单”背后的隐藏复杂性 当“蜜月期”结束，开发者开始构建更复杂的真实世界系统时，Go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/go-simple-illusion-easy-to-learn-hard-to-master-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master">本文永久链接</a> &#8211; https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master</p>
<p>大家好，我是Tony Bai。</p>
<p>“Go 语言看起来如此简单，我的这种假设是错的吗？”</p>
<p>近日，一位刚接触 Go 几个月的新手在reddit golang论坛发出了这样<a href="https://www.reddit.com/r/golang/comments/1oj9jb6/golang_seems_so_simple_am_i_wrong_to_assume_that/">一个真诚的提问</a>。他感觉 Go “超级简单”，并好奇自己是否因为初学者的身份，而忽略了语言中那些“疯狂的复杂性”。</p>
<p>这个问题，立刻引发了社区关注。数百条评论从四面八方涌来，汇成了一场关于 Go 语言简单性本质的深度辩论。最终，社区的集体智慧凝聚成一个经典而又充满辩证性的共识：<strong>Go 的简单，是刻意为之的设计；而通往精通之路，则隐藏在简约表象之下的深邃之处。</strong></p>
<p>本文将带你深入探索这座“简单”的冰山，从其光彩照人的水上部分，一直潜入其复杂深邃的水下世界。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-micro-column-2025-pr.png" alt="" /></p>
<h2>“蜜月期”——为什么 Go 语言感觉如此简单？</h2>
<p>对于初学者而言，Go 带来的“简单”感受是真实且强烈的。这并非巧合，而是源于 Go 设计者们一系列深思熟虑的“减法”哲学。</p>
<h3>极简的语法与关键字</h3>
<p>“25 个关键字，宝贝！” 一位评论者这样感叹道。Go 有意地限制了语言的表面积，仅保留了构建大型系统所必需的核心元素。它只有一个循环结构 for，没有 while、do-while 或 foreach 的变体。这种极简主义，让学习者可以快速掌握语言的全貌，而不必记忆大量特殊语法。</p>
<h3>“所见即所得”的代码</h3>
<p>一位来自 Java/Python 背景的开发者分享道：“Go 给你的玩具可能更少，但至少你可以相信，它们不会在调试时反咬你一口。” Go 缺乏猴子补丁 (monkey patching)、复杂的继承体系和隐式的魔法，这意味着代码的行为更加可预测。“代码读起来就像它实际运行的样子，即便这意味着多写几行。”</p>
<h3>“电池自带”的强大标准库</h3>
<p>“标准库太棒了，” 社区普遍赞同，“你需要花些时间才能理解，在不引入单个依赖的情况下，你能做多少事情。” 从 HTTP 服务器到密码学工具，Go 的标准库提供了构建现代网络服务所需 90% 的功能，让初学者可以立即开始构建有价值的应用，而无需在茫茫的第三方库中选择和配置。</p>
<h2>幻象的破灭——“简单”背后的隐藏复杂性</h2>
<p>当“蜜月期”结束，开发者开始构建更复杂的真实世界系统时，Go 的另一面便会逐渐显现。这份复杂性，并非来自语言本身，而是源于 Go 为了维持简单性，而将复杂性“转移”到的地方。</p>
<h3>并发：Go 的“光荣与荆棘”</h3>
<p>这是社区中被提及次数最多的“深水区”。Go 通过 goroutine 和 channel，将并发编程的门槛降到了前所未有的低度。然而，这种易用性也隐藏着巨大的风险。</p>
<blockquote>
<p>“理解并发作为一个概念可能会很复杂，但 Go 让实现它变得简单。”</p>
</blockquote>
<p>但“实现简单”不等于“用对简单”。</p>
<ul>
<li><strong>Goroutine 泄露</strong>：新手很容易创建出无人“负责”的 goroutine，导致其在后台永久运行，悄无声息地消耗内存和 CPU。</li>
<li><strong>竞态条件 (Race Conditions)</strong>：尽管 Go 提供了强大的竞态检测器 (-race)，但理解和避免数据竞争，需要对内存模型和同步原语（如 sync.Mutex）有深刻的理解。</li>
<li><strong>Channel 的滥用</strong>：“我数不清有多少次，人们到处使用 goroutine 和 channel，然后好奇为什么他们的项目变得如此之慢。” Channel 是强大的工具，但错误地使用无缓冲 channel、忘记关闭 channel、或用它来解决本该用互斥锁解决的问题，都会导致死锁、性能下降和难以调试的 bug。</li>
</ul>
<p><strong>精通并发，是区分 Go 新手与专家的第一道分水岭。</strong></p>
<h3>运维复杂性</h3>
<p>Go 的设计哲学，在某些方面将应用程序的韧性责任，从语言运行时“推”给了基础设施。这为 Go 程序带来了一种独特的<strong>运维复杂性</strong>。</p>
<p>最典型的例子就是 <strong>panic 的处理</strong>。</p>
<ul>
<li>在某些语言中（如 Java），一个未捕获的异常通常只会导致单个线程死亡，而整个应用程序进程会默认继续运行。</li>
<li>但在 Go 中，一个未被 recover 的 panic 会导致<strong>整个程序（进程）立即崩溃退出</strong>。Go 语言本身不提供自动重启或进程守护的能力，它将这种“灾难恢复”的职责，明确地交给了程序的运行环境。</li>
</ul>
<p>这意味着，构建一个高可用的 Go 服务，你<strong>必须</strong>依赖外部系统。正如一位资深开发者在讨论中指出的那样：</p>
<blockquote>
<p>“像 panic 这样的东西，要求你在一个编排器（如 K8s/ECS 等）下运行你的生产系统。”</p>
</blockquote>
<p>这种设计选择，对于新手来说可能是一个认知上的巨大跳跃。他们必须明白，Go 程序的健壮性，并不仅仅是代码层面的 if err != nil，更是在<strong>基础设施层面</strong>，通过配置进程管理器（如 systemd）或容器编排器（如 Kubernetes）的健康检查和自动重启策略来共同保证的。</p>
<p>Go 将自己定位为一个用于构建云原生应用的“零件”，而非一个大包大揽的“一体机”。这种对运维环境的<strong>隐性依赖</strong>，正是其简单性背后的一种深刻权衡。</p>
<h3>“魔鬼在细节中”：切片、接口与错误处理</h3>
<p>Go 的一些核心特性，虽然表面简单，但其底层机制却充满了需要深入理解的“微妙之处”。</p>
<ul>
<li><strong>切片 (Slices)</strong>：新手常常会对其“共享底层数组”的行为感到困惑，不经意间写出因 append 操作导致意外数据修改的 bug。</li>
<li><strong>接口 (Interfaces)</strong>：nil 接口与“值为 nil 的接口”之间的区别，是无数 Gopher 都曾踩过的经典“坑”。</li>
<li><strong>错误处理的冗长</strong>：if err != nil 虽然明确，但在 LLM 辅助编码时代到来之前，这种冗长曾是许多开发者的抱怨之源。现在，新的挑战变成了如何确保依赖 AI 的新手，能真正理解他们生成的每一行错误处理代码。</li>
</ul>
<h2>精通之路——从“知道”到“理解”</h2>
<p>那么，如何跨越从“简单”到“精通”的鸿沟？社区的智慧为我们指明了方向。</p>
<h3>接受 Go 的哲学</h3>
<p>Go 是一门<strong>“刻意设计的简单语言”</strong>。它的目标，是让大型团队能够编写出风格统一、易于阅读和维护的代码。这意味着，你需要接受它的“冗长”，理解它为何抵制某些“高级”特性，并学会在其提供的“约束”下优雅地解决问题。</p>
<h3>刻意练习核心概念</h3>
<p>不要满足于 API 的表面用法。花时间去：</p>
<ul>
<li><strong>画图理解并发模式</strong>：亲自绘制 goroutine 如何通过 channel 通信，理解扇入 (fan-in)、扇出 (fan-out) 等模式。</li>
<li><strong>实验切片的底层行为</strong>：编写小程序来观察 append 何时会触发底层数组的重新分配。</li>
<li><strong>深入标准库源码</strong>：阅读 net/http 或 context 包的源码，是理解 Go 设计哲学的最佳途径。</li>
</ul>
<h3>拥抱“造轮子”</h3>
<p>“你经常需要‘自己动手造轮子’(roll your own)”，一位开发者评论道。这在 Go 的世界里并非贬义。Go 强大的标准库为你提供了高质量的“零件”，鼓励你根据自己的具体需求，组合出最适合的“轮子”，而不是像其他生态那样，总是先去寻找一个庞大、臃肿的“现成汽车”。</p>
<h2>小结：“简单”是起点，而非终点</h2>
<p>回到最初的问题：Go 语言真的简单吗？</p>
<p><strong>是的，Go 的入口极其简单。</strong> 它拥有平缓的学习曲线，让有经验的程序员可以在一周内上手，让新手也能在短时间内构建出有用的程序。</p>
<p><strong>但精通 Go 绝不简单。</strong> 它的真正深度，不在于复杂的语法，而在于理解其并发模型背后的权衡、标准库设计的精妙、以及在简约哲学约束下构建复杂系统的工程智慧。</p>
<p>正如一位评论者所引用的那句古老格言：“<strong>一分钟学会，一辈子精通。</strong>” 虽说“一辈子”有些夸张，但这或许是对 Go 语言简单性与复杂性辩证关系的最佳诠释。Go 的“简单”，为你打开了一扇通往高效、可靠软件工程的大门，但门后的风景，需要你用持续的学习和深刻的思考，去亲自探索和领悟。</p>
<p>资料链接：https://www.reddit.com/r/golang/comments/1oj9jb6/golang_seems_so_simple_am_i_wrong_to_assume_that/</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p><strong>想系统学习Go，构建扎实的知识体系？</strong></p>
<p>我的新书《<a href="https://book.douban.com/subject/37499496/">Go语言第一课</a>》是你的首选。源自2.4万人好评的极客时间专栏，内容全面升级，同步至Go 1.24。首发期有专属五折优惠，不到40元即可入手，扫码即可拥有这本300页的Go语言入门宝典，即刻开启你的Go语言高效学习之旅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-primer-published-4.png" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/11/07/go-simple-illusion-easy-to-learn-hard-to-master/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>写作即思考：AI 时代，开发者为什么要警惕“思考外包”？</title>
		<link>https://tonybai.com/2025/07/25/writing-is-thinking/</link>
		<comments>https://tonybai.com/2025/07/25/writing-is-thinking/#comments</comments>
		<pubDate>Fri, 25 Jul 2025 05:16:34 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[Nature]]></category>
		<category><![CDATA[代码]]></category>
		<category><![CDATA[写作]]></category>
		<category><![CDATA[外包]]></category>
		<category><![CDATA[大模型]]></category>
		<category><![CDATA[工具]]></category>
		<category><![CDATA[幻觉]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[效率]]></category>
		<category><![CDATA[核心能力]]></category>
		<category><![CDATA[社论]]></category>
		<category><![CDATA[系统设计]]></category>
		<category><![CDATA[编码]]></category>
		<category><![CDATA[自然杂志]]></category>
		<category><![CDATA[设计决策]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4951</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/07/25/writing-is-thinking 大家好，我是Tony Bai。 最近，全球顶级的科学期刊《自然》(Nature) 发表了一篇社论，标题仅有三个词：“Writing is thinking” (写作即思考)。 这篇社论探讨的是大语言模型时代人类生成的科学写作的价值，其核心观点，对于我们技术领域的开发者、工程师和内容创作者来说，不啻为一记振聋发聩的警示。它在 AI 浪潮席卷一切的今天，迫使我们重新审视一个被我们日渐忽视，却又至关重要的行为——我们自己的思考过程。 开发者版本的“写作即思考” 对开发者而言，“写作”的形式多种多样： * 编写一份详尽的技术设计文档 (Design Doc / RFC)。 * 撰写一篇分享经验的技术博客。 * 甚至，是构建一个结构清晰、逻辑严谨的复杂软件模块。 这些行为的本质，都和《自然》社论的观点一致：它不仅仅是“报告结果”，更是一个强迫我们将脑中混乱、非线性的想法，梳理成结构化、有意图的叙事的过程。 当你无法用清晰的语言（或代码）写下来时，通常意味着你对这个问题还没有想清楚。这个“写”的过程，本身就是发现逻辑漏洞、提炼核心思想、明确最终影响力的思考过程。正如社论所引述的科学依据：书写行为本身，就能增强大脑的连接性，并对学习和记忆产生积极影响。 AI 带来的“隐形危机”：思考外包 现在，强大的 LLM 出现了。我们似乎可以轻易地“外包”掉这个艰苦的思考过程。 “帮我生成一份关于XX系统的微服务架构设计文档。” “为我刚才的 Go 函数编写一份详细的单元测试。” AI 瞬间就能产出看似完美的“结果”。但在这个过程中，我们失去了什么？ 效率的假象： AI 会产生幻觉。你可能需要花费更多的心力去验证、修正和编辑一份由 AI 生成的、你并不完全理解的复杂文档或代码，其成本甚至可能超过从零开始亲自撰写。 思想的归属： 社论提出了一个尖锐的问题——如果“写作即思考”，那么当你在阅读一篇由 LLM 生成的论文时，你读到的究竟是研究者的思想，还是 LLM 的“思想”？同理，当你的同事向你展示一份由 AI 生成的设计文档时，这背后真的有他深入的思考和权衡吗？ 核心能力的侵蚀： 这是最危险的一点。我们跳过了最宝贵的思考整理过程，直接获取了“结果”，却失去了将知识和经验内化为自身能力的宝贵“过程”。我们放弃了锻炼自己核心思维能力的机会。 从“外包者”到“放大器”：AI 的正确使用姿势 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/writing-is-thinking-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/07/25/writing-is-thinking">本文永久链接</a> &#8211; https://tonybai.com/2025/07/25/writing-is-thinking</p>
<p>大家好，我是Tony Bai。</p>
<p>最近，全球顶级的科学期刊《自然》(Nature) 发表了一篇社论，标题仅有三个词：“Writing is thinking” (写作即思考)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/writing-is-thinking-2.jpg" alt="" /></p>
<p>这篇社论探讨的是大语言模型时代人类生成的科学写作的价值，其核心观点，对于我们技术领域的开发者、工程师和内容创作者来说，不啻为一记振聋发聩的警示。它在 AI 浪潮席卷一切的今天，迫使我们重新审视一个被我们日渐忽视，却又至关重要的行为——我们自己的思考过程。</p>
<h2>开发者版本的“写作即思考”</h2>
<p>对开发者而言，“写作”的形式多种多样：<br />
*   编写一份详尽的技术设计文档 (Design Doc / RFC)。<br />
*   撰写一篇分享经验的技术博客。<br />
*   甚至，是构建一个结构清晰、逻辑严谨的复杂软件模块。</p>
<p>这些行为的本质，都和《自然》社论的观点一致：<strong>它不仅仅是“报告结果”，更是一个强迫我们将脑中混乱、非线性的想法，梳理成结构化、有意图的叙事的过程。</strong></p>
<p>当你无法用清晰的语言（或代码）写下来时，通常意味着你对这个问题还没有想清楚。这个“写”的过程，本身就是发现逻辑漏洞、提炼核心思想、明确最终影响力的<strong>思考过程</strong>。正如社论所引述的科学依据：书写行为本身，就能增强大脑的连接性，并对学习和记忆产生积极影响。</p>
<h2>AI 带来的“隐形危机”：思考外包</h2>
<p>现在，强大的 LLM 出现了。我们似乎可以轻易地“外包”掉这个艰苦的思考过程。</p>
<blockquote>
<p>“帮我生成一份关于XX系统的微服务架构设计文档。”<br />
  “为我刚才的 Go 函数编写一份详细的单元测试。”</p>
</blockquote>
<p>AI 瞬间就能产出看似完美的“结果”。但在这个过程中，我们失去了什么？</p>
<ol>
<li><strong>效率的假象：</strong> AI 会产生幻觉。你可能需要花费更多的心力去验证、修正和编辑一份由 AI 生成的、你并不完全理解的复杂文档或代码，其成本甚至可能超过从零开始亲自撰写。</li>
<li><strong>思想的归属：</strong> 社论提出了一个尖锐的问题——如果“写作即思考”，那么当你在阅读一篇由 LLM 生成的论文时，你读到的究竟是研究者的思想，还是 LLM 的“思想”？同理，当你的同事向你展示一份由 AI 生成的设计文档时，这背后真的有他深入的思考和权衡吗？</li>
<li><strong>核心能力的侵蚀：</strong> 这是最危险的一点。我们跳过了最宝贵的思考整理过程，直接获取了“结果”，却失去了将知识和经验<strong>内化</strong>为自身能力的宝贵“过程”。我们放弃了锻炼自己核心思维能力的机会。</li>
</ol>
<h2>从“外包者”到“放大器”：AI 的正确使用姿势</h2>
<p>那么，我们应该抵制 AI 吗？当然不。《自然》的社论也明确指出，AI 是一个极其有价值的<strong>辅助工具</strong>。问题的关键，不在于用不用，而在于怎么用。</p>
<p>我们应该警惕成为一个“思考外包者”，转而努力成为一个“思考放大器”的使用者。</p>
<p>这意味着，永远由<strong>人类</strong>掌握“思考”的主导权，而在特定的、非核心思考的环节，利用 AI 来提升效率。以下是一些高效的“放大器”模式：</p>
<ul>
<li><strong>语法与可读性优化器：</strong> “这是我写的一段技术描述，请帮我润色，使其更易于理解，并修正语法错误。”</li>
<li><strong>信息检索与综述助理：</strong> “帮我总结一下最近关于 QUIC 协议的三篇关键论文的核心观点。”</li>
<li><strong>头脑风暴伙伴：</strong> “我正在设计一个高可用的缓存系统，请帮我列出可能需要考虑的 10 个潜在故障点。”</li>
<li><strong>“破冰”与“思路转换”工具：</strong> “我对于如何向非技术人员解释‘幂等性’感到卡壳，请提供三种不同的比喻或解释方式。”</li>
</ul>
<p>在这些场景中，AI 是你的研究助理、语法老师、灵感催化剂，但绝不是替你完成核心思考的“枪手”。</p>
<h2>未来已来：从“代码实现者”到“思想叙事者”</h2>
<p>这场关于“写作与思考”的讨论，最终引向了一个更宏大的问题：当 AI 越来越擅长“写作”（即编码实现）时，我们人类工程师的不可替代价值到底在哪里？</p>
<p>答案或许就在《自然》社论的结尾：“将整个写作过程外包给 LLM，会剥夺我们反思和塑造引人入胜的叙事的机会”。</p>
<p>未来工程师的核心竞争力，正在从单纯的<strong>技术实现</strong>，向上游转移。以下三项“元技能”将变得至关重要：</p>
<ul>
<li>深度反思能力: 对技术领域、业务场景进行深刻的洞察和反思，理解“为什么”远比“怎么做”更重要。</li>
<li>创造性任务处理能力: 定义正确的问题，做出关键的架构取舍，进行富有创造力的系统设计。</li>
<li>思想叙事能力: 能够将复杂的技术决策、系统设计，用清晰、有说服力的“故事”（设计文档、技术演讲、甚至代码结构本身）讲述出来，影响和说服他人。</li>
</ul>
<p>你看，这三项无法被 AI 替代的核心能力，恰恰都是通过“写作即思考”这个艰苦而宝贵的过程来培养和强化的。</p>
<h2>小结：别让 AI 替你思考</h2>
<p>AI 是一场革命性的技术浪潮，它正在重塑我们的工作方式。但我们必须保持清醒：AI 是我们手中的工具，而不是我们大脑的替代品。</p>
<p>我们可以，也应该，让 AI 成为我们强大的“副驾驶”，帮我们处理繁琐的事务，为我们提供新的视角。但方向盘，必须始终握在我们自己手中，谨慎和正确使用带有深度思考和推理功能的AI大模型。</p>
<p>因为我们写下的每一行代码，每一份文档，不仅仅是交付物，更是我们思考过程的凝结与沉淀。这个过程，才是我们作为工程师，最宝贵的财富。</p>
<p>资料链接：https://www.nature.com/articles/s44222-025-00323-4</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/go-advanced-course-4.png" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/07/25/writing-is-thinking/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>探讨Docker容器中修改系统变量的方法</title>
		<link>https://tonybai.com/2014/10/14/discussion-on-the-approach-to-modify-system-variables-in-docker/</link>
		<comments>https://tonybai.com/2014/10/14/discussion-on-the-approach-to-modify-system-variables-in-docker/#comments</comments>
		<pubDate>Tue, 14 Oct 2014 13:56:12 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[phusion]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[SharedMemory]]></category>
		<category><![CDATA[shmget]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[共享内存]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[虚拟化]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1563</guid>
		<description><![CDATA[探讨完Docker对共享内存状态持久化的支持状况后，我将遗留产品build到一个pre-production image中，测试启动是否OK。很显然，我过于乐观了，Docker之路并不平坦。我收到了shmget报出的EINVAL错误码，提示参数非法。 shmget的manual对EINVAL错误码的说明如下： EINVAL： A&#160; new&#160; segment&#160; was&#160; to&#160; be&#160; created&#160; and size &#60; SHMMIN or size &#62; SHMMAX, or no new segment was to be created, a segment with given key existed, but size is greater than the size of that segment. 显然我们要创建的shared memory的size很可能大于SHMMAX这个系统变量了。那么一个从base image创建出的容器中的系统变量到底是什么值呢？我们来查看一下，我们基于&#34;centos:centos6&#34;启动一个Docker容器，并检查其中的 系统变量值设置： $ sudo docker run -it &#34;centos:centos6&#34; /bin/bash bash-4.1# [...]]]></description>
			<content:encoded><![CDATA[<p>探讨完<a href="http://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/">Docker对共享内存状态持久化的支持状况</a>后，我将遗留产品build到一个pre-production image中，测试启动是否OK。很显然，我过于乐观了，<a href="http://www.docker.com">Docker</a>之路并不平坦。我收到了shmget报出的EINVAL错误码，提示参数非法。 shmget的manual对EINVAL错误码的说明如下：</p>
<p><font face="Courier New">EINVAL：<br />
	A&nbsp; new&nbsp; segment&nbsp; was&nbsp; to&nbsp; be&nbsp; created&nbsp; and size &lt; SHMMIN or size &gt; SHMMAX, or no new segment was to be created, a segment with given key existed, but size is greater than the size of that segment.</font></p>
<p>显然我们要创建的shared memory的size很可能大于SHMMAX这个系统变量了。那么一个从base image创建出的容器中的系统变量到底是什么值呢？我们来查看一下，我们基于&quot;centos:centos6&quot;启动一个Docker容器，并检查其中的 系统变量值设置：</p>
<p><font face="Courier New">$ sudo docker run -it &quot;centos:centos6&quot; /bin/bash<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	33554432<br />
	bash-4.1# sysctl -a|grep shmmax<br />
	kernel.shmmax = 33554432</font></p>
<p>可以看出默认情况下，当前容器中root账号看到的shmmax值我<font face="Courier New">33554432</font>， 我的程序要创建的shm size的确要大于这个值，报出EINVAL错误也就无可厚非了。我尝试按照物理机上的方法临时修改一下该值：</p>
<p><font face="Courier New">bash-4.1# echo 68719476736 &gt; /proc/sys/kernel/shmmax<br />
	bash: /proc/sys/kernel/shmmax: Read-only file system</font></p>
<p><font face="Courier New">/proc/sys/kernel/shmmax居然是只读的，无法修改。</font></p>
<p><font face="Courier New">我又尝试修改/etc/sysctl.conf这个持久化系统变量的地方，但打开/etc/sysctl.conf文件，我发现我又错了，这 个文件中shmmax的值如下：</font></p>
<p><font face="Courier New"># Controls the maximum shared segment size, in bytes<br />
	kernel.shmmax = 68719476736</font></p>
<p><font face="Courier New"><font face="Courier New">/etc/sysctl.conf文件 中的系统变量shmmax的值是68719476736，而系统当前的实际值则是33554432，难道是/etc /sysctl.conf中的值没有生效，于是我手工重新加载一次该文件：</font></font></p>
<p><font face="Courier New"><font face="Courier New">-bash-4.1# sysctl -p<br />
	error: &quot;Read-only file system&quot; setting key &quot;net.ipv4.ip_forward&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;net.ipv4.conf.default.rp_filter&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;net.ipv4.conf.default.accept_source_route&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.sysrq&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.core_uses_pid&quot;<br />
	error: &quot;net.ipv4.tcp_syncookies&quot; is an unknown key<br />
	error: &quot;net.bridge.bridge-nf-call-ip6tables&quot; is an unknown key<br />
	error: &quot;net.bridge.bridge-nf-call-iptables&quot; is an unknown key<br />
	error: &quot;net.bridge.bridge-nf-call-arptables&quot; is an unknown key<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.msgmnb&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.msgmax&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.shmmax&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.shmall&quot;</font></font></p>
<p><font face="Courier New"><font face="Courier New">我得到了和之前类似的错误结果：只读文件系统，无法修改。于是乎两个问题萦绕在我的面前：<br />
	1、为什么容器内当前系统变量值与sysctl.conf中的不一致？<br />
	2、为什么无法修改当前系统变量值?</font></font></p>
<p><font face="Courier New"><font face="Courier New">在翻阅了Stackoverflow, github docker issues后，我得到了的答案如下：</font></font></p>
<p><font face="Courier New"><font face="Courier New">1、Docker的base image做的很精简，甚至都没有init进程，原本在OS启动时执行生效系统变量的过程(sysctl -p)也给省略了，导致这些系统变量依旧保留着kernel默认值。以CentOs为例，在linux kernel boot后，init都会执行/etc/rc.d/rc.sysinit，后者会加载/etc/sysctl.conf中的系统变量值。下面是 CentOs5.6中的rc.sysinit代码摘录：</font></font></p>
<p><font face="Courier New"><font face="Courier New">&#8230; &#8230;<br />
	# Configure kernel parameters<br />
	update_boot_stage RCkernelparam<br />
	sysctl -e -p /etc/sysctl.conf &gt;/dev/null 2&gt;&amp;1<br />
	&#8230; &#8230;</font></font></p>
<p><font face="Courier New"><font face="Courier New">2、Docker容器中的系统变量在non-priviledged模式下目前(我使用的时docker 1.2.0版本)就无法修改，</font></font>这 和resolv.conf、hosts等文件映射到宿主机对应的文件有不同。</p>
<p><font face="Courier New">$ mount -l<br />
	&#8230;. &#8230;.<br />
	/dev/mapper/ubuntu&#8211;Server&#8211;14&#8211;vg-root on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro,data=ordered)<br />
	/dev/mapper/ubuntu&#8211;Server&#8211;14&#8211;vg-root on /etc/hostname type ext4 (rw,relatime,errors=remount-ro,data=ordered)<br />
	/dev/mapper/ubuntu&#8211;Server&#8211;14&#8211;vg-root on /etc/hosts type ext4 (rw,relatime,errors=remount-ro,data=ordered)<br />
	&#8230; &#8230;</font></p>
<p>那么我们该如何修改系统变量值来满足遗留产品的需求呢？</p>
<p><b>一、使用&#8211;privileged选项</b></p>
<p>我们使用&#8211;privileged这个特权选项来启动一个基于centos:centos6的新容器，看看是否能对shmmax这样的系统变量值 进行修改：</p>
<p><font face="Courier New">$ sudo docker run -it &#8211;privileged&nbsp; &quot;centos:centos6&quot; /bin/bash<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	33554432<br />
	bash-4.1# echo 68719476736 &gt; /proc/sys/kernel/shmmax<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	68719476736</font><br />
	<font face="Courier New">bash-4.1# sysctl -p<br />
	net.ipv4.ip_forward = 0<br />
	net.ipv4.conf.default.rp_filter = 1<br />
	net.ipv4.conf.default.accept_source_route = 0<br />
	kernel.sysrq = 0<br />
	kernel.core_uses_pid = 1<br />
	&#8230; &#8230;<br />
	kernel.msgmnb = 65536<br />
	kernel.msgmax = 65536<br />
	kernel.shmmax = 68719476736<br />
	kernel.shmall = 4294967296</font></p>
<p>可以看出，通过&#8211;privileged选项，容器获得了额外的特权，并且可以对系统变量的值进行修改了。不过这样的修改是不能保存在容器里的， 我们stop 容器，再重启该容器就能看出来：</p>
<p><font face="Courier New">$ sudo docker start 3e22d65a7845<br />
	$ sudo docker attach 3e22d65a7845<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	33554432</font></p>
<p>shmmax的值在容器重启后又变回了原先的那个默认值。不过重启后的容器依旧具有privileged的特权，我们还可以重新手工执行命令对系 统变量进行修改：</p>
<p><font face="Courier New">bash-4.1# echo 68719476736 &gt; /proc/sys/kernel/shmmax<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	68719476736</font></p>
<p>但即便这样，也无法满足我们的需求，我们总不能每次都在容器中手工执行系统变量值修改的操作吧。privileged选项的能力能否带到 image中呢？答案是目前还不能，我们无法在build image时通过privileged选项修改系统变量值。</p>
<p>这样一来，我们能做的只有把产品启动与系统变量值修改放在一个脚本中了，并将该脚本作为docker 容器的cmd命令来执行，比如我们构建一个Dockerfile：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	RUN yum install python-setuptools -y<br />
	RUN easy_install supervisor<br />
	RUN mkdir -p /var/log/supervisor<br />
	COPY ./supervisord.conf /etc/supervisord.conf<br />
	COPY ./start.sh /bin/start.sh<br />
	RUN chmod +x /bin/start.sh<br />
	CMD ["/bin/start.sh]</font></p>
<p><font face="Courier New">//start.sh<br />
	sysctl -p<br />
	/usr/bin/supervisord</font></p>
<p>这样，start.sh在supervisord启动前将系统变量值重新加载，而supervisord后续启动的程序就可以看到这些新系统变量 的值了。不过别忘了利用这个image启动容器时要加上&#8211;priviledged选项，否则容器启动就会失败。</p>
<p><b>二、使用phusion/baseimage</b></p>
<p>前面说过/etc/sysctl.conf中的值没有生效是因为docker官方提供的centos:centos6把init进程的初始化过程给精 简掉了。<a href="https://registry.hub.docker.com/u/phusion/baseimage/">phusion/baseimage</a>是目前docker registery上仅次于ubuntu和centos两个之后的base image，其提供了/sbin/my_init这个init进程，用于在container充当init进程的角色。那么my_init是否可以用于执行sysctl -p呢？我们试验一下：</p>
<p>我们先pull这个base image下来：<font face="Courier New">sudo docker pull phusion/baseimage。pull成功后，我们先基于&ldquo;phusion/baseimage&rdquo;启动一个容器做一些explore工作：</font></p>
<p><font face="Courier New">$ sudo docker run -i -t &quot;phusion/baseimage&quot;<br />
	*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh&#8230;<br />
	No SSH host key available. Generating one&#8230;<br />
	Creating SSH2 RSA key; this may take some time &#8230;<br />
	Creating SSH2 DSA key; this may take some time &#8230;<br />
	Creating SSH2 ECDSA key; this may take some time &#8230;<br />
	Creating SSH2 ED25519 key; this may take some time &#8230;<br />
	invoke-rc.d: policy-rc.d denied execution of restart.<br />
	*** Running /etc/rc.local&#8230;<br />
	*** Booting runit daemon&#8230;<br />
	*** Runit started as PID 100</font></p>
<p><font face="Courier New">通过nsenter进去，查看一下/sbin/my_init的源码，我们发现这是一个python脚本，不过从头到尾浏览一遍，没有发现sysctl加载/etc/sysctl.conf系统变量的操作。</font></p>
<p><font face="Courier New">不过，phusion文档中说my_init可以在初始化过程中执行/etc/my_init.d下的脚本。那是不是我们将一个执行sysctl -p的脚本放入/etc/my_init.d下就可以实现我们的目的了呢？试试。</font></p>
<p><font face="Courier New">我们编写一个脚本：load_sys_varibles.sh</font></p>
<p><font face="Courier New">#!/bin/sh<br />
	sysctl -p &gt; init.txt</font></p>
<p><font face="Courier New">下面是制作image的Dockerfile:</font></p>
<p><font face="Courier New">FROM phusion/baseimage:latest<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	RUN echo &quot;kernel.shmmax = 68719476736&quot; &gt;&gt; /etc/sysctl.conf<br />
	RUN mkdir -p /etc/my_init.d<br />
	ADD load_sys_varibles.sh /etc/my_init.d/load_sys_varibles.sh<br />
	RUN chmod +x /etc/my_init.d/load_sys_varibles.sh<br />
	CMD ["/sbin/my_init"]</font></p>
<p><font face="Courier New">phusion/baseimage是基于ubuntu的OS，其sysctl.conf默认情况下没啥内容，所以我们在Dockerfile中向这个文件写入我们需要的系统变量值。构建image并启动容器：</font></p>
<p><font face="Courier New">$ sudo docker build -t &quot;myphusion:v1&quot; ./<br />
	Sending build context to Docker daemon 13.12 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM phusion/baseimage:latest<br />
	&nbsp;&#8212;&gt; cf39b476aeec<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; d0e9b51a3e4f<br />
	Step 2 : RUN echo &quot;kernel.shmmax = 68719476736&quot; &gt;&gt; /etc/sysctl.conf<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 2c800687cc83<br />
	Step 3 : RUN mkdir -p /etc/my_init.d<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; fe366eea5eb4<br />
	Step 4 : ADD load_sys_varibles.sh /etc/my_init.d/load_sys_varibles.sh<br />
	&nbsp;&#8212;&gt; a641bb595fb9<br />
	Removing intermediate container c381b9f001c2<br />
	Step 5 : RUN chmod +x /etc/my_init.d/load_sys_varibles.sh<br />
	&nbsp;&#8212;&gt; Running in 764866552f25<br />
	&nbsp;&#8212;&gt; eae3d7f1eac5<br />
	Removing intermediate container 764866552f25<br />
	Step 6 : CMD ["/sbin/my_init"]<br />
	&nbsp;&#8212;&gt; Running in 9ab8d0b717a7<br />
	&nbsp;&#8212;&gt; 8be4e7b6b174<br />
	Removing intermediate container 9ab8d0b717a7<br />
	Successfully built 8be4e7b6b174</font></p>
<p><font face="Courier New">$ sudo docker run -it &quot;myphusion:v1&quot;<br />
	*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh&#8230;<br />
	No SSH host key available. Generating one&#8230;<br />
	Creating SSH2 RSA key; this may take some time &#8230;<br />
	Creating SSH2 DSA key; this may take some time &#8230;<br />
	Creating SSH2 ECDSA key; this may take some time &#8230;<br />
	Creating SSH2 ED25519 key; this may take some time &#8230;<br />
	invoke-rc.d: policy-rc.d denied execution of restart.<br />
	*** Running /etc/my_init.d/load_sys_varibles.sh&#8230;<br />
	sysctl: setting key &quot;kernel.shmmax&quot;: Read-only file system<br />
	*** /etc/my_init.d/load_sys_varibles.sh failed with status 255</font></p>
<p><font face="Courier New">*** Killing all processes&#8230;</font></p>
<p><font face="Courier New">唉，还是老问题！即便是在my_init中执行，依旧无法逾越Read-only file system，查看Phusion/baseimage的Dockerfile才知道，它也是From ubuntu:14.04的，根不变，上层再怎么折腾也没用。</font></p>
<p><font face="Courier New">换一种容器run方法吧，加上&#8211;privileged：</font></p>
<p><font face="Courier New">$ sudo docker run -it &#8211;privileged&nbsp; &quot;myphusion:v1&quot;<br />
	*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh&#8230;<br />
	No SSH host key available. Generating one&#8230;<br />
	Creating SSH2 RSA key; this may take some time &#8230;<br />
	Creating SSH2 DSA key; this may take some time &#8230;<br />
	Creating SSH2 ECDSA key; this may take some time &#8230;<br />
	Creating SSH2 ED25519 key; this may take some time &#8230;<br />
	invoke-rc.d: policy-rc.d denied execution of restart.<br />
	*** Running /etc/my_init.d/load_sys_varibles.sh&#8230;<br />
	*** Running /etc/rc.local&#8230;<br />
	*** Booting runit daemon&#8230;<br />
	*** Runit started as PID 102</font></p>
<p><font face="Courier New">这回灵光了。enter到容器里看看设置的值是否生效了：</font></p>
<p><font face="Courier New">root@9e399f46372a:~#cat /proc/sys/kernel/shmmax<br />
	68719476736</font></p>
<p><font face="Courier New">结果如预期。这样来看phusion/baseimage算是为sysctl -p加载系统变量值提供了一个便利，但依旧无法脱离&#8211;privileged，且依旧无法在image中持久化这个设置。</font></p>
<p><font face="Courier New">在Docker github的issue中有人提出建议在Dockerfile中加入类似RUNP这样的带有特权的指令语法，但不知何时才能在Docker中加入这一功能。</font></p>
<p><font face="Courier New">总而言之，基于目前docker官网提供的base image，我们很难找到特别理想的修改系统变量值的方法，除非自己制作base image，这个还没尝试过，待后续继续研究。</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/14/discussion-on-the-approach-to-modify-system-variables-in-docker/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>探讨docker容器对共享内存的支持情况</title>
		<link>https://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/</link>
		<comments>https://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/#comments</comments>
		<pubDate>Sat, 11 Oct 2014 21:23:34 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[IPC]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[mmap]]></category>
		<category><![CDATA[POSIX]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[SharedMemory]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[共享内存]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1560</guid>
		<description><![CDATA[我们的遗留系统广泛使用了性能最佳的IPC方式 &#8211; 共享内存，而且用到了两种共享内存的实现方式：System V共享内存(shmget、shmat、shmdt)以及Mmap映射Regular File。System V共享内存支持一定程度上的内存数据持久化，即当程序创建共享内存对象后，如果不显式删除或物理主机重启，该IPC对象会一直保留，其中的数据也不会丢 失；mmap映射Regular File的方式支持内存数据持久化到文件中，即便物理主机重启，这部分数据依旧不会丢失，除非显式删除文件。这两个共享内存机制，尤其是其持久化的特性是 我们的系统所依赖的。但是在Docker容器中，这两种共享内存机制依旧能被很好的支持吗？我们通过试验来分析一下。 一、System V共享内存 一个启动的Docker容器就是一个拥有了自己的内核名字空间的进程，其pid、net、ipc、mnt、uts、user等均与其他进程隔离，对于运行于该容器内的程序而言，它仿佛会觉得它独占了一台&#8220;主机&#8221;。对于这类&#8220;主机&#8221;，我们首先来测试一下其中的system v共享内存是否依旧能像物理主机上一样，在程序退出后依旧能保持持久化？在容器退出后能保持么？ 我们先来写两个测试程序，一个用于创建system v共享内存，并写入一些数据，另外一个程序则映射该共享内存并尝试读出内存中的数据。由于Golang目前仍未提供对System V共享内存的高级封装接口，通过syscall包的Syscall调用又太繁琐，因此我们直接使用C代码与Go代码结合的方式实现这两个测试程序。之前写 过一篇名为《Go与C语言互操作》的博文，看不懂下面代码的朋友，可以先阅读一下这篇文章。 //systemv_shm_wr.go package main //#include &#60;sys/types.h&#62; //#include &#60;sys/ipc.h&#62; //#include &#60;sys/shm.h&#62; //#include &#60;stdio.h&#62; // //#define SHMSZ&#160;&#160;&#160;&#160; 27 // //int shm_wr() { //&#160;&#160;&#160; char c; //&#160;&#160;&#160; int shmid; //&#160;&#160;&#160; key_t key; //&#160;&#160;&#160; char *shm, *s; // //&#160;&#160;&#160; key = 5678; // [...]]]></description>
			<content:encoded><![CDATA[<p>我们的遗留系统广泛使用了性能最佳的IPC方式 &#8211; <a href="http://en.wikipedia.org/wiki/Shared_memory">共享内存</a>，而且用到了两种共享内存的实现方式：System V共享内存(shmget、shmat、shmdt)以及Mmap映射Regular File。System V共享内存支持一定程度上的内存数据持久化，即当程序创建共享内存对象后，如果不显式删除或物理主机重启，该IPC对象会一直保留，其中的数据也不会丢 失；mmap映射Regular File的方式支持内存数据持久化到文件中，即便物理主机重启，这部分数据依旧不会丢失，除非显式删除文件。这两个共享内存机制，尤其是其持久化的特性是 我们的系统所依赖的。但是在<a href="http://docker.com">Docker</a>容器中，这两种共享内存机制依旧能被很好的支持吗？我们通过试验来分析一下。</p>
<p><b>一、System V共享内存</b></p>
<p>一个启动的Docker容器就是一个拥有了自己的内核名字空间的进程，其<b>pid</b><b>、net</b><b>、ipc</b><b>、mnt</b><b>、uts</b><b>、user</b>等均与其他进程隔离，对于运行于该容器内的程序而言，它仿佛会觉得它独占了一台&ldquo;主机&rdquo;。对于这类&ldquo;主机&rdquo;，我们首先来测试一下其中的system v共享内存是否依旧能像物理主机上一样，在程序退出后依旧能保持持久化？在容器退出后能保持么？</p>
<p>我们先来写两个测试程序，一个用于创建system v共享内存，并写入一些数据，另外一个程序则映射该共享内存并尝试读出内存中的数据。由于Golang目前仍未提供对System V共享内存的高级封装接口，通过syscall包的Syscall调用又太繁琐，因此我们直接使用C代码与Go代码结合的方式实现这两个测试程序。之前写 过一篇名为《<a href="http://tonybai.com/2012/09/26/interoperability-between-go-and-c/">Go与C语言互操作</a>》的博文，看不懂下面代码的朋友，可以先阅读一下这篇文章。</p>
<p><font face="Courier New">//systemv_shm_wr.go<br />
	package main</font></p>
<p><font face="Courier New">//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/ipc.h&gt;<br />
	//#include &lt;sys/shm.h&gt;<br />
	//#include &lt;stdio.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_wr() {<br />
	//&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp; int shmid;<br />
	//&nbsp;&nbsp;&nbsp; key_t key;<br />
	//&nbsp;&nbsp;&nbsp; char *shm, *s;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; key = 5678;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; if ((shmid = shmget(key, SHMSZ, IPC_CREAT | 0666)) &lt; 0) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; s = shm;<br />
	//&nbsp;&nbsp;&nbsp; for (c = &#39;a&#39;; c &lt;= &#39;z&#39;; c++)<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *s++ = c;<br />
	//&nbsp;&nbsp;&nbsp; s = NULL;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_wr()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;SystemV Share Memory Create and Write Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;SystemV Share Memory Create and Write Ok&quot;)<br />
	}</font></p>
<p><font face="Courier New">//systemv_shm_rd.go</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/ipc.h&gt;<br />
	//#include &lt;sys/shm.h&gt;<br />
	//#include &lt;stdio.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_rd() {<br />
	//&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp; int shmid;<br />
	//&nbsp;&nbsp;&nbsp; key_t key;<br />
	//&nbsp;&nbsp;&nbsp; char *shm, *s;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; key = 5678;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; if ((shmid = shmget(key, SHMSZ, 0666)) &lt; 0) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; s = shm;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; int i = 0;<br />
	//&nbsp;&nbsp;&nbsp; for (i = 0; i &lt; SHMSZ-1; i++)<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%c &quot;, *(s+i));<br />
	//&nbsp;&nbsp;&nbsp; printf(&quot;\n&quot;);<br />
	//&nbsp;&nbsp;&nbsp; s = NULL;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_rd()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;SystemV Share Memory Create and Read Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;SystemV Share Memory Create and Read Ok&quot;)<br />
	}</font></p>
<p>我们通过go build构建上面两个程序，得到两个测试用可执行程序：systemv_shm_wr和systemv_shm_rd。下面我们来构建我们的测试用docker image，Dockerfile内容如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	COPY ./systemv_shm_wr /bin/<br />
	COPY ./systemv_shm_rd /bin/</font></p>
<p>构建Docker image：&ldquo;shmemtest:v1&rdquo;：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;shmemtest:v1&quot; ./<br />
	Sending build context to Docker daemon 16.81 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./systemv_shm_wr /bin/<br />
	&nbsp;&#8212;&gt; ea59fb767573<br />
	Removing intermediate container 4ce91720897b<br />
	Step 3 : COPY ./systemv_shm_rd /bin/<br />
	&nbsp;&#8212;&gt; 1ceb207b1009<br />
	Removing intermediate container 7ace7ad53a3f<br />
	Successfully built 1ceb207b1009</font></p>
<p>启动一个基于该image的容器：<br />
	<font face="Courier New">$ sudo docker run -it &quot;shmemtest:v1&quot; /bin/bash</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	0a2f37bee6eb&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shmemtest:v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 28 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 28 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elegant_hawking</font></p>
<p>进入容器，先后执行systemv_shm_wr和systemv_shm_rd，我们得到如下结果：</p>
<p><font face="Courier New">bash-4.1# systemv_shm_wr<br />
	SystemV Share Memory Create and Write Ok<br />
	bash-4.1# systemv_shm_rd<br />
	a b c d e f g h i j k l m n o p q r s t u v w x y z<br />
	SystemV Share Memory Create and Read Ok</font></p>
<p>在容器运行过程中，SystemV共享内存对象是可以持久化的。systemv_shm_wr退出后，数据依旧得以保留。我们接下来尝试一下重启container后是否还能读出数据：</p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	0a2f37bee6eb&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shmemtest:v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8 minutes ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 8 minutes&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elegant_hawking&nbsp;&nbsp;&nbsp;&nbsp;<br />
	$ sudo docker stop 0a2f37bee6eb<br />
	0a2f37bee6eb<br />
	$ sudo docker start 0a2f37bee6eb<br />
	0a2f37bee6eb<br />
	$ sudo docker attach 0a2f37bee6eb<br />
	bash-4.1# systemv_shm_rd<br />
	SystemV Share Memory Create and Read Error: -1</font></p>
<p>程序返回-1，显然在shmget时就出错了，系统已经没有了key为&quot;5678&quot;的这个共享内存IPC对象了。也就是说当容器stop时，就好比我们的物理主机关机，docker将该容器对应的共享内存IPC对象删除了。</p>
<p>从原理上分析，似乎我们也能得出此结论：毕竟Docker container是通过kernel namespace隔离的，容器中的进程在IPC资源申请时需要加入namespace信息。打个比方，如果我们启动容器的进程pid(物理主机视角)是 1234，那么这容器内进程申请的共享内存IPC资源（比如key=5678）的标识应该类似于&ldquo;1234:5678&rdquo;这样的形式。重启容器 后，Docker Daemon无法给该容器分配与上次启动相同的pid，因此pid发生了变化，之前容器中的&quot;1234:5678&quot;保留下来也是毫无意义的，还无端占用系 统资源。因此，System V IPC在Docker容器中的运用与物理机有不同，这方面要小心，目前似乎没有很好的方法，也许以后Docker会加入全局IPC，这个我们只能等待。</p>
<p><b>二、Mmap映射共享内存</b></p>
<p>接下来我们探讨mmap共享内存在容器中的支持情况。mmap常见的有两类共享内存映射方式，一种映射到/dev/zero，另外一种则是映射到 Regular Fiile。前者在程序退出后数据自动释放，后者则保留在映射的文件中。后者对我们更有意义，这次测试的也是后者。</p>
<p>同样，我们也先来编写两个测试程序。</p>
<p><font face="Courier New">//mmap_shm_wr.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">//#include &lt;stdio.h&gt;<br />
	//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/mman.h&gt;<br />
	//#include &lt;fcntl.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_wr()<br />
	//{<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *shm = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *s = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int fd;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((fd = open(&quot;./shm.txt&quot;, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) == -1)&nbsp; {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lseek(fd, 500, SEEK_CUR);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; write(fd, &quot;\0&quot;, 1);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lseek(fd, 0, SEEK_SET);<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shm = (char*)mmap(shm, SHMSZ, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!shm) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(fd);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = shm;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (c = &#39;a&#39;; c &lt;= &#39;z&#39;; c++) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *(s+(int)(c &#8211; &#39;a&#39;)) = c;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_wr()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Create and Write Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Create and Write Ok&quot;)<br />
	}</font></p>
<p><font face="Courier New">//mmap_shm_rd.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">//#include &lt;stdio.h&gt;<br />
	//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/mman.h&gt;<br />
	//#include &lt;fcntl.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_rd()<br />
	//{<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *shm = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *s = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int fd;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((fd = open(&quot;./shm.txt&quot;, O_RDONLY)) == -1)&nbsp; {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shm = (char*)mmap(shm, SHMSZ, PROT_READ, MAP_SHARED, fd, 0);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!shm) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(fd);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = shm;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int i = 0;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (i = 0; i &lt; SHMSZ &#8211; 1; i++) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%c &quot;, *(s + i));<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;\n&quot;);<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_rd()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Read Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Read Ok&quot;)<br />
	}</font></p>
<p>我们通过go build构建上面两个程序，得到两个测试用可执行程序：mmap_shm_wr和mmap_shm_rd。下面我们来构建我们的测试用docker image，Dockerfile内容如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	COPY ./mmap_shm_wr /bin/<br />
	COPY ./mmap_shm_rd /bin/</font></p>
<p>构建Docker image：&ldquo;shmemtest:v2&rdquo;：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;shmemtest:v2&quot; ./<br />
	Sending build context to Docker daemon 16.81 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./mmap_shm_wr /bin/<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 01e2f6bc7606<br />
	Step 3 : COPY ./mmap_shm_rd /bin/<br />
	&nbsp;&#8212;&gt; 0de95503c851<br />
	Removing intermediate container 0c472e92809f<br />
	Successfully built 0de95503c851</font></p>
<p>启动一个基于该image的容器：<br />
	<font face="Courier New">$ sudo docker run -it &quot;shmemtest:v2&quot; /bin/bash</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	1182f9eca367&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shmemtest:v2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 11 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 11 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; distracted_elion</font></p>
<p>进入容器，先后执行mmap_shm_wr和mmap_shm_rd，我们得到如下结果：</p>
<p><font face="Courier New">bash-4.1# mmap_shm_wr<br />
	Mmap Share Memory Create and Write Ok<br />
	bash-4.1# mmap_shm_rd<br />
	a b c d e f g h i j k l m n o p q r s t u v w x y z<br />
	Mmap Share Memory Read Ok</font></p>
<p>我们接下来尝试一下重启container后是否还能读出数据：</p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	1182f9eca367&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shmemtest:v2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; About a minute ago&nbsp;&nbsp; Up About a minute&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; distracted_elion&nbsp;&nbsp;&nbsp;<br />
	$ sudo docker stop 1182f9eca367<br />
	1182f9eca367<br />
	$ sudo docker start 1182f9eca367<br />
	1182f9eca367<br />
	$ sudo docker attach 1182f9eca36</font><font face="Courier New">7</font></p>
<p><font face="Courier New">bash-4.1# mmap_shm_rd<br />
	a b c d e f g h i j k l m n o p q r s t u v w x y z<br />
	Mmap Share Memory Read Ok</font></p>
<p>通过执行结果可以看出，通过mmap映射文件方式，共享内存的数据即便在容器重启后依旧可以得到保留。从原理上看，shm.txt是容器内 的一个文件，该文件存储在容器的可写文件系统layer中，从物理主机上看，其位置在/var/lib/docker/aufs/mnt /container_full_id/下，即便容器重启，该文件也不会被删除，而是作为容器文件系统的一部分：</p>
<p><font face="Courier New">$ sudo docker inspect -f &#39;{{.Id}}&#39; 1182f9eca367<br />
	1182f9eca36756219537f9a1c7cd1b62c6439930cc54bc69e87915c5dc8f7b97<br />
	$ sudo ls /var/lib/docker/aufs/mnt/1182f9eca36756219537f9a1c7cd1b62c6439930cc54bc69e87915c5dc8f7b97<br />
	bin&nbsp; dev&nbsp; etc&nbsp; home&nbsp; lib&nbsp; lib64&nbsp; lost+found&nbsp; media&nbsp; mnt&nbsp; opt&nbsp; proc&nbsp; root&nbsp; sbin&nbsp;&nbsp;&nbsp; selinux&nbsp;<big><b> shm.txt&nbsp;</b></big> srv&nbsp; sys&nbsp; tmp&nbsp; usr&nbsp; var</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>docker容器内服务程序的优雅退出</title>
		<link>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/</link>
		<comments>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/#comments</comments>
		<pubDate>Thu, 09 Oct 2014 13:58:49 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Dockerfile]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Namespace]]></category>
		<category><![CDATA[nsenter]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[Signal]]></category>
		<category><![CDATA[supervisor]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[yum]]></category>
		<category><![CDATA[信号]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[命令行]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[映像]]></category>
		<category><![CDATA[最佳实践]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[虚拟化]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1555</guid>
		<description><![CDATA[近期在试验如何将我们的产品部署到docker容器中去，这其中涉及到一个技术环节，那就是如何让docker容器退出时其内部运行的服务程序也 可以优雅的退出。所谓优雅退出，指的就是程序在退出前有清理资源（比如关闭文件描述符、关闭socket），保存必要中间状态，持久化内存数据 （比如将内存中的数据flush到文件中）的机会。docker作为目前最火的轻量级虚拟化技术，其在后台服务领域的应用是极其广泛的，其设计者 在程序优雅退出方面是有考虑的。下面我们由简单到复杂逐一考量一下。 一、优雅退出的原理 对于服务程序而言，一般都是以daemon形式运行在后台的。通知这些服务程序退出需要使用到系统的signal机制。一般服务程序都会监听某个 特定的退出signal，比如SIGINT、SIGTERM等（通过kill -l命令你可以查看到几十种signal）。当我们使用kill + 进程号时，系统会默认发送一个SIGTERM给相应的进程。该进程通过signal handler响应这一信号，并在这个handler中完成相应的&#8220;优雅退出&#8221;操作。 与&#8220;优雅退出&#8221;对立的是&#8220;暴力退出&#8221;，也就是我们常说的使用kill -9，也就是kill -s SIGKILL + 进程号，这个行为不会给目标进程任何时间空隙，而是直接将进程杀死，无论进程当前在做何种操作。这种操作常常导致&#8220;不一致&#8221;状态的出现。SIGKILL这 个信号比较特殊，进程无法有效监听该信号，无法有效针对该信号设置handler，无法改变其信号的默认处理行为。 二、测试用&#8220;服务程序&#8221; 为了测试docker容器对优雅退出的支持，我们编写如下&#8220;服务程序&#8221;用于放在docker容器中运行： //dockerapp1.go package main import &#34;fmt&#34; import &#34;time&#34; import &#34;os&#34; import &#34;os/signal&#34; import &#34;syscall&#34; type signalHandler func(s os.Signal, arg interface{}) type signalSet struct { &#160;&#160;&#160;&#160;&#160;&#160;&#160; m map[os.Signal]signalHandler } func signalSetNew() *signalSet { &#160;&#160;&#160;&#160;&#160;&#160;&#160; ss := new(signalSet) [...]]]></description>
			<content:encoded><![CDATA[<p><span style="line-height: 1.6em;">近期在试验如何将我们的产品部署到</span><a href="http://docker.com" style="line-height: 1.6em;">docker容器</a><span style="line-height: 1.6em;">中去，这其中涉及到一个技术环节，那就是如何让docker容器退出时其内部运行的服务程序也 可以优雅的退出。所谓优雅退出，指的就是程序在退出前有清理资源（比如关闭文件描述符、关闭socket），保存必要中间状态，持久化内存数据 （比如将内存中的数据flush到文件中）的机会。docker作为目前最火的轻量级虚拟化技术，其在后台服务领域的应用是极其广泛的，其设计者 在程序优雅退出方面是有考虑的。下面我们由简单到复杂逐一考量一下。</span></p>
<p><b>一、优雅退出的原理</b></p>
<p>对于服务程序而言，一般都是以daemon形式运行在后台的。通知这些服务程序退出需要使用到系统的signal机制。一般服务程序都会监听某个 特定的退出signal，比如SIGINT、SIGTERM等（通过kill -l命令你可以查看到几十种signal）。当我们使用kill + 进程号时，系统会默认发送一个SIGTERM给相应的进程。该进程通过signal handler响应这一信号，并在这个handler中完成相应的&ldquo;优雅退出&rdquo;操作。</p>
<p>与&ldquo;优雅退出&rdquo;对立的是&ldquo;暴力退出&rdquo;，也就是我们常说的使用kill -9，也就是kill -s SIGKILL + 进程号，这个行为不会给目标进程任何时间空隙，而是直接将进程杀死，无论进程当前在做何种操作。这种操作常常导致&ldquo;不一致&rdquo;状态的出现。SIGKILL这 个信号比较特殊，进程无法有效监听该信号，无法有效针对该信号设置handler，无法改变其信号的默认处理行为。</p>
<p><b>二、</b><b>测试用&ldquo;服务程序&rdquo;</b></p>
<p>为了测试docker容器对优雅退出的支持，我们编写如下&ldquo;服务程序&rdquo;用于放在docker容器中运行：</p>
<p><font face="Courier New">//dockerapp1.go</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;<br />
	import &quot;os&quot;<br />
	import &quot;os/signal&quot;<br />
	import &quot;syscall&quot;</font></p>
<p><font face="Courier New">type signalHandler func(s os.Signal, arg interface{})</font></p>
<p><font face="Courier New">type signalSet struct {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m map[os.Signal]signalHandler<br />
	}</font></p>
<p><font face="Courier New">func signalSetNew() *signalSet {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := new(signalSet)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.m = make(map[os.Signal]signalHandler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ss<br />
	}</font></p>
<p><font face="Courier New">func (set *signalSet) register(s os.Signal, handler signalHandler) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if _, found := set.m[s]; !found {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set.m[s] = handler<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func (set *signalSet) handle(sig os.Signal, arg interface{}) (err error) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if _, found := set.m[sig]; found {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set.m[sig](sig, arg)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return fmt.Errorf(&quot;No handler available for signal %v&quot;, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; panic(&quot;won&#39;t reach here&quot;)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go sysSignalHandleDemo()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the main goroutine wait!<br />
	}</font></p>
<p><font face="Courier New">func sysSignalHandleDemo() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := signalSetNew()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;handle signal: %v\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;signal termiate received, app exit normally\n&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGINT, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR1, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR2, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGTERM, handler)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan os.Signal)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var sigs []os.Signal<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for sig := range ss.m {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sigs = append(sigs, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; signal.Notify(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sig := &lt;-c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := ss.handle(sig, nil)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;unknown signal received: %v, app exit unexpectedly\n&quot;, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>关于<a href="http://tonybai.com/tag/golang">Go语言</a>对系统Signal的处理，可以参考《<a href="http://tonybai.com/2012/09/21/signal-handling-in-go/">Go中的系统Signal处理</a>》一文。</p>
<p><b>三、制作测试用docker image</b></p>
<p>在《 <a href="http://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/">Ubuntu Server 14.04安装docker</a>》一文中，我们完成了在ubuntu 14.04上安装docker的步骤。要制作测试用docker image，我们首先需要pull一个base image。我们以CentOS6.5为例：</p>
<p>在Ubuntu 14.04上执行：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">sudo&nbsp; docker pull centos:centos6</font></p>
<p>docker会自动从<a href="https://registry.hub.docker.com">官方仓库</a>下载一个制作好的docker image。下载成功后，我们可以run一下试试，像这样：</p>
<p><font face="Courier New">$&gt; sudo docker run -t -i centos:centos6 /bin/bash</font></p>
<p>我们查看一下CentOS6的小版本：<br />
	<font face="Courier New">$&gt; cat /etc/centos-release<br />
	CentOS release 6.5 (Final)</font></p>
<p>这是一个极其精简的CentOS，各种工具均未安装：<br />
	<font face="Courier New">bash-4.1# telnet<br />
	bash: telnet: command not found<br />
	bash-4.1# ssh<br />
	bash: ssh: command not found<br />
	bash-4.1# ftp<br />
	bash: ftp: command not found<br />
	bash-4.1# echo $PATH<br />
	/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</font></p>
<p>如果你要安装一些必要的工具，可以直接使用yum install，默认的base image已经将yum配置好了，可以直接使用。如果通过公司代理访问外部网络，别忘了先export http_proxy。另外docker直接使用宿主机的/etc/resolv.conf作为容器的DNS，我们也无需额外设置DNS。</p>
<p>接下来，我们就制作我们的第一个测试用image。安装官方推荐的Best Practice，我们使用Dockerfile来bulid一个测试用image。步骤如下：</p>
<p>- 建立~/ImagesFactory目录<br />
	- 将构建好的dockerapp1拷贝到~/ImagesFactory目录下<br />
	- 进入~/ImagesFactory目录，创建Dockerfile文件，Dockerfile内容如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	COPY ./dockerapp1 /bin<br />
	CMD /bin/dockerapp1</font></p>
<p>- 执行docker build，结果如下：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v1&quot; ./<br />
	Sending build context to Docker daemon 7.496 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp1 /bin<br />
	2014/10/09 16:05:25 lchown /var/lib/docker/aufs/mnt/fb0e864d3f07ca17ef8b6b69f034728e1f1158fd3f9c83fa48243054b2f26958/bin/dockerapp1: not a directory</font></p>
<p>居然build失败，提示什么not a directory。于是各种Search，终于发现问题所在，原来是&ldquo;<font face="Courier New">COPY ./dockerapp1 /bin</font>&rdquo;这条命令错了，少了个&ldquo;/&rdquo;，将&quot; /bin&quot;改为&ldquo;/bin/&rdquo;就OK了，Docker真是奇怪啊，这块明显应该做得更兼容些。新的Dockerfile如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	COPY ./dockerapp1 /bin/<br />
	CMD /bin/dockerapp1</font></p>
<p>构建结果如下：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v1&quot; ./<br />
	Sending build context to Docker daemon 7.496 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp1 /bin/<br />
	&nbsp;&#8212;&gt; 20c3783c42ab<br />
	Removing intermediate container cab639ab4321<br />
	Step 3 : CMD /bin/dockerapp1<br />
	&nbsp;&#8212;&gt; Running in 31875d3c37f9<br />
	&nbsp;&#8212;&gt; 21a720a808a7<br />
	Removing intermediate container 31875d3c37f9<br />
	Successfully built 21a720a808a7</font></p>
<p><font face="Courier New">$ sudo docker images<br />
	REPOSITORY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TAG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; VIRTUAL SIZE<br />
	test&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 21a720a808a7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 59 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 214.6 MB</font></p>
<p><b>四、第一个测试容器</b></p>
<p>我们基于image &quot;test:v1&quot;启动一个测试容器：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v1&quot;<br />
	daf3ae88fec23a31cde9f6b9a3f40057953c87b56cca982143616f738a84dcba</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	daf3ae88fec2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 17 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 16 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; condescending_sammet&nbsp;&nbsp; </font></p>
<p>通过docker run命令，我们基于image&quot;test:v1&quot;启动了一个容器。通过docker ps命令可以看到容器成功启动，容器id：<font face="Courier New">daf3ae88fec2，别名为：</font><font face="Courier New">condescending_sammet。</font></p>
<p><font face="Courier New">根据Dockerfile我们知道，容器启动后将执行&quot;/bin/dockerapp1&quot;这个程序，dockerapp1退出，容器即退出。 run命令的&quot;-d&quot;选项表示容器将以daemon的形式运行，我们在前台无法看到容器的输出。那么我们怎么查看容器的输出呢？我们可以通过 docker logs + 容器id的方式查看容器内应用的标准输出或标准错误。我们也可以进入容器来查看。</font></p>
<p><font face="Courier New">进入容器有多种方法，比如用sudo docker attach </font><font face="Courier New"><font face="Courier New">daf3ae88fec2</font>。attach后，就好比将daemon方式运行的容器 拿到了前台，你可以Ctrl + C一下，可以看到如下dockerapp1的输出:</font></p>
<p><font face="Courier New">^Chandle signal: interrupt</font></p>
<p><font face="Courier New">另外一种方式是利用nsenter工具进入我们容器的namespace空间。ubuntu 14.04下可以通过如下方式安装该工具：</font></p>
<p><font face="Courier New">$ wget <a class="moz-txt-link-freetext" href="https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz">https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz</a>; tar xzvf util-linux-2.24.tar.gz<br />
	$ cd util-linux-2.24<br />
	$ ./configure &#8211;without-ncurses &amp;&amp; make nsenter<br />
	$ sudo cp nsenter /usr/local/bin</font></p>
<p>安装后，我们通过如下方式即可进入上面的容器：</p>
<p><font face="Courier New">$ echo $(sudo docker inspect &#8211;format &quot;{{ .State.Pid }}&quot; daf3ae88fec2)<br />
	5494<br />
	$ sudo nsenter &#8211;target 5494 &#8211;mount &#8211;uts &#8211;ipc &#8211;net &#8211;pid<br />
	-bash-4.1# ps -ef<br />
	UID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PID&nbsp; PPID&nbsp; C STIME TTY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TIME CMD<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp; 0 09:20 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 /bin/dockerapp1<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 16&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp; 0 09:32 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 -bash<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 27&nbsp;&nbsp;&nbsp; 16&nbsp; 0 09:32 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 ps -ef<br />
	-bash-4.1# </font></p>
<p>进入容器后通过ps命令可以看到正在运行的dockerapp1程序。在容器内，我们可以通过kill来测试dockerapp1的运行情况：</p>
<p><font face="Courier New">-bash-4.1# kill -s SIGINT 1</font></p>
<p>通过前面的attach窗口，我们可以看到dockerapp1输出:</p>
<p><font face="Courier New">handle signal: interrupt</font></p>
<p>如果你发送SIGTERM信号，那么dockerapp1将终止运行，容器也就停止了。</p>
<p><font face="Courier New">-bash-4.1# kill 1</font></p>
<p>attach窗口显示：</p>
<p><font face="Courier New">signal termiate received, app exit normally</font></p>
<p>我们可以看到容器启动后默认执行的时Dockerfile中的CMD命令，如果Dockerfile中有多行CMD命令，Docker在启动容器 时只会执行最后一条CMD命令。如果在docker run中指定了命令，docker则会执行命令行中的命令而不会执行dockerapp1，比如：</p>
<p><font face="Courier New">$ sudo docker run -t -i &quot;test:v1&quot; /bin/bash<br />
	bash-4.1# </font></p>
<p>这里我们看到直接执行的时bash，dockerapp1并未执行。</p>
<p><b>五、docker stop的行为</b></p>
<p>我们先来看看docker stop的manual：</p>
<p><font face="Courier New">$ sudo docker stop &#8211;help<br />
	Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]<br />
	Stop a running container by sending SIGTERM and then SIGKILL after a grace period<br />
	&nbsp; -t, &#8211;time=10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.</font></p>
<p>可以看出当我们执行docker stop时，docker会首先向容器内的当前主程序发送一个SIGTERM信号，用于容器内程序的退出。如果容器在收到SIGTERM后没有马上退出， 那么stop命令会在等待一段时间（默认是10s）后，再向容器发送SIGKILL信号，将容器杀死，变为退出状态。</p>
<p>我们来验证一下docker stop的行为。启动刚才那个容器：</p>
<p><font face="Courier New">$ sudo docker start daf3ae88fec2<br />
	daf3ae88fec2</font></p>
<p><font face="Courier New">attach到容器daf3ae88fec2<br />
	$ sudo docker attach daf3ae88fec2</font></p>
<p>新打开一个窗口，执行docker stop命令：<br />
	<font face="Courier New">$ sudo docker stop daf3ae88fec2<br />
	daf3ae88fec2</font></p>
<p>可以看到attach窗口输出：<br />
	<font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p>通过docker ps查看，发现容器已经退出。</p>
<p>也许通过上面的例子还不能直观的展示stop命令的<b>两阶段行为</b>，因为dockerapp1收到SIGTERM后直接就退出 了，stop命令无需等待容器慢慢退出，也无需发送SIGKILL。我们改造一下dockerapp1这个程序。</p>
<p>我们复制一下dockerapp1.go为dockerapp2.go，编辑dockerapp2.go，将handler中对SIGTERM的 处理注释掉，其他不变：</p>
<p><font face="Courier New">handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;handle signal: %v\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;signal termiate received, app exit normally\n&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p>我们使用dockerapp2来构建一个新image：test:v2，将Dockerfile中得dockerapp1换成 dockerapp2即可。</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v2&quot; ./<br />
	Sending build context to Docker daemon 9.369 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp2 /bin/<br />
	&nbsp;&#8212;&gt; 27cd613a9bd7<br />
	Removing intermediate container 07c760b6223b<br />
	Step 3 : CMD /bin/dockerapp2<br />
	&nbsp;&#8212;&gt; Running in 1aac086452a7<br />
	&nbsp;&#8212;&gt; 82eb876fefd2<br />
	Removing intermediate container 1aac086452a7<br />
	Successfully built 82eb876fefd2</font></p>
<p>利用image &quot;test:v2&quot;创建一个容器来测试stop。</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v2&quot;<br />
	29f3ec1af3c355458cbbd802a5e8a53da28e9f51a56ce822c7bba2a772edceac</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	29f3ec1af3c3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 7 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 6 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; romantic_feynman&nbsp;</font>&nbsp;&nbsp;</p>
<p>Attach到这个容器并观察，在另外一个窗口stop该container。我们在attach窗口只看到如下输出：</p>
<p><font face="Courier New">handle signal: terminated</font></p>
<p>stop命令的执行没有立即返回，而是等待容器退出。等待10s后，容器退出，stop命令执行结束。从这个例子我们可以明显看出stop的两阶 段行为。</p>
<p>如果我们以<font face="Courier New">sudo docker run -i -t &quot;test:v1&quot; /bin/bash</font>形式启动容器，那stop命令会将SIGTERM发送给bash这个程序，即使你通过nsenter进入容 器，启动了dockerapp1，dockerapp1也不会收到SIGTERM，dockerapp1会随着容器的退出而被强行终止，就像被 kill -9了一样。</p>
<p><b>六、多进程容器服务</b>程序</p>
<p>上面无论是dockerapp1还是dockerapp2，都是一个单进程服务程序。如果我们在容器内执行一个多进程程序，我们该如何优雅退出 呢？我们先来编写一个多进程的服务程序dockerapp3：</p>
<p>在dockerapp1.go的基础上对main和sysSignalHandleDemo进行修改形成dockerapp3.go，修改后这两 个函数的代码如下：</p>
<p><font face="Courier New">//dockerapp3.go<br />
	&#8230; &#8230;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go sysSignalHandleDemo()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pid, _, err := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;err fork process, err: %v\n&quot;, err)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if pid == 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;i am in child process, pid = %v\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the child process wait<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;i am parent process, pid = %v\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;fork ok, childpid = %v\n&quot;, pid)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the main goroutine wait!<br />
	}</font></p>
<p><font face="Courier New">func sysSignalHandleDemo() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := signalSetNew()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: handle signal: %v\n&quot;, syscall.Getpid(), s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: signal termiate received, app exit normally\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGINT, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR1, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR2, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGTERM, handler)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan os.Signal)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var sigs []os.Signal<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for sig := range ss.m {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sigs = append(sigs, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; signal.Notify(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sig := &lt;-c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := ss.handle(sig, nil)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: unknown signal received: %v, app exit unexpectedly\n&quot;, syscall.Getpid(), sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>dockerapp3利用fork创建了一个子进程，这样dockerapp3实际上是两个进程在运行，各自有自己的signal监听 goroutine，goroutine的处理逻辑是相同的。注意：由于Windows和Mac OS X不具备fork语义，因此在这两个平台上运行dockerapp3不会得到预期结果。</p>
<p>利用dockerapp3，我们创建image &quot;test:v3&quot;:</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v3&quot; ./<br />
	[sudo] password for tonybai:<br />
	Sending build context to Docker daemon 11.24 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp3 /bin/<br />
	&nbsp;&#8212;&gt; 6ccf97065853<br />
	Removing intermediate container 6d85fe241939<br />
	Step 3 : CMD /bin/dockerapp3<br />
	&nbsp;&#8212;&gt; Running in 75d76380992a<br />
	&nbsp;&#8212;&gt; c9e7bf361ed7<br />
	Removing intermediate container 75d76380992a<br />
	Successfully built c9e7bf361ed7</font></p>
<p>启动基于test:v3 image的容器：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v3&quot;<br />
	781cecb4b3628cb33e1b104ea57e506ad5cb4a44243256ebd1192af86834bae6<br />
	$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	781cecb4b362&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 5 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 4 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insane_bohr&nbsp;&nbsp;&nbsp;</font>&nbsp;&nbsp;&nbsp;</p>
<p>通过docker logs查看dockerapp3的输出：</p>
<p><font face="Courier New">$ sudo docker logs 781cecb4b362<br />
	i am parent process, pid = 1<br />
	fork ok, childpid = 13<br />
	i am in child process, pid = 13</font></p>
<p>可以看出主进程pid为1，子进程pid为13。我们通过stop停止该容器：</p>
<p><font face="Courier New">$ sudo docker stop 781cecb4b362<br />
	781cecb4b362</font></p>
<p>再次通过docker logs查看：</p>
<p><font face="Courier New">$ sudo docker logs 781cecb4b362<br />
	i am parent process, pid = 1<br />
	fork ok, childpid = 13<br />
	i am in child process, pid = 13<br />
	1: handle signal: terminated<br />
	1: signal termiate received, app exit normally</font></p>
<p>我们可以看到主进程收到了stop发来的SIGTERM并退出，主进程的退出导致容器退出，导致子进程13也无法生存，并且没有优雅退出。而在非 容器状态下，子进程是可以被init进程接管的。</p>
<p>因此对于docker容器内运行的多进程程序，stop命令只会将SIGTERM发送给容器主进程，要想让其他进程也能优雅退出，需要在主进程与 其他进程间建立一种通信机制。在主进程退出前，等待其他子进程退出。待所有其他进程退出后，主进程再退出，容器停止。这样才能保证服务程序的优雅 退出。</p>
<p><b>七、容器内启动多个服务程序</b></p>
<p>虽说docker <a href="https://docs.docker.com/articles/dockerfile_best-practices/">best practice</a>建议一个container内只放置一个服务程序，但对已有的一些遗留系统，在架构没有做出重构之前，很可能会有在一个 container中部署两个以上服务程序的情况和需求。而docker Dockerfile只允许执行一个CMD，这种情况下，我们就需要借助类似supervisor这样的进程监控管理程序来启动和管理container 内的多个程序了。</p>
<p>下面我们来自制作一个基于centos:centos6的安装了supervisord以及两个服务程序的image。我们将dockerapp1拷贝一份，并将拷贝命名为dockerapp1-brother。下面是我们的Dockerfile：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	RUN yum install python-setuptools -y<br />
	RUN easy_install supervisor<br />
	RUN mkdir -p /var/log/supervisor<br />
	COPY ./supervisord.conf /etc/supervisord.conf<br />
	COPY ./dockerapp1 /bin/<br />
	COPY ./dockerapp1-brother /bin/<br />
	CMD ["/usr/bin/supervisord"]</font></p>
<p>supervisord的配置文件supervisord.conf内容如下：</p>
<p><font face="Courier New">; supervisor config file</font></p>
<p><font face="Courier New">[unix_http_server]<br />
	file=/var/run/supervisor.sock&nbsp;&nbsp; ; (the path to the socket file)<br />
	chmod=0700&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; sockef file mode (default 0700)</font></p>
<p><font face="Courier New">[supervisord]<br />
	logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)<br />
	pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)<br />
	childlogdir=/var/log/supervisor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; (&#39;AUTO&#39; child log dir, default $TEMP)</font></p>
<p><font face="Courier New">[rpcinterface:supervisor]<br />
	supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface</font></p>
<p><font face="Courier New">[supervisorctl]<br />
	serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL&nbsp; for a unix socket</font></p>
<p><font face="Courier New">[supervisord]<br />
	nodaemon=false</font></p>
<p><font face="Courier New">[program:dockerapp1]<br />
	command=/bin/dockerapp1<br />
	stdout_logfile=/tmp/dockerapp1.log<br />
	stopsignal=TERM<br />
	stopwaitsecs=10</font></p>
<p><font face="Courier New">[program:dockerapp1-brother]<br />
	command=/bin/dockerapp1-brother<br />
	stdout_logfile=/tmp/dockerapp1-brother.log<br />
	stopsignal=QUIT<br />
	stopwaitsecs=10</font></p>
<p>开始build镜像：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">$&gt; sudo docker build -t=&quot;test:supervisor-v1&quot; ./<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; Successfully built d006b9ad10eb</font></p>
<p>基于该镜像，启动一个容器：<br />
	<font face="Courier New">$&gt; sudo docker run -d &quot;test:supervisor-v1&quot;<br />
	05ded2b898c90059d4c9b5c6ccc8603b6848ae767360c42bd9b36ff87fb4b9df</font></p>
<p>执行ps命令查看镜像id：<br />
	<font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES</font></p>
<p>怎么回事？Container没有启动起来？</p>
<p><font face="Courier New">$ sudo docker ps -a<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	05ded2b898c9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:supervisor-v1&nbsp;&nbsp;&nbsp; &quot;/usr/bin/supervisor&nbsp;&nbsp; 22 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Exited (0) 21 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hungry_engelbart</font></p>
<p>通过ps -a查看，container启动是成功了，但是成功退出了。于是尝试查看一下log：</p>
<p><font face="Courier New">sudo docker logs 05ded2b898c9<br />
	/usr/lib/python2.6/site-packages/supervisor-3.1.2-py2.6.egg/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a &quot;-c&quot; argument specifying an absolute path to a configuration file for improved security.<br />
	&nbsp; &#39;Supervisord is running as root and it is searching &#39;</font></p>
<p>似乎是supervisord转为daemon程序，容器主进程退出了，容器随之终止了。</p>
<p>看来容器内的supervisord不能以daemon形式运行，应该以前台形式run。修改一下supervisord.conf中得配置：</p>
<p>将<br />
	<font face="Courier New">[supervisord]<br />
	nodaemon=false</font></p>
<p>改为</p>
<p><font face="Courier New">[supervisord]<br />
	nodaemon=true</font></p>
<p>重新制作镜像:</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:supervisor-v2&quot; ./<br />
	Sending build context to Docker daemon 13.12 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : RUN yum install python-setuptools -y<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; e09c66a1ea8c<br />
	Step 3 : RUN easy_install supervisor<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 9c8797e8c27e<br />
	Step 4 : RUN mkdir -p /var/log/supervisor<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 9bfc67f8517d<br />
	Step 5 : COPY ./supervisord.conf /etc/supervisord.conf<br />
	&nbsp;&#8212;&gt; 8c514f998363<br />
	Removing intermediate container 4a185856e6ed<br />
	Step 6 : COPY ./dockerapp1 /bin/<br />
	&nbsp;&#8212;&gt; 0317bd4914d3<br />
	Removing intermediate container ac5738380854<br />
	Step 7 : COPY ./dockerapp1-brother /bin/<br />
	&nbsp;&#8212;&gt; d89711888bdf<br />
	Removing intermediate container eadc9444e716<br />
	Step 8 : CMD ["/usr/bin/supervisord"]<br />
	&nbsp;&#8212;&gt; Running in aaa042ac3914<br />
	&nbsp;&#8212;&gt; 9655256bbfed<br />
	Removing intermediate container aaa042ac3914<br />
	Successfully built 9655256bbfed</font></p>
<p>有了前面的铺垫，这次build image瞬间完成。启动容器，查看容器启动状态，查看容器内supervisord的运行日志如下：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:supervisor-v2&quot;<br />
	61916f1c82338b28ced101b6bde119e4afb7c7fa349b4332ed51a43a4586b1b9</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	61916f1c8233&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:supervisor-v2&nbsp;&nbsp; &quot;/usr/bin/supervisor&nbsp;&nbsp; 16 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 16 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; prickly_einstein</font></p>
<p><font face="Courier New">$ sudo docker logs 8eb3e9892e66</font></p>
<p><font face="Courier New">/usr/lib/python2.6/site-packages/supervisor-3.1.2-py2.6.egg/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a &quot;-c&quot; argument specifying an absolute path to a configuration file for improved security.<br />
	&nbsp; &#39;Supervisord is running as root and it is searching &#39;<br />
	2014-10-09 14:36:02,334 CRIT Supervisor running as root (no user in config file)<br />
	2014-10-09 14:36:02,349 INFO RPC interface &#39;supervisor&#39; initialized<br />
	2014-10-09 14:36:02,349 CRIT Server &#39;unix_http_server&#39; running without any HTTP authentication checking<br />
	2014-10-09 14:36:02,349 INFO supervisord started with pid 1<br />
	2014-10-09 14:36:03,354 INFO spawned: &#39;dockerapp1&#39; with pid 14<br />
	2014-10-09 14:36:03,363 INFO spawned: &#39;dockerapp1-brother&#39; with pid 15<br />
	2014-10-09 14:36:04,368 INFO success: dockerapp1 entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)<br />
	2014-10-09 14:36:04,369 INFO success: dockerapp1-brother entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)</font></p>
<p>可以看到supervisord已经将dockerapp1和dockerapp1-brother启动起来了。</p>
<p>现在我们尝试停止容器，我们预期是supervisord在退出前通知dockerapp1和dockerapp1-brother先退出，我们可以通过 查看容器内的/tmp/dockerapp1.log和/tmp/dockerapp1-brother.log来确认supervisord是否做了通 知。</p>
<p><font face="Courier New">$ sudo docker stop 61916f1c8233<br />
	61916f1c8233</font></p>
<p><font face="Courier New">$ sudo docker logs 61916f1c8233<br />
	&#8230; &#8230;<br />
	2014-10-09 14:37:52,253 WARN received SIGTERM indicating exit request<br />
	2014-10-09 14:37:52,254 INFO waiting for dockerapp1, dockerapp1-brother to die<br />
	2014-10-09 14:37:52,254 INFO stopped: dockerapp1-brother (exit status 0)<br />
	2014-10-09 14:37:52,256 INFO stopped: dockerapp1 (exit status 0)</font></p>
<p>通过容器的log，我们看出supervisord是等待两个程序退出后才退出的，不过我们还是要看看两个程序的输出日志以最终确认。重新启动容器，通过nsenter进入到容器中。</p>
<p><font face="Courier New">-bash-4.1# vi /tmp/dockerapp1.log</font></p>
<p><font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p><font face="Courier New">-bash-4.1# vi /tmp/dockerapp1-brother.log</font></p>
<p><font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p>两个程序的标准输出日志证实了我们的预期。</p>
<p>BTW，在物理机上测试supervisord以daemon形式运行，当kill掉supervisord时，supervisord是不会通知其监控 和管理的程序退出的。只有在以non-daemon形式运行时，supervisord才会在退出前先通知下面的程序退出。如果在一段时间内下面程序没有 退出，supervisord在退出前会kill -9强制杀死这些程序的进程。</p>
<p>最后要说的时，在验证一些想法时，没有必要build image，我们可以直接将本地文件copy到容器中，下面是一个例子，我们将dockerapp1和dockerapp1-brother拷贝到镜像中：<br />
	<font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	4d8982bfccc7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; centos:centos6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 26 minutes ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 26 minutes&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sharp_thompson&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	$ sudo docker inspect -f &#39;{{.Id}}&#39; 4d8982bfccc7<br />
	4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4<br />
	$ sudo cp dockerapp1&nbsp; /var/lib/docker/aufs/mnt/4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4/bin/dockerapp1<br />
	$ sudo cp dockerapp1-brother&nbsp; /var/lib/docker/aufs/mnt/4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4/bin/dockerapp1-brother</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Golang Channel用法简编</title>
		<link>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/</link>
		<comments>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/#comments</comments>
		<pubDate>Mon, 29 Sep 2014 09:02:43 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Actor]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Concurrent]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[LXC]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[TIOBE]]></category>
		<category><![CDATA[内存模型]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[布道师]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1551</guid>
		<description><![CDATA[在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的golangtc已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&#34;Golang中 国&#34;，即golangtc.com去下载go 1.3.2版本。 Go这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在TIOBE编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用 Docker你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&#8220;docker&#8221;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 Docker &#8211; 从入门到实践》。 据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在这里我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。 在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 Gopher Academy 发起的GopherCon 会议也于今年第一次举行，并放出诸多高质量资料，在这里可以下载。欧洲的Go语言大会.dotgo也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。 言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&#8220;舶来品&#8221;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。 一、Golang并发基础理论 Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&#8220;知道&#8221;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从这里下载到CSP论文的最新版本。 维基百科中概要罗列了CSP模型与另外一种并发模型Actor模型的区别： Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处： &#160;&#160;&#160; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。 &#160;&#160;&#160; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。 &#160;&#160;&#160; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。 二、Go Channel基本操作语法 Go Channel的基本操作语法如下： [...]]]></description>
			<content:encoded><![CDATA[<p>在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的<a href="http://golangtc.com">golangtc</a>已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&quot;Golang中 国&quot;，即golangtc.com去下载go 1.3.2版本。</p>
<p><a href="http://golang.org">Go</a>这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在<a href="http://www.tiobe.com">TIOBE</a>编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用<a href="http://docker.com"> <b>Docker</b></a>你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&ldquo;docker&rdquo;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 <a href="https://www.gitbook.io/book/yeasy/docker_practice">Docker &#8211; 从入门到实践</a>》。</p>
<p>据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在<a href="https://github.com/qiniu/go/issues/15#issuecomment-55568731">这里</a>我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。</p>
<p>在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 <span style="font-family: Ubuntu, Tahoma, sans-serif; font-size: 14px; line-height: 20px;"><a href="http://gopheracademy.com/">Gopher Academy</a> 发起的</span><a href="http://www.gophercon.com">GopherCon</a> 会议也于今年第一次举行，并放出诸多高质量资料，在<a href="https://github.com/gophercon/2014-talks">这里</a>可以下载。欧洲的Go语言大会<a href="http://www.dotgo.eu/">.dotgo</a>也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。</p>
<p>言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&ldquo;舶来品&rdquo;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。</p>
<p><b>一、Golang并发基础理论</b></p>
<p>Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&ldquo;知道&rdquo;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从<a href="http://www.usingcsp.com">这里</a>下载到CSP论文的最新版本。</p>
<p><a href="http://en.wikipedia.org/wiki/Communicating_sequential_processes">维基百科</a>中概要罗列了CSP模型与另外一种并发模型Actor模型的区别：</p>
<p>Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处：<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。</p>
<p><b>二、Go Channel基本操作语法</b></p>
<p>Go Channel的基本操作语法如下：</p>
<p><font face="Courier New">c := make(chan bool) //创建一个无缓冲的bool型Channel <br />
	c &lt;- x&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //向一个Channel发送一个值<br />
	&lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从一个Channel中接收一个值<br />
	x = &lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从Channel c接收一个值并将其存储到x中<br />
	x, ok = &lt;- c&nbsp; //从Channel接收一个值，如果channel关闭了或没有数据，那么ok将被置为false</font></p>
<p><i>不带缓冲的Channel</i>兼具通信和同步两种特性，颇受青睐。</p>
<p><b>三、Channel用作信号(Signal)的场景</b></p>
<p>1、等待一个事件(Event)</p>
<p><font face="Courier New">等待一个事件，有时候通过close一个Channel就足够了。例如：</font></p>
<p><font face="Courier New">//testwaitevent1.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin doing something!&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Doing something&#8230;&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>close(c)</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>&lt;-c</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done!&quot;)<br />
	}</font></p>
<p>这里main goroutine通过&quot;<font face="Courier New">&lt;-c</font>&quot;来等待sub goroutine中的&ldquo;完成事件&rdquo;，sub goroutine通过close channel促发这一事件。当然也可以通过向Channel写入一个bool值的方式来作为事件通知。main goroutine在channel c上没有任何数据可读的情况下会阻塞等待。</p>
<p>关于输出结果：</p>
<p>根据《<a href="http://golang.org/ref/mem">Go memory model</a>》中关于close channel与recv from channel的order的定义：<span style="color: rgb(34, 34, 34); font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-style: italic; line-height: normal;"><font face="Courier New">The closing of a channel happens before a receive that returns a zero value because the channel is closed.</font></span></p>
<p>我们可以很容易判断出上面程序的输出结果：</p>
<p><font face="Courier New">Begin doing something!<br />
	Doing something&#8230;<br />
	Done!</font></p>
<p>如果将<font face="Courier New">close(c)</font>换成<font face="Courier New">c&lt;-true</font>，则根据《Go memory model》中的定义：<font face="Courier New"><span style="color: rgb(34, 34, 34); font-size: 16px; font-style: italic; line-height: normal;">A receive from an unbuffered channel happens before the send on that channel completes.</span></font><br />
	&quot;<font face="Courier New">&lt;-c</font>&quot;要先于&quot;<font face="Courier New">c&lt;-true</font>&quot;完成，但也不影响日志的输出顺序，输出结果仍为上面三行。</p>
<p>2、协同多个Goroutines</p>
<p>同上，close channel还可以用于协同多个Goroutines，比如下面这个例子，我们创建了100个Worker Goroutine，这些Goroutine在被创建出来后都阻塞在&quot;<font face="Courier New">&lt;-start&quot;</font>上，直到我们在main goroutine中给出<u>开工</u>的信号：&quot;<font face="Courier New">close(start)&quot;</font>，这些goroutines才开始真正的并发运行起来。</p>
<p><font face="Courier New">//testwaitevent2.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func worker(start chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-start<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;This is Worker:&quot;, index)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; start := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go worker(start, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(start)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {} //deadlock we expected<br />
	}</font></p>
<p>3、Select</p>
<p>【select的基本操作】<br />
	select是Go语言特有的操作，使用select我们可以同时在多个channel上进行发送/接收操作。下面是select的基本操作。</p>
<p><font face="Courier New">select {<br />
	case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp; // </font><font face="Courier New"><font face="Courier New">检查ok值判断someOtherchan是否已经关闭</font></font></p>
<p><font face="Courier New">case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">default:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	}</font></p>
<p>【惯用法：for/select】</p>
<p>我们在使用select时很少只是对其进行一次evaluation，我们常常将其与for {}结合在一起使用，并选择适当时机从for{}中退出。</p>
<p><font face="Courier New">for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 检查ok值判断someOtherchan是否已经关闭</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p>【终结workers】</p>
<p>下面是一个常见的终结sub worker goroutines的方法，每个worker goroutine通过select监视一个die channel来及时获取main goroutine的退出通知。</p>
<p><font face="Courier New">//testterminateworker1.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; go worker(die, i)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp; close(die)<br />
	&nbsp;&nbsp;&nbsp; select {} </font><font face="Courier New"><font face="Courier New">//deadlock we expected</font><br />
	}</font></p>
<p>【终结验证】</p>
<p>有时候终结一个worker后，main goroutine想确认worker routine是否真正退出了，可采用下面这种方法：</p>
<p><font face="Courier New">//testterminateworker2.go<br />
	package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; //&quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; go worker(die)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &lt;-die<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Worker goroutine has been terminated&quot;)<br />
	}</font></p>
<p>【关闭的Channel永远不会阻塞】</p>
<p>下面演示在一个已经关闭了的channel上读写的结果：</p>
<p><font face="Courier New">//testoperateonclosedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(cb)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, x)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x, ok := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v %#v\n&quot;, x, ok)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ci := make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(ci)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; y := &lt;-ci<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, y)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb &lt;- true<br />
	}</font></p>
<p><font face="Courier New">$go run </font><font face="Courier New"><font face="Courier New">testoperateonclosedchannel.go</font><br />
	false<br />
	false false<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看到在一个已经close的unbuffered channel上执行读操作，回返回channel对应类型的零值，比如bool型channel返回false，int型channel返回0。但向close的channel写则会触发panic。不过无论读写都不会导致阻塞。</p>
<p>【关闭带缓存的channel】</p>
<p>将unbuffered channel换成buffered channel会怎样？我们看下面例子：</p>
<p><font face="Courier New">//testclosedbufferedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan int, 3)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 15<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 34<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 65<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testclosedbufferedchannel.go<br />
	15<br />
	34<br />
	65<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看出带缓冲的channel略有不同。尽管已经close了，但我们依旧可以从中读出关闭前写入的3个值。第四次读取时，则会返回该channel类型的零值。向这类channel写入操作也会触发panic。</p>
<p>【range】</p>
<p>Golang中的range常常和channel并肩作战，它被用来从channel中读取所有值。下面是一个简单的实例：</p>
<p><font face="Courier New">//testrange.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func generator(strings chan string) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;Five hour&#39;s New York jet lag&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;and Cayce Pollard wakes in Camden Town&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;to the dire and ever-decreasing circles&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;of disrupted circadian rhythm.&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(strings)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings := make(chan string)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go generator(strings)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for s := range strings {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%s\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;\n&quot;)<br />
	}</font></p>
<p><b>四、隐藏状态</b></p>
<p>下面通过一个例子来演示一下channel如何用来隐藏状态：</p>
<p>1、例子：唯一的ID服务</p>
<p><font face="Courier New">//testuniqueid.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func newUniqueIDService() &lt;-chan string {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := make(chan string)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var counter int64 = 0<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id &lt;- fmt.Sprintf(&quot;%x&quot;, counter)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; counter += 1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return id<br />
	}<br />
	func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := newUniqueIDService()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 0; i &lt; 10; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&lt;-id)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$ go run testuniqueid.go<br />
	0<br />
	1<br />
	2<br />
	3<br />
	4<br />
	5<br />
	6<br />
	7<br />
	8<br />
	9</font></p>
<p>newUniqueIDService通过一个channel与main goroutine关联，main goroutine无需知道uniqueid实现的细节以及当前状态，只需通过channel获得最新id即可。</p>
<p><b>五、默认情况</b></p>
<p>我想这里John Graham-Cumming主要是想告诉我们select的default分支的实践用法。</p>
<p>1、select&nbsp; for non-blocking receive</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列</font></p>
<p><font face="Courier New">select {<br />
	case b = &lt;-idle:  //尝试从idle队列中读取<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	default:&nbsp; //队列空，分配一个新的buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; makes += 1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = make([]byte, size)<br />
	}</font></p>
<p>2、select for non-blocking send</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) </font><font face="Courier New"><font face="Courier New">//用一个带缓冲的channel构造一个简单的队列</font></font></p>
<p><font face="Courier New">select {<br />
	case idle &lt;- b: //尝试向队列中插入一个buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230;<br />
	default: //队列满？</font></p>
<p><font face="Courier New">}</font></p>
<p><b>六、Nil Channels</b></p>
<p>1、nil channels阻塞</p>
<p>对一个没有初始化的channel进行读写操作都将发生阻塞，例子如下：</p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-c<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p>2、nil channel在select中很有用</p>
<p>看下面这个例子：</p>
<p><font face="Courier New">//testnilchannel_bad.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c1, c2 chan int = make(chan int), make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p>我们原本期望程序交替输出5和7两个数字，但实际的输出结果却是：</p>
<p><font face="Courier New">5<br />
	0<br />
	0<br />
	0<br />
	&#8230; &#8230; 0死循环</font></p>
<p>再仔细分析代码，原来select每次按case顺序evaluate：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 前5s，select一直阻塞；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 第5s，c1返回一个5后被close了，&ldquo;case x := &lt;-c1&rdquo;这个分支返回，select输出5，并重新select<br />
	&nbsp;&nbsp;&nbsp; &#8211; 下一轮select又从&ldquo;case x := &lt;-c1&rdquo;这个分支开始evaluate，由于c1被close，按照前面的知识，close的channel不会阻塞，我们会读出这个 channel对应类型的零值，这里就是0；select再次输出0；这时即便c2有值返回，程序也不会走到c2这个分支<br />
	&nbsp;&nbsp;&nbsp; &#8211; 依次类推，程序无限循环的输出0</p>
<p>我们利用nil channel来改进这个程序，以实现我们的意图，代码如下：</p>
<p><font face="Courier New">//testnilchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c1, c2 chan int = make(chan int), make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x, ok := &lt;-c1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x, ok := &lt;-c2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if c1 == nil &amp;&amp; c2 == nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	5<br />
	7<br />
	over</font></p>
<p>可以看出：通过将已经关闭的channel置为nil，下次select将会阻塞在该channel上，使得select继续下面的分支evaluation。</p>
<p><b>七、Timers</b></p>
<p>1、超时机制Timeout</p>
<p>带超时机制的select是常规的tip，下面是示例代码，实现30s的超时select：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timeout := time.After(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- timeout:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p>2、心跳HeartBeart</p>
<p>与timeout实现类似，下面是一个简单的心跳select实现：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; heartbeat := time.Tick(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- heartbeat:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230; do heartbeat stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x集成Amazon内购和GameCircle服务</title>
		<link>https://tonybai.com/2014/08/04/amazon-inapp-purchasing-and-gamecirle-in-cocos2dx/</link>
		<comments>https://tonybai.com/2014/08/04/amazon-inapp-purchasing-and-gamecirle-in-cocos2dx/#comments</comments>
		<pubDate>Mon, 04 Aug 2014 10:30:59 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Amazon]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[AppStore]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Eclipse]]></category>
		<category><![CDATA[Game]]></category>
		<category><![CDATA[GameCircle]]></category>
		<category><![CDATA[GooglePlay]]></category>
		<category><![CDATA[In-App-Purchasing]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[LeaderBoards]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[亚马逊]]></category>
		<category><![CDATA[内购]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[引擎]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[游戏]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1544</guid>
		<description><![CDATA[由于种种原因，这篇文章已经拖延了N多时间了。今天花了些时间把如何在Cocos2d-x(我用的版本是2.2.2)游戏中集成Amazon的内购和GameCircle服务(仅适用于Android版本)整理一下，发出来，作备忘。 之前在做&#8220;手指足球世界杯2014&#8221;时，想给这款小游戏加上内购(In-App Purchasing)和积分榜(ScoreBoard)功能。说到Android手机游戏的内购，人们第一时间想到的就是Google Play，不过悲催的是，Google Play在国内各种无法访问，行货机也不预装，其相关Service的测试十分困难，翻看了一些集成Google Game Service的文章，其过程坎坷之程度让人望而却步。于是我将目光转而投向了Amazon Game Service。亚马逊的游戏服务起步要晚些，成熟性肯定不如Google，但在国内来说也不失为另一个不错的选择，Google虽好，但访问不了有啥 法。但似乎国内同行使用Amazon游戏服务的并不多，度娘上相关中文资料甚少。但从Amazon发布的数据来看，其市场正在逐步扩大，并紧紧跟随 Google Play的脚步。 之前用kindle paperwhite时在amazon.com上注册了一个国际帐号，这次正好用这个。不过你要使用Amazon的Game Service，普通Amazon帐号是不行的。你要升级为Amazon的Developer。申请Developer帐号的过程还是蛮繁琐的，要提交一 堆资料，具体细节我大致忘的差不多了，这里就不说了。按照Amazon网站的提示一步一步做就是了。 有了帐号后，你可以下载Amazon的Game SDK了，这个包有近50M大小，本地解压后可以看到其提供的Android SDK种类： AmazonSDK/Android$ ls Ads&#160; AmazonInsights&#160; DeviceMessaging&#160; GameCircle&#160; InAppPurchasing&#160; LoginWithAmazon&#160; Maps&#160; MobileAssociates&#160; README.txt Ads我之前用的是Google Admob，这里就不再用Amazon的了，我需要的是这里的InAppPurchasing和GameCircle。我们接下来一个一个来说。 * Amazon InAppPurchasing Amazon支持三种内购类型：Consumables、Entitlements和Subscriptions： &#160;&#160;&#160; Consumables就像游戏中的红心、金币等，用户可以多次购买，每次可以买多个，并根据游戏规则，每次消耗若干个以达到某种游戏目的；在哪台设备上购买，就只能在哪台设备上使用。 &#160;&#160;&#160; Entitlements是某种授权协议，一个用户只需购买一次，即可长期使用某种特权功能，并与设备无关，可在多个设备下授权使用。比如鳄鱼洗澡游戏中购买高级关卡等。 &#160;&#160;&#160; Subscriptions有订阅的意思，需要某种Entitlements或某种访问权，在一定时间段内绑定有效，到期后自动renew，比如某种杂志的阅读权等。 我只想给游戏增加一些红心功能，一颗红心，可以让游戏者有一次续命的机会，因此我需要实现Consumables型内购。Amazon SDK中提供了Consumeables类内购的Android范例AmazonSDK/Android/InAppPurchasing/samples/SampleIAPConsumablesApp。我们可以参考这个例子来实现我的&#34;红心内购&#34;。 &#160;&#160;&#160; 1、添加依赖的jar包 &#160;&#160;&#160; 在你的游戏proj中添加内购功能所依赖的Amazon SDK jar包，包括AmazonInsights-android-sdk-2.1.26.jar、in-app-purchasing-1.0.3.jar 和login-with-amazon-sdk.jar。 &#160;&#160;&#160; 2、添加源文件 &#160;&#160;&#160; 参照例子，将AppPurchasingObserver.java、AppPurchasingObserverListener.java和MySKU.java拷贝到你的与XXActivity.java同级目录下。 [...]]]></description>
			<content:encoded><![CDATA[<p>由于种种原因，这篇文章已经拖延了N多时间了。今天花了些时间把如何在<a href="http://cocos2d-x.org/">Cocos2d-x</a>(我用的版本是2.2.2)游戏中集成<a href="https://developer.amazon.com/public">Amazon</a>的<a href="https://developer.amazon.com/public/apis/earn/in-app-purchasing">内购</a>和<a href="https://developer.amazon.com/public/apis/engage/gamecircle">GameCircle</a>服务(仅适用于Android版本)整理一下，发出来，作备忘。</p>
<p>	之前在做&ldquo;<a href="http://iwobi.net">手指足球世界杯2014</a>&rdquo;时，想给这款小游戏加上内购(In-App Purchasing)和积分榜(ScoreBoard)功能。说到Android手机游戏的内购，人们第一时间想到的就是<a href="http://play.google.com">Google Play</a>，不过悲催的是，Google Play在国内各种无法访问，行货机也不预装，其相关Service的测试十分困难，翻看了一些集成Google Game Service的文章，其过程坎坷之程度让人望而却步。于是我将目光转而投向了Amazon Game Service。亚马逊的游戏服务起步要晚些，成熟性肯定不如Google，但在国内来说也不失为另一个不错的选择，Google虽好，但访问不了有啥 法。但似乎国内同行使用Amazon游戏服务的并不多，度娘上相关中文资料甚少。但从Amazon发布的数据来看，其市场正在逐步扩大，并紧紧跟随 Google Play的脚步。</p>
<p>	之前用kindle paperwhite时在amazon.com上注册了一个国际帐号，这次正好用这个。不过你要使用Amazon的Game Service，普通Amazon帐号是不行的。你要升级为Amazon的Developer。申请Developer帐号的过程还是蛮繁琐的，要提交一 堆资料，具体细节我大致忘的差不多了，这里就不说了。按照Amazon网站的提示一步一步做就是了。</p>
<p>	有了帐号后，你可以下载Amazon的Game SDK了，这个包有近50M大小，本地解压后可以看到其提供的Android SDK种类：</p>
<p>	<font face="Courier New">AmazonSDK/Android$ ls<br />
	Ads&nbsp; AmazonInsights&nbsp; DeviceMessaging&nbsp; GameCircle&nbsp; InAppPurchasing&nbsp; LoginWithAmazon&nbsp; Maps&nbsp; MobileAssociates&nbsp; README.txt</font></p>
<p>	Ads我之前用的是Google Admob，这里就不再用Amazon的了，我需要的是这里的InAppPurchasing和GameCircle。我们接下来一个一个来说。</p>
<p>	<b>* Amazon InAppPurchasing</b></p>
<p>	Amazon支持三种内购类型：Consumables、Entitlements和Subscriptions：<br />
	&nbsp;&nbsp;&nbsp; Consumables就像游戏中的红心、金币等，用户可以多次购买，每次可以买多个，并根据游戏规则，每次消耗若干个以达到某种游戏目的；在哪台设备上购买，就只能在哪台设备上使用。<br />
	&nbsp;&nbsp;&nbsp; Entitlements是某种授权协议，一个用户只需购买一次，即可长期使用某种特权功能，并与设备无关，可在多个设备下授权使用。比如鳄鱼洗澡游戏中购买高级关卡等。<br />
	&nbsp;&nbsp;&nbsp; Subscriptions有订阅的意思，需要某种Entitlements或某种访问权，在一定时间段内绑定有效，到期后自动renew，比如某种杂志的阅读权等。</p>
<p>	我只想给游戏增加一些红心功能，一颗红心，可以让游戏者有一次续命的机会，因此我需要实现Consumables型内购。Amazon SDK中提供了Consumeables类内购的Android范例<font face="Courier New">AmazonSDK/Android/InAppPurchasing/samples/SampleIAPConsumablesApp</font>。我们可以参考这个例子来实现我的&quot;红心内购&quot;。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>1、添加依赖的jar包</i><br />
	&nbsp;&nbsp;&nbsp; 在你的游戏proj中添加内购功能所依赖的Amazon SDK jar包，包括AmazonInsights-android-sdk-2.1.26.jar、in-app-purchasing-1.0.3.jar 和login-with-amazon-sdk.jar。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>2、添加源文件</i><br />
	&nbsp;&nbsp;&nbsp; 参照例子，将AppPurchasingObserver.java、AppPurchasingObserverListener.java和MySKU.java拷贝到你的与XXActivity.java同级目录下。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>3、初始化Amazon IAP</i><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 在你的XXActivity类中添加如下方法：</p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; public PurchaseDataStorage purchaseDataStorage;</p>
<p>	&nbsp;&nbsp;&nbsp; private void setupIAPOnCreate() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; purchaseDataStorage = new PurchaseDataStorage(this);</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; AppPurchasingObserver purchasingObserver<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = new AppPurchasingObserver(this, purchaseDataStorage);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; purchasingObserver.setListener(this);</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onCreate: registering AppPurchasingObserver&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; PurchasingManager.registerObserver(purchasingObserver);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; protected void onCreate(Bundle savedInstanceState){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setupIAPOnCreate();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <font face="Courier New">protected void onResume() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.onResume();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onResume: call initiateGetUserIdRequest&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; PurchasingManager.initiateGetUserIdRequest();</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onResume: call initiateItemDataRequest for skus: &quot;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; + MySKU.getAll());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; PurchasingManager.initiateItemDataRequest(MySKU.getAll());<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp;<i> 4、添加购买方法</i></p>
<p>	&nbsp;&nbsp;&nbsp; 在Cocos2d-x的某个Scene或Layer中实现的购买方法事件的callback，后者通过Jni调用Java静态方法：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">void BuyHeartScene::buyHearts(int number) {<br />
	#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)<br />
	&nbsp;&nbsp;&nbsp; JniMethodInfo t;<br />
	&nbsp;&nbsp;&nbsp; if (JniHelper::getStaticMethodInfo(t, &quot;net/iwobi/game/flickworldcup/FlickWorldCupActivity&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;onBuyHeartClick&quot;, &quot;(I)V&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;CallStaticVoidMethod(t.classID, t.methodID, number);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (t.env-&gt;ExceptionOccurred()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionDescribe();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionClear();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;DeleteLocalRef(t.classID);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	#endif<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; 该Java方法的实现如下(我这里有五种商品ONEHEART到FIVEHEART)：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">public static void onBuyHeartClick(int type) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String requestId;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch (type) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.ONEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.TWOHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 3:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.THREEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 4:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.FOURHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 5:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.FIVEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.ONEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; PurchaseData purchaseData = ((FlickWorldCupActivity)context).purchaseDataStorage<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .newPurchaseData(requestId);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onBuyHeartClick: requestId (&quot; + requestId<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; + &quot;) requestState (&quot; + purchaseData.getRequestState() + &quot;)&quot;);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; <i>5、修改各种回调方法</i></p>
<p>	&nbsp;&nbsp;&nbsp; 将SampleIAPConsumablesApp/src/com/amazon/sample/iap/consumable /MainActivity.java中的onPurchase为前缀名的方法以及onGetUserIdResponseSuccessful挪到你的 Activity源文件中。这些方法绝大部分是不需要修改的，除非你不喜欢例子中日志输出的格式，或是想用toast之类的提示方式改造各种 callback的结果显示方式。</p>
<p>	&nbsp;&nbsp;&nbsp; 这里我主要修改了一个方法：onPurchaseResponseSuccess。该方法在购买成功后被调用，我们在这个事件发生时更新Scene或Layer的显示(updateHeartInScene)。</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">@Override<br />
	&nbsp;&nbsp;&nbsp; public void onPurchaseResponseSuccess(String userId, String sku,<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String purchaseToken) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onPurchaseResponseSuccess: for userId (&quot; + userId<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; + &quot;) sku (&quot; + sku + &quot;)&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUData skuData = purchaseDataStorage.getSKUData(sku);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (skuData == null)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return;</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.ONEHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(1);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.TWOHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(2);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.THREEHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(3);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.FOURHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(4);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.FIVEHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(5);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; <i>6、AndroidManifest.xml和其他Java文件</i></p>
<p>	&nbsp;&nbsp;&nbsp; AppPurchasingObserver.java和AppPurchasingObserverListener.java你可以原封不动的使用。MySKU.java可以根据你的内购项目做改造：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">public enum MySKU {</p>
<p>	&nbsp;&nbsp;&nbsp; ONEHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.oneheart&quot;, 1),<br />
	&nbsp;&nbsp;&nbsp; TWOHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.twoheart&quot;, 1),<br />
	&nbsp;&nbsp;&nbsp; THREEHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.threeheart&quot;, 1),<br />
	&nbsp;&nbsp;&nbsp; FOURHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.fourheart&quot;, 1),<br />
	&nbsp;&nbsp;&nbsp; FIVEHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.fiveheart&quot;, 1);<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; private String sku;<br />
	&nbsp;&nbsp;&nbsp; private int quantity;</p>
<p>	&nbsp;&nbsp;&nbsp; private MySKU(String sku, int quantity) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.sku = sku;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.quantity = quantity;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static MySKU valueForSKU(String sku) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (ONEHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return ONEHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (TWOHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return TWOHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (THREEHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return THREEHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (FOURHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return FOURHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (FIVEHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return FIVEHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return null;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public String getSku() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return sku;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public int getQuantity() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return quantity;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; private static Set&lt;String&gt; SKUS = new HashSet&lt;String&gt;();<br />
	&nbsp;&nbsp;&nbsp; static {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(ONEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(TWOHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(THREEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(FOURHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(FIVEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static Set&lt;String&gt; getAll() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return SKUS;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;}</p>
<p>	&nbsp;AndroidManifest.xml中在application标签下添加如下配置：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;receiver android:name=&quot;com.amazon.inapp.purchasing.ResponseReceiver&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;action<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.inapp.purchasing.NOTIFY&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:permission=&quot;com.amazon.inapp.purchasing.Permission.NOTIFY&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/receiver&gt;<br />
	&nbsp;有了以上代码，我们的内购就可以运行起来了。</font><b>&nbsp;&nbsp;&nbsp;</p>
<p>	* 内购测试</b></p>
<p>	使用Amazon In-app Purchasing API一个最大好处就是测试简单。Amazon提供一个本地测试程序Amazon App Tester（安装到Android模拟器中），可以模拟内购Server，SDK自动判断当前场景，如果是测试，你的集成了内购SDK的游戏将连接本地 测试程序完成内购流程。通过在本地测试程序中设置模拟不同的内购流程，我们可以轻松完成测试。</p>
<p>	你需要给Amazon App Tester提供一个名为amazon.sdktester.json的文件，这样Amazon App Tester可以知道你的游戏有哪些内购项目，并模拟出这些内购项目。这个json文件可以自行编辑，也可以在Amazon deveoper网站上生成下载。</p>
<p>	我直接将内购项目添加到我的Amazon帐号的游戏应用下面，一共五个，添加成功后，下载json文件。将该文件放在模拟器的/mnt/sdcard下，绝对路径为/mnt/sdcard/amazon.sdktester.json。</p>
<p>	之后，启动App Tester，再启动你的游戏，点击内购项目，看看是否能购买成功。</p>
<p>	<b>* 内购上线</b></p>
<p>	按照Amazon官方说法，SDK会自动区分测试场景和正式场景，因此通过App Tester测试的游戏在发布后，理论上内购是没有问题的。不过我上线后还是遇到了问题，即点击购买某个项目后，游戏没有任何反应，等了若干分钟都是这 样。我将这个问题反馈给Amazon Support，得到的答复居然是游戏代码没有问题，他们测试了若干中机型，都可以打开内购页面，并进行内购。只是有时内购页面打开有些延迟，但都能打 开。看到这里，我猜是否又是大陆网络的问题呢！不管它了，至少通过Amazon Support的回复可以证明我的代码是ok的。只能希望美国人民多多购买我的内购项目了^_^。</p>
<p>	<b>* Amazon游戏圈</b></p>
<p>	想给游戏增加成就榜和成就提交功能，如果自己实现服务端，显然麻烦，工作量大不说，还得维护一个Server。但市面上提供这类服务的游戏平台不多。 Google Play的游戏Service提供这种服务，不过还是上面提到的原因，我与Google的这个服务无缘啊。Amazon Game SDK后期推出了GameCircle服务。</p>
<p>	GameCircle目前提供achievements, leaderboards和Whispersync三种特性：<br />
	&nbsp;&nbsp;&nbsp; achievements就是奖励机制，帮助游戏提高玩家粘性。<br />
	&nbsp;&nbsp;&nbsp; leaderboards类似于积分榜，可以用于提交玩家积分以及显示玩家的全球排名。<br />
	&nbsp;&nbsp;&nbsp; Whispersync是一种数据游戏同步服务，同步玩家进度，保寸玩家个性化数据等。</p>
<p>	这里我要用到的是leaderboards。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>1、建立GameCircle</i><br />
	&nbsp;&nbsp;&nbsp; 使用游戏圈前，你需要在Amazon官方的Amazon Apps &amp; Services Developer Console下创建属于你的Game Circle，然后创建一个LeaderBoard，设置LeaderBoard属性。SDK中提供了GameCircle的Demo：<font face="Courier New">AmazonSDK/Android/GameCircle</font>。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>2、导入jar包，设置AndroidManifest.xml</i><br />
	&nbsp;&nbsp;&nbsp; 要想使用GameCircle，我们需要导入相应的SDK jar包：gamecirclesdk.jar。</p>
<p>	&nbsp;&nbsp;&nbsp; 在AndroidManifest.xml中，需要在application标签下添加以下配置：</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">&lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.ags.html5.overlay.GameCircleUserInterface&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:hardwareAccelerated=&quot;false&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@style/GCOverlay&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/activity&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.identity.auth.device.authorization.AuthorizationActivity&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:allowTaskReparenting=&quot;true&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:launchMode=&quot;singleTask&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@android:style/Theme.NoDisplay&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;action android:name=&quot;android.intent.action.VIEW&quot; /&gt;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; /&gt;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;data<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:host=&quot;net.iwobi.game.flickworldcup&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:scheme=&quot;amzn&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/activity&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.ags.html5.overlay.GameCircleAlertUserInterface&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:hardwareAccelerated=&quot;false&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@style/GCAlert&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/activity&gt;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;receiver<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.identity.auth.device.authorization.PackageIntentReceiver&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:enabled=&quot;true&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;action android:name=&quot;android.intent.action.PACKAGE_INSTALL&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;action android:name=&quot;android.intent.action.PACKAGE_ADDED&quot; /&gt;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;data android:scheme=&quot;package&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/receiver&gt;</font><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 这些配置中需要的res，可以从AmazonSDK/Android/GameCircle/GameCircleSDK/res/中找到并copy到你的project中。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>3、初始化GameCircle</i></p>
<p>	&nbsp;&nbsp;&nbsp; GameCircleSDK这个Demo中没有提供太多源码，src目录下是空的。因此我们只能参考Amazon Developer站点上页面上的说明一步步的添加和调整我们的代码了。</p>
<p>	&nbsp;&nbsp;&nbsp; 在你的XXActivity类中，我们添加如下方法：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">//reference to the agsClient<br />
	&nbsp;&nbsp;&nbsp; public AmazonGamesClient agsClient;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; AmazonGamesCallback callback = new AmazonGamesCallback() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onServiceNotReady(AmazonGamesStatus status) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; switch (status) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // The SDK failed to initialize correctly.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case CANNOT_INITIALIZE:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onServiceNotReady: CANNOT_INITIALIZE&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; msg.obj = &quot;Can not initialize Amazon Game Services&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // The SDK is in the process of initializing.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case INITIALIZING:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onServiceNotReady: INITIALIZING&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; msg.obj = &quot;Initializing Amazon Game Services&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // The device not registered with an account<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case NOT_AUTHENTICATED:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onServiceNotReady: NOT_AUTHENTICATED&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; msg.obj = &quot;The Device does not registered with an account&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // The game is not authorized to use this service.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case NOT_AUTHORIZED:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onServiceNotReady: NOT_AUTHORIZED&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; msg.obj = &quot;Not authorized to use Amazon Game Services&quot;;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //unable to use service<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 21;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onServiceReady(AmazonGamesClient amazonGamesClient) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; agsClient = amazonGamesClient;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //ready to use GameCircle<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (agsClient != null)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;on AmazonGamesCallback: call onServiceReady, agsClient init ok&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;on AmazonGamesCallback: call onServiceReady, agsClient init failed&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; };<br />
	&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; //list of features your game uses (in this example, achievements and leaderboards)<br />
	&nbsp;&nbsp;&nbsp; EnumSet&lt;AmazonGamesFeature&gt; myGameFeatures = EnumSet.of(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AmazonGamesFeature.Leaderboards);</font></p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; protected void onResume() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.onResume();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; AmazonGamesClient.initialize(this, <b>callback, myGameFeatures</b>);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp; &nbsp;&nbsp; &nbsp; <font face="Courier New">public void onPause() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onPause();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (agsClient != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; agsClient.release();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</font><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; <i>4、提交成就积分</i></p>
<p>	&nbsp;&nbsp;&nbsp; 当玩家结束游戏时，可以选择将此次的高分上传到leaderboards上。游戏中应对积分提交的代码也在XXActivity中。</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">public static void onSubmitScoreToLeaderBoard(int score) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (((FlickWorldCupActivity)context).agsClient == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 21;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = &quot;Unable to use Amazon Game Services&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; LeaderboardsClient lbClient = ((FlickWorldCupActivity)context).agsClient.getLeaderboardsClient();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; AGResponseHandle&lt;SubmitScoreResponse&gt; handle = lbClient.submitScore(&quot;<b>FlickWorldCupTopScore</b>&quot;, score);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Optional callback to receive notification of success/failure.<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; handle.setCallback(new AGResponseCallback&lt;SubmitScoreResponse&gt;() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public void onComplete(SubmitScoreResponse result) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (result.isError()) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Add optional error handling here.&nbsp; Not strictly required<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // since retries and on-device request caching are automatic.<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 22;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = &quot;Submit Score to LeaderBoard Failed!&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Continue game flow.<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 23;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = &quot;Submit Score to LeaderBoard OK!&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; });&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; 如果仅是查看积分排行，可以用下面这个方法：</p>
<p>	&nbsp;&nbsp;&nbsp;<font face="Courier New"> public static void onShowLeaderBoardOverlay() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (((FlickWorldCupActivity)context).agsClient == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 21;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = &quot;Unable to use Amazon Game Services&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; LeaderboardsClient lbClient = ((FlickWorldCupActivity)context).agsClient.getLeaderboardsClient();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; AGResponseHandle&lt;RequestResponse&gt; handle = lbClient.showLeaderboardOverlay(&quot;FlickWorldCupTopScore&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; handle.setCallback(new AGResponseCallback&lt;RequestResponse&gt;() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public void onComplete(RequestResponse result) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (result.isError()) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Add optional error handling here.&nbsp; Not strictly required<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // since retries and on-device request caching are automatic.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onShowLeaderBoardOverlay &#8211; onComplete: Show LeaderBoard Request Failed!&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; });&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp;<br />
	<b>* 游戏圈上线</b></p>
<p>	游戏圈无法在本地进行测试，只能在真实的游戏圈中测试代码是否ok。不过Amazon的游戏圈提供了管理功能，在测试后发布前可将游戏圈 leaderboard的值reset。游戏圈leaderboard发布后，你就可以使用leaderboard了。游戏圈功能在国内访问是没有任何问 题的，查看积分榜，提交分数到积分榜都很顺畅。</p>
<p>	<b>* 小结</b></p>
<p>	Amazon游戏SDK在国内的应用估计比较小众，大家可能更多的选择用Google Play提供的服务或是AppStore的，但Amazon毕竟为游戏开发者提供了一个选择（而且是完全免费的哦），另外Amazon的Support对 提交问题的反馈较为及时(无论是mail还是forum上的提问)，基本24小时内就会有答复。各种设施的发布也比较快，有时候3-4个小时即可生效。</p>
<p>	目前Amazon Game SDK的资料多为英文，且集中在Amazon官方站点以及官方维护的<a href="http://forums.developer.amazon.com/forums/index.jspa">support论坛</a>中。遇到问题，亚马逊的论坛是第一选择。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/08/04/amazon-inapp-purchasing-and-gamecirle-in-cocos2dx/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x屏幕适配之Sprite绘制原理</title>
		<link>https://tonybai.com/2014/05/13/sprite-draw-principles-of-cocos2dx-screen-adaptation/</link>
		<comments>https://tonybai.com/2014/05/13/sprite-draw-principles-of-cocos2dx-screen-adaptation/#comments</comments>
		<pubDate>Mon, 12 May 2014 17:04:14 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[android-native-app-glue]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CGL]]></category>
		<category><![CDATA[Cocos2d]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Frustum]]></category>
		<category><![CDATA[Game-Engine]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[MobileGame]]></category>
		<category><![CDATA[NativeActivity]]></category>
		<category><![CDATA[NDK]]></category>
		<category><![CDATA[opengl]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[projection]]></category>
		<category><![CDATA[Screen-Adaptation]]></category>
		<category><![CDATA[Sprite]]></category>
		<category><![CDATA[viewport]]></category>
		<category><![CDATA[内存计数]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[屏幕适配]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[平头截体]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[性能优化]]></category>
		<category><![CDATA[手游]]></category>
		<category><![CDATA[投影变换]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[模型视图变换]]></category>
		<category><![CDATA[游戏引擎]]></category>
		<category><![CDATA[源码分析]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[精灵]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[视口变换]]></category>
		<category><![CDATA[触控科技]]></category>
		<category><![CDATA[跨平台]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1533</guid>
		<description><![CDATA[手机(智能终端)游戏绝大多数为全屏(Full Screen)显示，这样开发人员在制作游戏时势必要考虑不同手机(智能终端）屏幕大小、宽高比的不同给游戏画面带来的影响，并且要将这种影响降低到最 小，努力使用不同终端的游戏玩家拥有几乎相同的游戏画面体验。为此各种游戏引擎在屏幕适配方面都给出了自己的方案，Cocos2d-x也不例外。 在Cocos2d-x官网Wiki上特地撰写了一篇讲解Cocos2d-x多屏幕适配原理的文章&#8220;Detailed explanation of Cocos2d-x Multi-resolution adaptation&#8221;。 这里我们以Cocos2d-x引擎（基于2.2.2版本）自带的Sample项目HelloCpp(cocos2d-x-2.2.2/samples/Cpp/HelloCpp）为例，直观的看看这个方案带来的好 处。首先，我们对HelloCpp项目做些许改造： &#160;&#160;&#160; &#8211; 注释掉AppDelegate.cpp中applicationDidFinishLaunching下的pEGLView-&#62;setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, kResolutionNoBorder); &#160;&#160;&#160; &#8211; 仅使用Resource/iphone下的资源，即仅searchPath.push_back(smallResource.directory)； 这里我们有一张480&#215;320分辨率大小PNG文件。 &#160;&#160;&#160; &#8211; 通过改变proj.linux/main.cpp中的eglView-&#62;setFrameSize(960, 640);来改变屏幕参数。（用linux工程模拟甚为方便，编译和运行占用资源小，极为迅捷，效果与Android平台是等 效的） 我们对比一下以下三种条件下的游戏Demo显示结果： &#160;&#160;&#160; 1) 屏幕大小480&#215;320，未做任何屏幕适配工作，不调用pEGLView-&#62;setDesignResolutionSize。 &#160;&#160;&#160; 2) 屏幕大小960&#215;640，未做任何屏幕适配工作，不调用pEGLView-&#62;setDesignResolutionSize。 &#160;&#160;&#160; 3) 屏幕大小同为960&#215;640，按照上面Cocos2d-x屏幕适配指南Wiki中的做法，调用pEGLView-&#62;setDesignResolutionSize(480, 320); 如我们所料，我们得到三个截然不同的结果。 第一种情况，我们所得到的游戏屏幕截图如下： 第二种情况，我们所得到的游戏屏幕截图如下： 第三种情况，我们所得到的游戏屏幕截图如下： 第一种情况是最理想的情况，屏幕大小与背景图片大小相同，如我们所愿，屏幕与背景图片吻合的天衣无缝。 第二种情况显然是模拟我们初次遇到问题的场景。屏幕Size扩大为原先的二倍，在资源没有变化的情况下，我们发现480&#215;320大小的背景图片没 有铺满屏幕，仅仅是居中显示，并在四周露出较多&#8221;黑边&#8220;，这显然不是我们想要的。 第三种情况，也就是我们按照官方屏幕适配方案调整后得到的结果，在资源依旧不变的情况下，我们得到了相对令人满意的结果：背景图片恰如其分的铺满 整个屏幕，比例正确。这样我们用一套资源就可以同时适配两个屏幕了：480&#215;320、960&#215;640。这两种终端的玩家至少不会对我们的游戏心生 抱怨之情^_^。 当然在遇到第二种情况的时候，你也大可再准备一套新资源，比如一张960&#215;640的背景图片。在480&#215;320手机上，使用480&#215;320的图 片；在960&#215;640的手机上，使用960&#215;640的背景图片。但这种方法的弊端至少有三： &#160;&#160;&#160; &#8211; 包大了：游戏的安装包Size急剧变大。 &#160;&#160;&#160; &#8211; 活儿多了：因适配屏幕种类太多而制作大量的图片。 &#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>手机(智能终端)游戏绝大多数为全屏(Full Screen)显示，这样开发人员在制作游戏时势必要考虑不同手机(智能终端）屏幕大小、宽高比的不同给游戏画面带来的影响，并且要将这种影响降低到最 小，努力使用不同终端的游戏玩家拥有几乎相同的游戏画面体验。为此各种游戏引擎在屏幕适配方面都给出了自己的方案，<a href="http://www.cocos2d-x.org">Cocos2d-x</a>也不例外。 在Cocos2d-x官网Wiki上特地撰写了一篇讲解Cocos2d-x多屏幕适配原理的文章&ldquo;<a href="http://www.cocos2d-x.org/wiki /Detailed_explanation_of_Cocos2d-x_Multi-resolution_adaptation">Detailed explanation of Cocos2d-x Multi-resolution adaptation</a>&rdquo;。</p>
<p>这里我们以Cocos2d-x引擎（基于2.2.2版本）自带的Sample项目HelloCpp(<font face="Courier&lt;br /&gt;&lt;br /&gt;<br />
      New">cocos2d-x-2.2.2/samples/Cpp/HelloCpp</font>）为例，直观的看看这个方案带来的好 处。首先，我们对HelloCpp项目做些许改造：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 注释掉AppDelegate.cpp中applicationDidFinishLaunching下的<font face="Courier New">pEGLView-&gt;setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, kResolutionNoBorder)</font>;<br />
	&nbsp;&nbsp;&nbsp; &#8211; 仅使用Resource/iphone下的资源，即仅<font face="Courier New">searchPath.push_back(smallResource.directory)</font>； 这里我们有一张480&#215;320分辨率大小PNG文件。<br />
	&nbsp;&nbsp;&nbsp; &#8211; 通过改变proj.linux/main.cpp中的<font face="Courier New">eglView-&gt;setFrameSize(960, 640)</font>;来改变屏幕参数。（用linux工程模拟甚为方便，编译和运行占用资源小，极为迅捷，效果与Android平台是等 效的）</p>
<p>我们对比一下以下三种条件下的游戏Demo显示结果：<br />
	&nbsp;&nbsp;&nbsp; 1) 屏幕大小480&#215;320，未做任何屏幕适配工作，不调用pEGLView-&gt;setDesignResolutionSize。<br />
	&nbsp;&nbsp;&nbsp; 2) 屏幕大小960&#215;640，未做任何屏幕适配工作，不调用pEGLView-&gt;setDesignResolutionSize。<br />
	&nbsp;&nbsp;&nbsp; 3) 屏幕大小同为960&#215;640，按照上面Cocos2d-x屏幕适配指南Wiki中的做法，调用<font face="Courier New">pEGLView-&gt;setDesignResolutionSize(480, 320);</font></p>
<p>如我们所料，我们得到三个截然不同的结果。</p>
<p>第一种情况，我们所得到的游戏屏幕截图如下：<br />
	<img alt="" src="/wp-content/uploads/origin-size.jpg" style="height: 320px; width: 441px;" /></p>
<p>第二种情况，我们所得到的游戏屏幕截图如下：<br />
	<img alt="" src="/wp-content/uploads/before-setdesignresolution.jpg" style="height: 320px; width: 459px;" /></p>
<p>第三种情况，我们所得到的游戏屏幕截图如下：</p>
<p><img alt="" src="/wp-content/uploads/after-setdesignresolution.jpg" style="width: 480px; height: 334px;" /></p>
<p>第一种情况是最理想的情况，屏幕大小与背景图片大小相同，如我们所愿，屏幕与背景图片吻合的天衣无缝。<br />
	第二种情况显然是模拟我们初次遇到问题的场景。屏幕Size扩大为原先的二倍，在资源没有变化的情况下，我们发现480&#215;320大小的背景图片没 有铺满屏幕，仅仅是居中显示，并在四周露出较多&rdquo;黑边&ldquo;，这显然不是我们想要的。<br />
	第三种情况，也就是我们按照官方屏幕适配方案调整后得到的结果，在资源依旧不变的情况下，我们得到了相对令人满意的结果：背景图片恰如其分的铺满 整个屏幕，比例正确。这样我们用一套资源就可以同时适配两个屏幕了：480&#215;320、960&#215;640。这两种终端的玩家至少不会对我们的游戏心生 抱怨之情^_^。</p>
<p>当然在遇到第二种情况的时候，你也大可再准备一套新资源，比如一张960&#215;640的背景图片。在480&#215;320手机上，使用480&#215;320的图 片；在960&#215;640的手机上，使用960&#215;640的背景图片。但这种方法的弊端至少有三：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 包大了：游戏的安装包Size急剧变大。<br />
	&nbsp;&nbsp;&nbsp; &#8211; 活儿多了：因适配屏幕种类太多而制作大量的图片。<br />
	&nbsp;&nbsp;&nbsp; &#8211; 新屏幕出来咋办：如果某个厂家突然于某天出品一款手机，其分辨率与以往市面上的所有手机均不同，那你的游戏因没有对应的资源，肯定无法很好适配该手机，导 致较差用户体验。</p>
<p>为此，适配屏幕唯一的出路似乎只有按照官方推荐的方案进行了，当然适当结合有限种类的资源也许可以更好的提升游戏体验。</p>
<p>如果仅仅从游戏制作角度来看，我们找到了可以适配屏幕的方法就可以了，没有必要刨根问底。甚至当有人问起来：为何 setDesignResolutionSize后，背景图片就可以充满屏幕了呢？我们可以回答：&ldquo;引擎对精灵进行了缩放，就是这样&rdquo;。但对于上 面的背景精灵来说，真的是我们理解的普通意义上的&ldquo;精灵缩放(Scale)吗？本着&ldquo;知其然，也要知其所以然&rdquo;的精神，这里对引擎如何对 Sprite进行绘制进行了一番研究，我还真发现了一些与我之前理解差异较大的&ldquo;深奥&rdquo;原理，这里与大家一起分享一下。</p>
<p><b>一、绘制参数初始化</b></p>
<p>我们还是从代码开始，了解一下引擎绘制参数的初始化工作是如何做的、在哪里做的，为后续的分析做些铺垫。这里以Cocos2d-x 2.2.2 Android平台为例。关于Cocos2d-x 2.2.2 Android平台的引擎粗线条启动流程分析，可以参考《<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">Hello，Cocos2d-x</a>》这篇文章。看完这篇文章，你就会知道我们这次应该从<font face="Courier New">Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit</font>开 始。</p>
<p><font face="Courier New">// samples/Cpp/HelloCpp/proj.android/jni/hellocpp/main.cpp<br />
	void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JNIEnv*&nbsp; env, jobject thiz, jint w, jint h)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (!CCDirector::<b>sharedDirector</b>()-&gt;getOpenGLView())<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCEGLView *view = CCEGLView::sharedOpenGLView();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>view-&gt;setFrameSize(w, h);</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AppDelegate *pAppDelegate = new AppDelegate();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCApplication::sharedApplication()-&gt;run();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">这里是引擎部分初始化的起点:CCDirector和CCEGLView先后完成创建与初始化。接下来我们分别看一下这两个过程，我们主要关 注与绘制参数设置相关的内容：</font></p>
<p><font face="Courier New">bool CCDirector::init(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; <b>setDefaultValues</b>();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; m_obWinSizeInPoints = CCSizeZero;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; m_pobOpenGLView = NULL;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; m_fContentScaleFactor = 1.0f;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; return true;<br />
	}</font></p>
<p><font face="Courier New">void CCDirector::setDefaultValues(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCConfiguration *conf =<br />
	&nbsp;&nbsp;&nbsp;&nbsp; CCConfiguration::sharedConfiguration();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; // GL projection<br />
	&nbsp;&nbsp;&nbsp; const char *projection =<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf-&gt;getCString(&quot;cocos2d.x.gl.projection&quot;,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &quot;<b>3d</b>&quot;);<br />
	&nbsp;&nbsp;&nbsp; if( strcmp(projection, &quot;3d&quot;) == 0 )<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_eProjection = <b>kCCDirectorProjection3D</b>;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">由于conf中没有配置&ldquo;cocos2d.x.gl.projection&rdquo;，因此projection使用了 getCString传入的默认值：&quot;3d&quot;，m_eProjection则被赋值为kCCDirectorProjection3D。</font></p>
<p><font face="Courier New">CCEGLView的创建更为简单：</font></p>
<p><font face="Courier New">CCEGLView::CCEGLView()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; initExtensions();<br />
	}</font></p>
<p><font face="Courier New">但背后真正发挥关键作用的是其父类CCEGLViewProtocol。</font></p>
<p><font face="Courier New">CCEGLViewProtocol::CCEGLViewProtocol()<br />
	: m_pDelegate(NULL)<br />
	, m_fScaleX(1.0f)<br />
	, m_fScaleY(1.0f)<br />
	, m_eResolutionPolicy(kResolutionUnKnown)<br />
	{<br />
	}</font></p>
<p><font face="Courier New">这里我们看到了三个重要的字段：m_fScaleX、m_fScaleY以及m_eResolutionPolicy，这三个字段对于后续屏 幕适配起到至关重要的作用。</font></p>
<p><font face="Courier New">nativeInit中的view-&gt;SetFrameSize(w, h)用于设置的屏幕物理分辨率，如果你的手机是960&#215;640分辨率的，那FrameSize就是960&#215;640。</font></p>
<p><font face="Courier New">void CCEGLViewProtocol::setFrameSize(float width,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float height)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; m_obDesignResolutionSize<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = m_obScreenSize<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = CCSizeMake(width, height);<br />
	}<br />
	初始情况下，CCEGLViewProtocol将&ldquo;设计分辨率&rdquo;m_obDesignResolutionSize也设置为与 FrameSize or m_obScreenSize同等大小。</font></p>
<p><font face="Courier New">我们回到游戏逻辑层代码AppDelegate.cpp，我们知道游戏逻辑的入口在这里，最初的参数初始化是在为Director设置 GLView实例时进行的：</font></p>
<p><font face="Courier New">bool AppDelegate::applicationDidFinishLaunching() {<br />
	&nbsp;&nbsp;&nbsp; // initialize director<br />
	&nbsp;&nbsp;&nbsp; CCDirector* pDirector = CCDirector::sharedDirector();<br />
	&nbsp;&nbsp;&nbsp; CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; pDirector-&gt;<b>setOpenGLView</b>(pEGLView);<br />
	&nbsp;&nbsp;&nbsp; CCSize frameSize = pEGLView-&gt;getFrameSize();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">void CCDirector::setOpenGLView(CCEGLView *pobOpenGLView)<br />
	{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pobOpenGLView = pobOpenGLView;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // set size<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_obWinSizeInPoints =<br />
	<b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pobOpenGLView-&gt;getDesignResolutionSize()</b>;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (m_pobOpenGLView)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>setGLDefaultValues();</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CHECK_GL_ERROR_DEBUG();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">由于尚未调用setDesignResolutionSize，因此m_obWinSizeInPoints的值与FrameSize大小相 同。</font></p>
<p><font face="Courier New">setGLDefaultValues最为关键，这是我们第一次遇到该函数，该方法用于初始化一些OpenGL的参数，建立好后续 OpenGL操作时所需要的各种数据结构。</font></p>
<p><font face="Courier New">void CCDirector::setGLDefaultValues(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; setAlphaBlending(true);<br />
	&nbsp;&nbsp;&nbsp; setDepthTest(false);<br />
	&nbsp;&nbsp;&nbsp; <b>setProjection(</b>m_eProjection);<br />
	&nbsp;&nbsp;&nbsp; // set other opengl default values<br />
	&nbsp;&nbsp;&nbsp; glClearColor(0.0f, 0.0f, 0.0f, 1.0f);<br />
	}</font></p>
<p><font face="Courier New">glClearColor(0.0f, 0.0f, 0.0f, 1.0f);设置初始颜色为黑色，alpha为1.0f，即完全不透明。setProjection是实际上绘制参数设置的核心。</font></p>
<p><font face="Courier New">void CCDirector::setProjection(ccDirectorProjection kProjection)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCSize size = m_obWinSizeInPoints;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <b>setViewport</b>();<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; switch (kProjection)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp; case kCCDirectorProjection3D:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float zeye = this-&gt;<b>getZEye</b>();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmMat4 matrixPerspective, matrixLookup;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmGLMatrixMode(KM_GL_PROJECTION);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmGLLoadIdentity();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // issue #1334<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmMat4PerspectiveProjection</b>( &amp;matrixPerspective,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; 60,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLfloat)size.width/size.height,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; 0.1f, zeye*2);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixPerspective);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmGLMatrixMode(KM_GL_MODELVIEW);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmGLLoadIdentity();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmVec3 eye, center, up;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;eye, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, zeye );<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;center, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, 0.0f );<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;up, 0.0f, 1.0f, 0.0f);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmMat4LookAt</b>(&amp;matrixLookup, &amp;eye,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;center, &amp;up);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixLookup);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; m_eProjection = kProjection;<br />
	&nbsp;&nbsp;&nbsp; ccSetProjectionMatrixDirty();<br />
	}</font></p>
<p><font face="Courier New">由于前面m_eProjection已经被赋值为kCCDirectorProjection3D，因此我们只分析 kCCDirectorProjection3D这个case分支。该函数大致进行设置的顺序是：设置视口变换（ViewPort)、设置投影变换矩阵和 设置模型视图变换矩阵。我们分别来看：</font></p>
<p><font face="Courier New">&nbsp;<b>* 设置视口(ViewPort)</b></font></p>
<p><font face="Courier New">void CCDirector::setViewport()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (m_pobOpenGLView)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pobOpenGLView-&gt;<b>setViewPortInPoints</b>(0, 0,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_obWinSizeInPoints.width,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_obWinSizeInPoints.height);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">void CCEGLViewProtocol::setViewPortInPoints(float x ,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; float y , float w , float h)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; <b>glViewport</b>((GLint)(x * m_fScaleX<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + m_obViewPortRect.origin.x),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLint)(y * m_fScaleY<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + m_obViewPortRect.origin.y),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLsizei)(w * m_fScaleX),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLsizei)(h * m_fScaleY));<br />
	}</font></p>
<p><font face="Courier New">这是我们遇到的第一个OpenGL概念：设置视口变换，关于视口变换究竟起到什么作用，后续会细说。</font></p>
<p><font face="Courier New">&nbsp;<b>* 设置&ldquo;投影变换&rdquo;矩阵参数</b></font></p>
<p><font face="Courier New">&nbsp;kmMat4PerspectiveProjection( &amp;<b>matrixPerspective</b>, 60,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLfloat)size.width/size.height, 0.1f, zeye*2);<br />
	&nbsp;kmGLMultMatrix(&amp;matrixPerspective);</font></p>
<p><font face="Courier New">&nbsp;<b>* 设置&ldquo;模型视图变换&rdquo;矩阵参数</b></font></p>
<p><font face="Courier New">&nbsp;kmVec3 eye, center, up;<br />
	&nbsp;kmVec3Fill( &amp;eye, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, zeye );<br />
	&nbsp;kmVec3Fill( &amp;center, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, 0.0f );<br />
	&nbsp;kmVec3Fill( &amp;up, 0.0f, 1.0f, 0.0f);<br />
	&nbsp;kmMat4LookAt(&amp;<b>matrixLookup</b>, &amp;eye,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;center, &amp;up);</font></p>
<p><font face="Courier New">至此，引擎的绘制参数初始化设置就OK了，在你调用setDesignResolutionSize之前，这些参数不会被改变。</font></p>
<p><b>二、kazmath</b></p>
<p>Cocos2d-x引擎最底层采用OpenGL ES 2.0进行图形绘制，这样要想搞清楚前面的问题缘由，对OpenGL那一套技术体系至少要有一些直观认识才行。在这之前，我们还要先了解一些 Cocos2d-x深度使用的kazmath库。<font face="Courier New">根据《<a href="http://book.douban.com/subject/24704115/">Cocos2d-x高级开发教程</a>》书 中说: &ldquo;因为在Cocos2d-x 2.0采用的OpenGL ES 2.0中，而那些OpenGL ES 1.0函数已经不可使用了。但OpenGL ES 2.0已经放弃了固定的渲染流水线，取而代之的是自定义的各种着色器，在这种情况下变换操作通常需要由开发者来维护。所幸引擎也引入了一套第三方库 Kazmath，它使得我们几乎可以按照原来OpenGL ES 1.0所采用的方式进行开发&rdquo;。</font></p>
<p><font face="Courier New">至此，我们大致知道了Kazmath库是用来辅助我们按照OpenGL ES 1.0的方式管理变换矩阵以及做变换操作的，接下来我们一起来看看kazmath库的结构吧：</font></p>
<p><font face="Courier New">//cocos2d-x-2.2.2/cocos2dx/kazmath/src/GL/matrix.c</font></p>
<p><font face="Courier New">km_mat4_stack modelview_matrix_stack;<br />
	km_mat4_stack projection_matrix_stack;<br />
	km_mat4_stack texture_matrix_stack;<br />
	km_mat4_stack* current_stack = NULL;<br />
	static unsigned char initialized = 0;</font></p>
<p><font face="Courier New">以上是Cocos2d-x整个引擎生命周期内会用到的与opengl变换矩阵相关的一些全局变量。</font></p>
<p><font face="Courier New">kazmath声明了三个变换矩阵的栈，modelview_matrix_stack（模型视图矩阵栈）、 projection_matrix_stack（投影矩阵栈）以及texture_matrix_stack（纹理矩阵栈）。不过Cocos2d-x引 擎只用到了前两个变化矩阵栈。current_stack指向当前所使用的那个变换矩阵栈。</font></p>
<p><font face="Courier New">这些栈的初始化在lazyInitialize中：</font></p>
<p><font face="Courier New">void lazyInitialize()<br />
	{</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (!initialized) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmMat4 identity; //Temporary identity matrix</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Initialize all 3 stacks<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //modelview_matrix_stack =<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (km_mat4_stack*) malloc(sizeof(km_mat4_stack));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_initialize(&amp;modelview_matrix_stack);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //projection_matrix_stack =<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (km_mat4_stack*) malloc(sizeof(km_mat4_stack));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_initialize(&amp;projection_matrix_stack);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //texture_matrix_stack =<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (km_mat4_stack*) malloc(sizeof(km_mat4_stack));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_initialize(&amp;texture_matrix_stack);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; current_stack = &amp;<b>modelview_matrix_stack</b>;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; initialized = 1;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmMat4Identity(&amp;identity);</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Make sure that each stack has the identity matrix<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_push(&amp;modelview_matrix_stack, &amp;identity);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_push(&amp;projection_matrix_stack, &amp;identity);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_push(&amp;texture_matrix_stack, &amp;identity);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">kmMat4Identify用于初始化&ldquo;单位矩阵(Indentify Matrix)&rdquo;，所谓&quot;单位矩阵&quot;，指的是对脚线上元素都为1的矩阵。从kmMat4Identify的实现，我们也可以看出这一点：</font></p>
<p><font face="Courier New">kmMat4* const kmMat4Identity(kmMat4* pOut)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; memset(pOut-&gt;mat, 0, sizeof(float) * 16);<br />
	&nbsp;&nbsp;&nbsp; <b>pOut-&gt;mat[0] = pOut->mat[5]<br />
	&nbsp; &nbsp;&nbsp; = pOut-&gt;mat[10]<br />
	&nbsp;&nbsp;&nbsp;&nbsp; = pOut-&gt;mat[15] = 1.0f;</b><br />
	&nbsp;&nbsp;&nbsp; return pOut;<br />
	}</font></p>
<p><font face="Courier New">最后，lazyInitialize函数将单位矩阵分别圧入（km_mat4_stack_push）不同的matrix stack。</font></p>
<p><font face="Courier New">再回顾一下CCDirector::setProjection，该函数通过kazmath先后设置了 projection_matrix_stack和modelview_matrix_stack的top元素。</font></p>
<p><font face="Courier New">&nbsp;&nbsp; kmGLMatrixMode(<b>KM_GL_PROJECTION</b>);<br />
	&nbsp;&nbsp; kmGLLoadIdentity();<br />
	&nbsp;&nbsp; <b>kmMat4PerspectiveProjection</b>( &amp;matrixPerspective, 60,<br />
	&nbsp;&nbsp;&nbsp;&nbsp; (GLfloat)size.width/size.height, 0.1f, zeye*2);<br />
	&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixPerspective);<br />
	&nbsp;&nbsp;<br />
	&nbsp;&nbsp; kmGLMatrixMode(<b>KM_GL_MODELVIEW</b>);<br />
	&nbsp;&nbsp; kmGLLoadIdentity();<br />
	&nbsp;&nbsp; kmVec3 eye, center, up;<br />
	&nbsp;&nbsp; kmVec3Fill( &amp;eye, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, zeye );<br />
	&nbsp;&nbsp; kmVec3Fill( &amp;center, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, 0.0f );<br />
	&nbsp;&nbsp; kmVec3Fill( &amp;up, 0.0f, 1.0f, 0.0f);<br />
	&nbsp;&nbsp; <b>kmMat4LookAt</b>(&amp;matrixLookup, &amp;eye,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;center, &amp;up);<br />
	&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixLookup);</font></p>
<p><font face="Courier New"><b>三、精灵绘制</b></font></p>
<p><font face="Courier New">由《<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">Hello，Cocos2d-x</a>》一文我们知道，一旦引擎初始化完毕，就开始了每帧图像的绘制工作，Render Thread在一个&ldquo;死循环&rdquo;中反复调用CCDirector的drawScene方法 （CCDisplayLinkDirector::mainLoop中调用了drawScene）：</font></p>
<p><font face="Courier New">void CCDirector::drawScene(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; glClear(GL_COLOR_BUFFER_BIT<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | GL_DEPTH_BUFFER_BIT);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; <b>kmGLPushMatrix</b>();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // draw the scene<br />
	&nbsp;&nbsp;&nbsp; if (m_pRunningScene)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRunningScene-&gt;<b>visit</b>();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; <b>kmGLPopMatrix</b>();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">Cocos2d-x采用&ldquo;渲染树&rdquo;的方式进行绘制，即先从场景(Scene)的顶层根节点开始，深度优先的递归绘制Child Node。而整个绘制的顶层节点是CCScene。绘制从m_pRunningScene-&gt;visit()真正开始。visit是Scene、 Layer、Sprite的共同父类CCNode实现的方法：</font></p>
<p><font face="Courier New">void CCNode::visit()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (!m_bVisible)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; <b>kmGLPushMatrix</b>();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; this-&gt;<b>transform</b>();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; if(m_pChildren &amp;&amp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pChildren-&gt;count() &gt; 0)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; sortAllChildren();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // <b>draw children zOrder</b> &lt; 0<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; ..<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // self draw<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this-&gt;<b>draw</b>();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // <b>draw other children nodes</b><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this-&gt;draw();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; <b>kmGLPopMatrix</b>();<br />
	}<br />
	&nbsp;&nbsp;&nbsp;<br />
	Visit大致做了这么几件事：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 向当前OpenGL变换矩阵栈Push元素<br />
	&nbsp;&nbsp;&nbsp; &#8211; 用当前OpenGL变换矩阵栈栈顶元素的变换参数做节点变换<br />
	&nbsp;&nbsp;&nbsp; &#8211; 递归绘制zOrder &lt; 0 的子节点<br />
	&nbsp;&nbsp;&nbsp; &#8211; 绘制自己<br />
	&nbsp;&nbsp;&nbsp; &#8211; 递归绘制其他子节点<br />
	&nbsp;&nbsp;&nbsp; &#8211; 从当前OpenGL变换矩阵栈Pop元素</font></p>
<p><font face="Courier New">如果你想知道为什么父节点缩放(Scale)、旋转(Rotate)、扭曲(Skew)后，子节点也会跟着父节点同样缩放(Scale)、旋 转(Rotate)、扭曲？其原理就在这里的transform方法中：</font></p>
<p><font face="Courier New">void CCNode::transform()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; kmMat4 transfrom4x4;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // Convert 3&#215;3 into 4&#215;4 matrix<br />
	&nbsp;&nbsp;&nbsp; CCAffineTransform tmpAffine<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = this-&gt;nodeToParentTransform();<br />
	&nbsp;&nbsp;&nbsp; CGAffineToGL(&amp;tmpAffine,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; transfrom4x4.mat);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // Update Z vertex manually<br />
	&nbsp;&nbsp;&nbsp; transfrom4x4.mat[14] = m_fVertexZ;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmGLMultMatrix( &amp;transfrom4x4 );<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">在进入tranform以前，Cocos2d-x做了啥？对了，kmGLPushMatrix()：</font></p>
<p><font face="Courier New">void kmGLPushMatrix(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; kmMat4 top;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; lazyInitialize();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //<b>Duplicate the top of the stack (i.e the current matrix)</b><br />
	&nbsp;&nbsp;&nbsp; kmMat4Assign(&amp;top, current_stack-&gt;top);<br />
	&nbsp;&nbsp;&nbsp; km_mat4_stack_push(current_stack, &amp;top);<br />
	}</font></p>
<p><font face="Courier New">在引擎初始化后，我们的current_stack是模型视图矩阵栈modelview_matrix_stack。所有设置的初始参数都保 存在该栈的栈顶元素中。在每次Node绘制前，Node都会创建自己的变换矩阵，但这个矩阵不是凭空创造的，从kmGLPushMatrix 可以看出，在当前Node将新创建的矩阵元素圧栈前，它复制了原栈顶元素，也就<b>携带有父节点所有的初始变换信息</b>，也就是说在 km_mat4_stack_push后，栈顶放置的元素其实是原栈顶元素的复制品，而后续所有操作都是基于这个复制品的。这样一来，如果父 节点做了缩放或旋转或扭曲，那这些信息都会作为初始信息作为子节点变换的基础，后续子节点自身的变换参数也都是在这个基础上做出的，最终的矩 阵是transform方法中的kmGLMultMatrix后得出的。真正的矩阵变换计算都在nodeToParentTransform 中，不过要想看懂这个函数，需要对OpenGL有更深入的了解才行，这里略过^_^。</font></p>
<p><font face="Courier New">真正绘制Node的方法是CCNode::draw的override方法。CCNode::draw是一个空函数，各个子类 override该方法进行各自的绘制。以CCSprite::draw为例：</font></p>
<p><font face="Courier New">void CCSprite::draw(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CC_NODE_DRAW_SETUP();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ccGLBindTexture2D( m_pobTexture-&gt;getName() );<br />
	&nbsp;&nbsp;&nbsp; ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );</font></p>
<p><font face="Courier New">#define kQuadSize sizeof(m_sQuad.bl)<br />
	&nbsp;&nbsp;&nbsp; long offset = (long)&amp;m_sQuad;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // vertex<br />
	&nbsp;&nbsp;&nbsp; int diff = offsetof( ccV3F_C4B_T2F, vertices);<br />
	&nbsp;&nbsp;&nbsp; glVertexAttribPointer(kCCVertexAttrib_Position, 3,<br />
	&nbsp;&nbsp;&nbsp;&nbsp; GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // texCoods<br />
	&nbsp;&nbsp;&nbsp; diff = offsetof( ccV3F_C4B_T2F, texCoords);<br />
	&nbsp;&nbsp;&nbsp; glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2,<br />
	&nbsp; &nbsp; &nbsp; GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // color<br />
	&nbsp;&nbsp;&nbsp; diff = offsetof( ccV3F_C4B_T2F, colors);<br />
	&nbsp;&nbsp;&nbsp; glVertexAttribPointer(kCCVertexAttrib_Color, 4,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GL_UNSIGNED_BYTE, GL_TRUE,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kQuadSize, (void*)(offset + diff));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">这里的draw是一个典型的OpenGL绘制工序。CC_NODE_DRAW_SETUP()将之前的经过若干准备而得到的最终各类变换矩阵 整合并传给OpenGL：</font></p>
<p><font face="Courier New">/** @def CC_NODE_DRAW_SETUP<br />
	&nbsp;Helpful macro that setups the GL server state,<br />
	&nbsp;the correct GL program and sets the Model View<br />
	&nbsp;Projection matrix<br />
	&nbsp;@since v2.0<br />
	&nbsp;*/<br />
	#define CC_NODE_DRAW_SETUP() \<br />
	do { \<br />
	&nbsp;&nbsp;&nbsp; ccGLEnable(m_eGLServerState); \<br />
	&nbsp;&nbsp;&nbsp; CCAssert(getShaderProgram(), &quot;No shader program set for this node&quot;); \<br />
	&nbsp;&nbsp;&nbsp; { \<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getShaderProgram()-&gt;use(); \<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getShaderProgram()-&gt;<b>setUniformsForBuiltins</b>(); \<br />
	&nbsp;&nbsp;&nbsp; } \<br />
	} while(0)</font></p>
<p><font face="Courier New">void CCGLProgram::setUniformsForBuiltins()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; kmMat4 matrixP;<br />
	&nbsp;&nbsp;&nbsp; kmMat4 matrixMV;<br />
	&nbsp;&nbsp;&nbsp; kmMat4 matrixMVP;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmGLGetMatrix(KM_GL_PROJECTION, &amp;matrixP);<br />
	&nbsp;&nbsp;&nbsp; kmGLGetMatrix(KM_GL_MODELVIEW, &amp;matrixMV);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmMat4Multiply(&amp;matrixMVP, &amp;matrixP, &amp;matrixMV);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; setUniformLocationWithMatrix4fv(m_uUniforms[kCCUniformPMatrix],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; matrixP.mat, 1);<br />
	&nbsp;&nbsp;&nbsp; setUniformLocationWithMatrix4fv(m_uUniforms[kCCUniformMVMatrix],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; matrixMV.mat, 1);<br />
	&nbsp;&nbsp;&nbsp; setUniformLocationWithMatrix4fv(m_uUniforms[kCCUniformMVPMatrix],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; matrixMVP.mat, 1);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">经过计算顶点、绑定纹理等步骤后，最终由glDrawArrays完成Node绘制。</font></p>
<p><font face="Courier New"><b>四、m_fScaleX和m_fScaleY都是1.0，背景精灵为何被放大？</b></font></p>
<p><font face="Courier New">根据上面的分析，我们了解到&ldquo;子节点将跟随父节点的缩放而缩放&rdquo;。据此，我们来分析一下前面提到的屏幕适配例子中的第三种情况，即屏幕大小为 960&#215;640，按照Cocos2d-x屏幕适配指南Wiki中的做法，调用 pEGLView-&gt;setDesignResolutionSize(480, 320)。在该情况中，我们得到的结果是480&#215;320大小的背景图片充满了大小为960&#215;640的屏幕窗口，这给我们的直观印象就是背景图片被放大了一 倍。下面我们就尝试用上面的分析来解释一下这个现象。</font></p>
<p><font face="Courier New">在这个例子中，渲染树结构如下：<br />
	&nbsp;&nbsp; CCScene<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; CCLayer<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; CCSprite &#8211; 背景图精灵</font></p>
<p><font face="Courier New">按照之前的理论，背景图精灵自身或父类应该有缩放的设置，比如m_fScaleX = 2.0之类的设置，于是我在代码中输出了Scene、Layer以及Sprite的m_fScaleX和m_fScaleY值。但出乎预料的是，这些 Node子类的两个轴向缩放值都保持了默认值，即1.0f。在代码里翻了半天，也的确没有找到改写Scene、Layer或Sprite Scale的地方。又一想：代码中调用了setDesignResolutionSize，这样CCEGLView的m_fScaleX = m_fScaleY = 2.0f，难道是CCEGLView的m_fScale传递给了CCScene等Node子类，但事实总是残酷的，代表这一联系的代码也始终未被我所找 到，看来继续纠结m_fScale的值设置是无法搞清楚真正原因，应该换换思路了。这里背景图的放大不应该是Node scale值设置的问题，也就是说关键环节不应该在绘制流程，而是在之前的OpenGL变换矩阵参数设置，看来不再深入学习点OpenGL知识，这个问题 就很难搞定了，于是开始翻看《<a href="http://book.douban.com/subject/4311129/">OpenGL编程指南</a>7th》（号称OpenGL红宝书）和《<a href="http://book.douban.com/subject/10774590/">OpenGL超级宝典</a>》（号称OpenGL蓝宝 书）。虽然我的阅读是粗粒度的，但还是收获到了一些答案。</font></p>
<p><font face="Courier New"><b>五、OpenGL基础</b></font></p>
<p><font face="Courier New">OpenGL是帮助我们将三维世界的物体转换到二维屏幕上的一组接口。在新技术尚未出现之前，我们的屏幕永远是二维的，即便是现在的3D电影 也是双眼视角二维图像叠加的结果。我们知道&ldquo;</font><font face="Courier New">将大象装进冰箱总共分三 步&rdquo;，将一个三维模型转换到二维屏幕上，OpenGL也规定了相对流水线般的步骤。</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads/opengl-display-process.jpg" style="width: 480px; height: 111px;" /></font></p>
<p><font face="Courier New">OpenGL三维图形的显示流程</font></p>
<p><font face="Courier New">三维图形显示流程中，涉及到OpenGL的一个重要操作，那就是&ldquo;变换(Transformation)&rdquo;，主要的变换包括模型视图变换 （model-view transformation）、投影变换(projection transformation)以及视口变换(ViewPort transformation)。</font><font face="Courier New">我们经常用相机模拟来对比OpenGL解决这一问题的过程以及相关概念。</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads/opengl-transforms.jpg" style="width: 480px; height: 765px;" /></font></p>
<p><font face="Courier New">回顾一下我们自己用相机拍照的步骤吧。</font></p>
<p><font face="Courier New">第零步，选景。景就是所谓的三维模型或三维物体，或简称<b>模型</b>(Model)，就是我们要显示到屏幕上的物体；<br />
	第一步，确定相机位置。让相机以一定的距离、高度、角度对准模型。在这里，相机的位置变换，对应OpenGL的&ldquo;视图变换或叫视点变换 (View Transformation)&rdquo;。在这一步里（对应上面图中的第二步），我们还可以调整三维物体的相对位置、角度与相机的距离，这就是模型变换 （Modeling Transformation），两种变换达成的效果是相同的，因此总称模型视图变换(Model-View Transformation)。<br />
	第二步，选镜头，并调焦。确定图像投影在胶片上的范围以及景深等。这一步叫投影变换（Projection Transformation）。<br />
	第三步，冲洗照片。拍摄好的图像放在底片上，但我们需要选择冲洗后最终是放在6寸相纸还是20寸相纸上，显然在不同大小相纸上，图像的显示效 果不同（比如大小）。这个过程叫视口变换（Viewport Transformation）。</font></p>
<p><font face="Courier New"><tt>三维空间的物体都是用三维坐标描述的，谈到坐标就离不开坐标系，OpenGL中的坐标系就有多种，我们最常用的就是世界坐标系。</tt></font></p>
<p><font face="Courier New"><tt>世界坐标系是以屏幕中心为原点(0, 0, 0)，你面对屏幕，你的右边是x正轴，上面是y正轴，屏幕指向你的为z正轴。</tt></font><font face="Courier New"><tt>无论如何变换，世界坐标系都不动。</tt></font>我们在Cocos2d-x中设置 初始参数时，参数的单位多为世界坐标系中的单位。</p>
<p>视点变换时会涉及到视点坐标系，但这个变换由opengl接口来负责，我们不用过多关心。</p>
<p>绘图坐标系（局部坐标系），当前绘图坐标系是绘制物体时的坐标系。程序刚初始化时，世界坐标系和当前绘图坐标系是重合的，当用 glTranslatef()等变换函数做移动和旋转时，都是改变的当前绘图坐标系，改变的位置都是当前绘图坐标系相对自己的x,y,z轴所做的 改变，改变以后，再绘图时，都是在当前绘图坐标系进行绘图，所有的函数参数也都是相对当前绘图坐标系来讲的。</p>
<p>屏幕坐标系，即终端屏幕上的坐标系，与世界坐标系有不同，它以屏幕左上角的点为原点，向右是x正轴，向下是y正轴，屏幕指向你的为z正轴。</p>
<p>注意视口(Viewport)的设置是以实际屏幕坐标定义了窗口中的区域，长度宽度都是以<b>实际像素</b>为单位。当然引擎在精灵绘图时用 的是绘图坐标系，我们理解原点在左下角即可。</p>
<p><b>六、Cocos2d-x各种变换矩阵的初始参数设置</b></p>
<p>前面说过，Cocos2d-x在CCDirector::setProjection中完成了对变换矩阵的初始参数设置，我们逐一来看看这些设置对模型映射后的二维图像有何影响，这也是理解篇头几个问题的关键环节。</p>
<p>&nbsp; <b>* 投影变换</b><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 前面提到过，投影变换相当于调节相机镜头。OpenGL中提供了两种投影方式，一种是正射投影，另一种是透视投影。Cocos2d-x使用的是透视投影 （Perspective Projection)。透视投影是实际人们观察事物的真实反馈，即离视点近的物体大，离视点远的物体小，远到极点即为消失，成为灭点。Cocos2d- x使用的是kmMat4PerspectiveProjection，对应OpenGL中的gluPerspective，该方法创建一个对称透视视景体 (View Volumn)，见下图：</p>
<p><img alt="" src="/wp-content/uploads/projection.jpg" style="width: 480px; height: 208px;" /></p>
<p>gluPerspective的函数原型如下：<font face="Courier New">void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);</font></p>
<p>&nbsp;&nbsp;&nbsp; 参数fovy定义视野在X-Z平面的角度，范围是[0.0, 180.0]，也就是上图中的&ldquo;视角&rdquo;；<br />
	&nbsp;&nbsp;&nbsp; 参数aspect是投影平面宽度与高度的比率；<br />
	&nbsp;&nbsp;&nbsp; 参数zNear和Far分别是近远裁剪面沿Z负轴到视点的距离，它们总为正值。<br />
	&nbsp;&nbsp;<br />
	Cocos2d-x中是这么设置投影变换矩阵的：</p>
<p><font face="Courier New">&nbsp; float zeye = this-&gt;getZEye();<br />
	&nbsp; kmMat4PerspectiveProjection( &amp;matrixPerspective, 60, (GLfloat)size.width/size.height, 0.1f, zeye*2);</font></p>
<p><font face="Courier New">&nbsp; float CCDirector::getZEye(void)<br />
	&nbsp; {<br />
	&nbsp;&nbsp;&nbsp; return (m_obWinSizeInPoints.height / 1.1566f);<br />
	&nbsp; }</font></p>
<p><font face="Courier New">从参数上来看，<br />
	&nbsp;&nbsp;&nbsp; 视角 = 60度<br />
	&nbsp;&nbsp;&nbsp; 宽高比 = 设计分辨率的宽高比，<br />
	&nbsp;&nbsp;&nbsp; 近平面 = 距离视点0.1f，几乎与视点重合<br />
	&nbsp;&nbsp;&nbsp; 远平面 = 距离视点zeye * 2距离。<br />
	&nbsp;&nbsp;&nbsp; 视点位置 = 设计分辨率.height / 1.1566f</font></p>
<p><font face="Courier New">投影是用来对模型进行截取的，只有在投影变换所建立的平头截体（Frustum，投影的近、远两个截面以及其他四个面构成的立体体）内的模型部分才会被最终映射和显示。我们用下面的图来直观了解一下各个参数在三维空间的概念吧。</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads/projection-1.jpg" style="width: 480px; height: 366px;" /></font></p>
<p><font face="Courier New">显然引擎如此设置投影矩阵的参数是有考虑的：<br />
	首先就是投影平头截体的宽高比 = 设计分辨率的宽高比，这样设置使得一切符合设计分辨率宽高比的模型都可以被理想截取。<br />
	其次，视角60度，zEye的在Z轴正方向距离世界原点的距离 = (m_obWinSizeInPoints.height / 1.1566f),这里的1.1566f是怎么来的呢？我们沿着X轴负方向向zy平面投影，得到下图：</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads/projection-2.jpg" style="width: 480px; height: 388px;" /></font></p>
<p><font face="Courier New">看这个图，让我想起了初中几何，通过60度的视角，我们可以推断由eye、XZ截断上平面与Y轴的交点、XZ截断下平面与Y轴的交点组成一个等边三角形， 现在我们已知在Zy平面投影中视点与原点的距离为m_obWinSizeInPoints.height / 1.1566f, 我们还知道夹角是60度，我们求一下投影在(z=0，XY平面)的截面高度h。</font></p>
<p><font face="Courier New">cos30 = (</font><font face="Courier New">m_obWinSizeInPoints.height / 1.1566f)/ h<br />
	h = (m_obWinSizeInPoints.height / 1.1566f)/cos30 = m_obWinSizeInPoints.height;</font></p>
<p><font face="Courier New">我们计算出来的结果是 h = m_obWinSizeInPoints.height = 设计分辨率中的高度分量。这意味这什么呢？Cocos2d-x是2D游戏渲染引擎，针对该引擎的模型的z坐标都是0，因此模型实际上就在xy平面内，也就 是说eye与原点的距离恰好就是eye与模型的距离，而模型可显示区域的最大高度也就是h，即m_obWinSizeInPoints.height。这 个结论会在后续问题分析时发挥作用。</font></p>
<p><font face="Courier New">注意虽然这里知道eye在Z轴正方向距离世界原点的距离，但eye的(x, y)坐标在投影设置后依旧无法确认，我们需要在设置模型视图变换时得到eye的(x, y)坐标。</font></p>
<p><font face="Courier New">&nbsp; <b>* 视图变换</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmGLMatrixMode(KM_GL_MODELVIEW);<br />
	&nbsp;&nbsp;&nbsp; kmGLLoadIdentity();<br />
	&nbsp;&nbsp;&nbsp; kmVec3 eye, center, up;<br />
	&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;eye, size.width/2, size.height/2, zeye );<br />
	&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;center, size.width/2, size.height/2, 0.0f );<br />
	&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;up, 0.0f, 1.0f, 0.0f);<br />
	&nbsp;&nbsp;&nbsp; kmMat4LookAt(&amp;matrixLookup, &amp;eye, &amp;center, &amp;up);<br />
	&nbsp;&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixLookup);</font></p>
<p><font face="Courier New">OpenGL原生的视图变换参数设置方法是gluLookAt，在kazmath中对应的方法为kmMat4LookAt。gluLookAt的函数原型是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; void gluLookAt(GLdouble eyex, GLdouble exey, GLdouble eyez,<br />
	&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; GLdouble centrex, GLdouble centrey, GLdouble centrez,<br />
	&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; GLdouble upx, GLdouble upy, GLdouble upz);</font></p>
<p><font face="Courier New">eye的坐标(eyex, eyey, eyez), Cocos2d-x中是这么设置的kmVec3Fill( &amp;eye, size.width/2, size.height/2, zeye )。可以看出eye在xy平面的投影恰好是以屏幕分辨率构成的矩形的中心。</font></p>
<p><font face="Courier New">centre坐标，表示的是视线方向，该方向矢量是由eye坐标、centre坐标共同构成的，由eye指向center。Cocos2d-x的设置 kmVec3Fill( &amp;center, size.width/2, size.height/2, 0.0f )。x, y坐标与eye的相同，因此视线平行于Z轴。</font></p>
<p><font face="Courier New">最后的up参数可以理解为头顶方向，这里设置为Y轴方向。</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads//view-transform.jpg" style="width: 480px; height: 328px;" /></font></p>
<p><font face="Courier New">可以看出，eye就在投影区的中心，由于投影区的高度为size.height(投影变换时分析得到的)，这样根据投影矩阵设置的宽高比，得出该投影区的宽度也恰为size.width。</font></p>
<p><font face="Courier New"><b>七、再分析</b></font></p>
<p><font face="Courier New">有了以上关于Cocos2d-x引擎的了解，我们再回过头来用OpenGL的变换原理对篇头的三种情况做分析。</font></p>
<p><font face="Courier New">&nbsp;1) 屏幕大小480&#215;320，未做任何屏幕适配工作，不调用pEGLView-&gt;setDesignResolutionSize。结果：背景图充满窗口。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 在这种情况下，各个OpenGL变换矩阵参数值如下：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; eye视点坐标(240, 160, 320/1.1566f);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 投影变换矩阵在xy平面的截面区域恰好是480&#215;320；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 背景图锚点位置(240, 160, 0)；</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 在这种情况下，截面区域恰与背景图重合，显示在屏幕上后，背景图恰充满窗口，见下图：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads/case1.jpg" style="width: 480px; height: 328px;" /><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;2) 屏幕大小960&#215;640，未做任何屏幕适配工作，不调用pEGLView-&gt;setDesignResolutionSize。结果：背景图未充满窗口，四周有较大黑边。<br />
	&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 在这种情况下，各个OpenGL变换矩阵参数值如下：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; eye视点坐标(480, 320, 480/1.1566f);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投影变换矩阵在xy平面的截面区域是960&#215;640；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 而背景图锚点位置(480, 320, 0)；</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 因此背景图(480&#215;320)未能完整充满截面区域(960&#215;640)，背景图周围将有较大黑边，见下图：<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;<img alt="" src="/wp-content/uploads/case2.jpg" style="width: 480px; height: 328px;" />&nbsp;</font></p>
<p><font face="Courier New">&nbsp;3) 屏幕大小同为960&#215;640，按照上面Cocos2d-x屏幕适配指南Wiki中的做法，调用pEGLView-&gt;setDesignResolutionSize(480, 320)。结果：背景图放大为原来2倍，充满屏幕窗口。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 在这种情况下，各个OpenGL变换矩阵参数值如下：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; eye视点坐标(240, 160, 320/1.1566f);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投影变换矩阵在xy平面的截面区域是480&#215;320；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 而背景图锚点位置(240, 160, 0)；</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 在这种情况下，截面区域恰与背景图重合。但这里需要注意的是现在屏幕是960&#215;640，而截面区域仅仅是480&#215;320，为何映射后，背景图充满屏幕了呢？这里就不能不提到视口的作用了。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 前面说过视口相当于相片，现在我们拍摄出的图片是480&#215;320的，但我们选择的底片Viewport却是960&#215;640的，怎么办，在视口转换 时，OpenGL自动将480&#215;320的图片映射到960&#215;640的底片上，相当于对图像进行的放大。而960&#215;640的视口恰好与屏幕窗口大小一致且坐 标重叠，于是我们就在屏幕上看到了一个铺满屏幕的背景图，见下图：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads/case3.jpg" style="width: 480px; height: 500px;" /></font></p>
<p><font face="Courier New">&nbsp;4) 我们再来说两个有关视口的例子</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 以第三种情况为基础，我们修改一下引擎代码，看看视口的作用。<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 我们手工将CCDirector::setViewport()中的：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; m_pobOpenGLView-&gt;setViewPortInPoints(0, 0, m_obWinSizeInPoints.width, m_obWinSizeInPoints.height);<br />
	&nbsp;&nbsp;&nbsp; 改为：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; m_pobOpenGLView-&gt;setViewPortInPoints(0, 0, m_obWinSizeInPoints.width/2, m_obWinSizeInPoints.height/2);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 这样修改后，Viewport从point(0,0), rect (960&#215;640)变成了point(0,0), rect (480&#215;320)。也就是说用照相机拍出的景物大小是480&#215;320，底片也是480&#215;320，但屏幕是960&#215;640，我们可以将屏幕理解为相框，把 一张480&#215;320的照片，放到960&#215;640大小的相框里，相片只能占据相框的四分之一。这个例子的最终屏幕显示结果见下图：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads/case4.jpg" style="width: 480px; height: 500px;" /></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 前面的例子中背景图片size均小于屏幕大小，我们再来举一个资源图片大于屏幕大小的例子，看看经过一系列变换会得到什么样的结果。<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 首先将CCDirector::setViewport()中的代码恢复原先状态。然后我们准备一张1024&#215;768(&gt;屏幕的960&#215;640)的 背景图片&quot;HelloWorld-1024&#215;768.jpg&quot;，修改HelloWorldScene.cpp，将：<br />
	&nbsp;&nbsp;&nbsp; CCSprite* pSprite = CCSprite::create(&quot;HelloWorld.png&quot;);<br />
	&nbsp;&nbsp;&nbsp; 修改为：<br />
	&nbsp;&nbsp;&nbsp; CCSprite* pSprite = CCSprite::create(&quot;<b>HelloWorld-1024&#215;768.png</b>&quot;);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 注释掉AppDelegate.cpp中的pEGLView-&gt;setDesignResolutionSize调用，这样更直观。</font></p>
<p><font face="Courier New">&nbsp; &nbsp; 这样修改后，各参数如下：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; eye视点坐标(480, 320, 640/1.1566f);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投影变换矩阵在xy平面的截面区域是960&#215;640；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 而背景图锚点位置(480, 320, 0)；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Viewport point(0,0), rect (960&#215;640)<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 由于背景资源图片太大(1024&#215;768)，大于我们的投影截面区域960&#215;640，因此模型真正能显示的部分仅仅是投影截面区域中的那960&#215;640范围内的图片。于是显示结果如下：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads//case-5-transform.jpg" style="width: 480px; height: 334px;" /></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 矩阵变换过程如下：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads/case5.jpg" style="width: 480px; height: 325px;" /></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 投影截面区域与视口区域重叠，这里就不再赘述了。</font></p>
<p><font face="Courier New"><b>八、CCDirector::m_fContentScaleFactor</b></font></p>
<p><font face="Courier New">决定图像在屏幕上的最终显示结果的因素还有一个，那就是CCDirector::m_fContentScaleFactor。在最初的HelloCpp例子中，我们能看到这样的代码：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (frameSize.height &gt; mediumResource.size.height)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; searchPath.push_back(largeResource.directory);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDirector-&gt;setContentScaleFactor(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MIN(largeResource.size.height/designResolutionSize.height,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; largeResource.size.width/designResolutionSize.width));<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 可以看出这个contentScaleFactor存储的是资源分辨率与设计分辨率的比值。我们还是用例子来看看该元素对显示的影响。我们在第一种情况的基础上验证。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 第一种情况：屏幕480&#215;320，未调用setDesignResolutionSize，资源大小480&#215;320。结果：图片充满屏幕。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 现在我们增加并使用一个新资源：HelloWorld-960&#215;640.png，这个图片大小960&#215;640，是屏幕大小的二倍，根据上面的分析，我们很容易猜测到最终结果是：只有图片中央区域(480&#215;320)可以显示出来，其余部分被投影矩阵截掉。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 现在我们使用setContentScaleFactor，在AppDelegate.cpp中做如下调用：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; pDirector-&gt;setContentScaleFactor(MIN(960/480, 640/320));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 这样我们得到的m_fContentScaleFactor = 2。而我们编译运行后得到的结果是：图片铺满整个屏幕。为什么会这样呢？</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 我们在代码中搜索contentScaleFactor，我们找到一些宏和调用：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; </font><br />
	<font face="Courier New">#define CC_CONTENT_SCALE_FACTOR() CCDirector::sharedDirector()-&gt;getContentScaleFactor()</font></p>
<p><font face="Courier New">CCSize CCTexture2D::getContentSize()</font><br />
	<font face="Courier New">{</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; CCSize ret;<br />
	&nbsp;&nbsp;&nbsp; ret.width = <b>m_tContentSize.width / CC_CONTENT_SCALE_FACTOR();</b><br />
	&nbsp;&nbsp;&nbsp; ret.height = <b>m_tContentSize.height / CC_CONTENT_SCALE_FACTOR();</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return ret;<br />
	}</font></p>
<p><font face="Courier New">#define CC_RECT_PIXELS_TO_POINTS(__rect_in_pixels__)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \</font><br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; CCRectMake( (__rect_in_pixels__).origin.x / CC_CONTENT_SCALE_FACTOR(), (__rect_in_pixels__).origin.y / CC_CONTENT_SCALE_FACTOR(),&nbsp;&nbsp;&nbsp; \<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (__rect_in_pixels__).size.width / CC_CONTENT_SCALE_FACTOR(), (__rect_in_pixels__).size.height / CC_CONTENT_SCALE_FACTOR() )</font><br />
	&#8230; &#8230;</p>
<p><font face="Courier New">bool CCSprite::initWithTexture(CCTexture2D *pTexture)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCAssert(pTexture != NULL, &quot;Invalid texture for sprite&quot;);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; CCRect rect = CCRectZero;<br />
	&nbsp;&nbsp;&nbsp; rect.size = pTexture-&gt;getContentSize();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return initWithTexture(pTexture, rect);<br />
	}</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 这些代码都在告诉我们，如果m_fContentScaleFactor = 2，那代码会对Sprite的纹理进行缩放，让上面得到的数据是经过contentScaleFactor变换的，我们可以认为我们所用的实际资源大小是 原资源的1/m_fContentScaleFactor即可。</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/05/13/sprite-draw-principles-of-cocos2dx-screen-adaptation/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x 3.0rc0集成Google AdMob SDK</title>
		<link>https://tonybai.com/2014/05/01/integrate-cocos2dx3rc0-with-admob/</link>
		<comments>https://tonybai.com/2014/05/01/integrate-cocos2dx3rc0-with-admob/#comments</comments>
		<pubDate>Wed, 30 Apr 2014 18:23:16 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AdMob]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[android-native-app-glue]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CGL]]></category>
		<category><![CDATA[Cocos2d]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Cocos2d-x-3.0]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[EGL]]></category>
		<category><![CDATA[Game-Engine]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Jni]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[MobileGame]]></category>
		<category><![CDATA[NativeActivity]]></category>
		<category><![CDATA[NDK]]></category>
		<category><![CDATA[opengl]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Pthread]]></category>
		<category><![CDATA[SDK]]></category>
		<category><![CDATA[内存计数]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[广告平台]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[性能优化]]></category>
		<category><![CDATA[手游]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[游戏引擎]]></category>
		<category><![CDATA[源码分析]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[触控科技]]></category>
		<category><![CDATA[跨平台]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1531</guid>
		<description><![CDATA[话说Cocos2d-x 3.0上一周迫不及待地发布了正式版，本是一件值得庆幸的事情。但由于不可解决的技术问题，引擎无奈将Android平台的NativeActivity 实现重新回退到了Cocos2d-x 2.2.x版本的实现方案。由于之前已经将 GameDemo移植到了Cocos2d-x 3.0rc0版，直观感受到了NativeActivity方案带来的游戏操作体验上的提升（触屏事件的响应），因此心里总是&#8220;挂念&#8221;引擎的 NativeActivity方案。按照Cocos2d-x官方人士的说法，只要NDK的问题修复，NativeActivity还是未来引擎在 Android平台上的第一选择。我的理解，将来Cocos2d-x的某个版本中还会恢复NativeActivity的实现方案。 上次只是将GameDemo的核心逻辑移植到了Cocos2d-x 3.0rc0上，但一些外部SDK集成尚未做完，这两天闲遐时开始着手研究如何做，而首先要移植的就是Google AdMob SDK。关于Cocos2d-x 3.0rc0集成Google AdMob SDK，网上已经有了技术方案原型，最初应该是一个外国开发者提出的方案，后来被CocoaChina cocos2d-x社区版主翻译成了中文教程，这里基本上也是参考的这个方案，只是做了局部细化和进一步说明，希望能帮助大家进一步解惑。 一、功能说明 GameDemo游戏分为三个场景：WelcomeScene、GameScene以及EndScene。集成AdMob SDK的功能要求如下： &#160;&#160;&#160; &#8211; 当游戏进入到WelcomeScene时，AdMob SDK完成初始化，发出Ad Request，当收到Ad的时候才会在屏幕上方显示带有Ad的窗口； &#160;&#160;&#160; &#8211; 当点击Start进入到GameScene时，隐藏Ad窗口，以不干扰玩家的游戏操作为优先； &#160;&#160;&#160; &#8211; 当游戏Over进入到EndScene的时候，恢复显示Ad窗口； &#160;&#160;&#160; &#8211; 当玩家点击&#8220;Retry&#8221;回到GameScene时，隐藏Ad窗口。 二、方案原理 这个方案就是通过android.widget.PopupWindow实现广告窗口悬浮在当前窗口之上。按照Android官方Doc中的说 明，PopupWindow用于实现一个弹出框，它可以使用任意布局的View作为其内容，这个弹出框是悬浮在当前activity之上的。 至于PopupWindow为何能显示在当前Activity之上，可以查看PopupWindow的源码，大致思路就是PopupWindow通过 new时传入的context(当前Activity)获得了当前WindowManager，并将AdView作为子View添加到该窗口中。这里简要列出一些关键源码，大家可粗略理解一下： &#160;&#160;&#160; public PopupWindow(Context context, AttributeSet attrs, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; int defStyleAttr, int defStyleRes) { &#160;&#160;&#160;&#160;&#160;&#160;&#160; mContext = context; &#160;&#160;&#160;&#160;&#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>话说<a href="http://www.cocos2d-x.org">Cocos2d-x</a> 3.0上一周迫不及待地发布了<a href="http://www.cocos2d-x.org/news/215">正式版</a>，本是一件值得庆幸的事情。但由于不可解决的<a href="http://www.cocos2d-x.org/forums/6/topics/48163">技术问题</a>，引擎无奈将Android平台的NativeActivity 实现重新<a href="http://tonybai.com/2014/04/23/changes-in-cocos2dx-3-rc2-for-android/">回退</a>到了Cocos2d-x 2.2.x版本的实现方案。由于之前已经将 GameDemo移植到了<a href="http://tonybai.com/2014/04/22/hello-cocos2dx-3-rc0/">Cocos2d-x 3.0rc0版</a>，直观感受到了NativeActivity方案带来的游戏操作体验上的提升（触屏事件的响应），因此心里总是&ldquo;挂念&rdquo;引擎的 NativeActivity方案。按照Cocos2d-x官方人士的说法，只要NDK的问题修复，NativeActivity还是未来引擎在 Android平台上的第一选择。我的理解，将来Cocos2d-x的某个版本中还会恢复NativeActivity的实现方案。</p>
<p>	上次只是将GameDemo的核心逻辑移植到了Cocos2d-x 3.0rc0上，但一些外部SDK集成尚未做完，这两天闲遐时开始着手研究如何做，而首先要移植的就是<a href="https://developers.google.com/mobile-ads-sdk/">Google AdMob SDK</a>。关于Cocos2d-x 3.0rc0集成Google AdMob SDK，网上已经有了技术方案原型，最初应该是一个<a href="http://www.dynadream.com/ddweb/index.php/Special_Blog">外国开发者提出的方案</a>，后来被<a href="http://www.cocoachina.com">CocoaChina</a> cocos2d-x社区版主翻译成了<a href="http://www.cocoachina.com/bbs/read.php?tid=194999">中文教程</a>，这里基本上也是参考的这个方案，只是做了局部细化和进一步说明，希望能帮助大家进一步解惑。</p>
<p>	<b>一、功能说明</b></p>
<p>	GameDemo游戏分为三个场景：WelcomeScene、GameScene以及EndScene。集成AdMob SDK的功能要求如下：</p>
<p>	&nbsp;&nbsp;&nbsp; &#8211; 当游戏进入到WelcomeScene时，AdMob SDK完成初始化，发出Ad Request，当收到Ad的时候才会在屏幕上方显示带有Ad的窗口；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 当点击<b>Start</b>进入到GameScene时，隐藏Ad窗口，以不干扰玩家的游戏操作为优先；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 当游戏Over进入到EndScene的时候，恢复显示Ad窗口；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 当玩家点击&ldquo;<b>Retry</b>&rdquo;回到GameScene时，隐藏Ad窗口。</p>
<p>	<b>二、方案原理</b></p>
<p>	这个方案就是通过android.widget.PopupWindow实现广告窗口悬浮在当前窗口之上。按照Android<a href="http://developer.android.com/reference/android/widget/PopupWindow.html">官方Doc</a>中的说 明，PopupWindow用于实现一个弹出框，它可以使用任意布局的View作为其内容，这个弹出框是悬浮在当前activity之上的。</p>
<p>	至于PopupWindow为何能显示在当前Activity之上，可以查看PopupWindow的源码，大致思路就是PopupWindow通过 new时传入的context(当前Activity)获得了当前WindowManager，并将AdView作为子View添加到该窗口中。这里简要列出一些关键源码，大家可粗略理解一下：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">public PopupWindow(Context context, AttributeSet attrs,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int defStyleAttr, int defStyleRes) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mContext = context;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>mWindowManager</b> = (WindowManager)context<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .getSystemService(Context.WINDOW_SERVICE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;. &#8230;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public void setContentView(View contentView) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (isShowing()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>mContentView</b> = contentView;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mContext == null &amp;&amp; mContentView != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mContext = mContentView.getContext();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mWindowManager == null &amp;&amp; mContentView != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mWindowManager = (WindowManager) mContext<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .getSystemService(Context.WINDOW_SERVICE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public void showAtLocation(View parent, int gravity, int x, int y) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; showAtLocation(parent.getWindowToken(), gravity, x, y);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public void showAtLocation(IBinder token, int gravity, int x, int y) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (isShowing() || mContentView == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unregisterForScrollChanged();</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mIsShowing = true;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mIsDropdown = false;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WindowManager.LayoutParams p = createPopupLayout(token);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.windowAnimations = computeAnimationResource();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>preparePopup</b>(p);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (gravity == Gravity.NO_GRAVITY) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gravity = Gravity.TOP | Gravity.START;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.gravity = gravity;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.x = x;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.y = y;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mHeightMode &lt; 0) p.height = mLastHeight = mHeightMode;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mWidthMode &lt; 0) p.width = mLastWidth = mWidthMode;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>invokePopup</b>(p);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; private void invokePopup(WindowManager.LayoutParams p) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mContext != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.packageName = mContext.getPackageName();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mPopupView.setFitsSystemWindows(mLayoutInsetDecor);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setLayoutDirectionFromAnchor();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>mWindowManager.addView(mPopupView, p);</b><br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	UI线程负责更新窗口，因此popupWindow的创建与操作都应该通过runOnUiThread传递给UI线程执行。</p>
<p>	游戏逻辑的C++层代码通过Jni控制PopupWindow的Show和dismiss。别忘了C++层的代码是在渲染线程执行的哦，这也是为何要用handler和runOnUIThread的原因。</p>
<p>	在Cocos2d-x 2.2.2版本集成AdMob时，AdMob只是在收到ad后才显示出广告Banner，但是在cocos2d-x 3.0rc0中，当广告加载未成功时，android.widget.PopupWindow会显示一个小黑框，特难看，因此我们需要自己来控制。</p>
<p>	<b>三、集成步骤</b></p>
<p>	【AdMob准备】</p>
<p>	&nbsp; 到Google AdMob注册一个帐号，如果你有Google帐号，那直接开通AdMob（需填写更加详细的信息）。<br />
	&nbsp; 下载Google AdMob SDK，之前一直用GoogleAdMobAdsSdk-6.4.1.jar，这里还以这个版本为例。但Google官方已经不推荐这个版本了，你可以下 载Google Play Services版本的Mobile Ads API，但代码与这里会稍有不同。将下载的jar包放到GameDemo/proj.android/libs中。<br />
	&nbsp;<br />
	【修改AndroidManifest.xml】</p>
<p>	&nbsp; 在&lt;Application&gt;标签里添加：</p>
<p>	<font face="Courier New">&nbsp; &lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.google.ads.AdActivity&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; android:configChanges=&quot;keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize&quot; /&gt;<br />
	&nbsp; &lt;meta-data<br />
	&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;ADMOB_PUBLISHER_ID&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; android:value=&quot;<i>YOUR_PUBLISHER_ID_VALUE</i>&quot; /&gt;</font></p>
<p>	&nbsp; 权限方面至少包含如下两个：<br />
	<font face="Courier New">&nbsp;&nbsp; &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;<br />
	&nbsp;&nbsp; &lt;uses-permission android:name=&quot;android.permission.ACCESS_NETWORK_STATE&quot;/&gt;</font></p>
<p>	<b>【Java层代码集成】</b></p>
<p>	我们只需要修改GameDemoActivity.java这个文件。</p>
<p>	<font face="Courier New">import android.app.NativeActivity;<br />
	import android.os.Bundle;<br />
	import android.util.Log;</p>
<p>	import android.os.Handler;<br />
	import android.os.Message;<br />
	import android.view.Gravity;<br />
	import android.view.View;<br />
	import android.view.Gravity;<br />
	import android.view.ViewGroup.LayoutParams;<br />
	import android.view.ViewGroup.MarginLayoutParams;<br />
	import android.view.WindowManager;<br />
	import android.widget.LinearLayout;</p>
<p>	import android.widget.PopupWindow;<br />
	import com.google.ads.AdRequest;<br />
	import com.google.ads.AdSize;<br />
	import com.google.ads.AdView;<br />
	import com.google.ads.AdListener;<br />
	import com.google.ads.Ad;</font></p>
<p>	<font face="Courier New">public class GameDemoActivity extends NativeActivity{<br />
	&nbsp;&nbsp;&nbsp; private static GameDemoActivity context;</p>
<p>	&nbsp;&nbsp;&nbsp; private AdView adView;<br />
	&nbsp;&nbsp;&nbsp; private PopupWindow popUpWindow;<br />
	&nbsp;&nbsp;&nbsp; private LinearLayout popupWindowLayout;<br />
	&nbsp;&nbsp;&nbsp; private LinearLayout mainActivityLayout;<br />
	&nbsp;&nbsp;&nbsp; private boolean hasAdReceived;</p>
<p>	&nbsp;&nbsp;&nbsp; private static Handler adHandler = new Handler() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void handleMessage(Message msg) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!context.hasAdReceived)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch (msg.what) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 0:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (View.VISIBLE ==<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.getVisibility()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.setVisibility(View.GONE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.popUpWindow.dismiss();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (View.VISIBLE !=<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.getVisibility()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.setVisibility(View.VISIBLE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.popUpWindow.showAtLocation(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.mainActivityLayout, Gravity.TOP, 0, 0);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; };</p>
<p>	&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; protected void onCreate(Bundle savedInstanceState) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onCreate(savedInstanceState);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context = this;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adView = new AdView(this, AdSize.BANNER, &quot;a1533d6de900e31&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adView.setAdListener(new AdmobListener());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //初始时，广告View隐藏起来<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adView.setVisibility(View.GONE);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static void setupAds(){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.initAdPopupWindow();<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public void initAdPopupWindow() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (adView != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.runOnUiThread(new Runnable() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void run() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MarginLayoutParams params = new MarginLayoutParams(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LayoutParams.WRAP_CONTENT,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LayoutParams.WRAP_CONTENT);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; params.setMargins(0, 0, 0, 0);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popupWindowLayout = new LinearLayout(context);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popupWindowLayout.setPadding(-5, -5, -5, -5);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popupWindowLayout.setOrientation(LinearLayout.VERTICAL);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popupWindowLayout.<b>addView</b>(adView, params);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow = new PopupWindow(context);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.setWidth(320);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.setHeight(50);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.setWindowLayoutMode(LayoutParams.WRAP_CONTENT,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LayoutParams.WRAP_CONTENT);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.setClippingEnabled(false);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.<b>setContentView</b>(popupWindowLayout);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mainActivityLayout = new LinearLayout(context);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.<b>setContentView</b>(mainActivityLayout, params);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.hasAdReceived = false;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AdRequest adRequest = new AdRequest();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //测试Admob时使用测试Device，发布版需要去掉这行代码<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adRequest.addTestDevice(&quot;CCE4220B2509A406B515E7C9A205AEE1&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.loadAd(adRequest);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.update();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; //GoogleAdMobAdsSdk-6.4.1版本中的AdListener是interface，因此<br />
	&nbsp;&nbsp;&nbsp; //我们需要override所有接口，但只有onReceiveAd是我们关心的。<br />
	&nbsp;&nbsp;&nbsp; private class AdmobListener implements AdListener {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onReceiveAd(Ad ad) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //只有第一次成功接收Ad后，我们后续才显示广告窗口，否则<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //popupWindow会显示为一个小黑框，特难看。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;, &quot;onReceiveAd&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!hasAdReceived){<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; hasAdReceived = true;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onFailedToReceiveAd(Ad ad,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AdRequest.ErrorCode error) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;failed to receive ad (&quot; + error+ &quot;)&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onPresentScreen(Ad ad) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;, &quot;onPresentScreen&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onDismissScreen(Ad ad) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;, &quot;onDismisScreen&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onLeaveApplication(Ad ad) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;, &quot;onLeaveApp&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static void setAdVisible(boolean b) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (b) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 1;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 0;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	【C++层代码集成】</p>
<p>	在AppDelegate.cpp中添加setupAds方法：</p>
<p>	<font face="Courier New">void AppDelegate::setupAds()<br />
	{<br />
	#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)<br />
	&nbsp;&nbsp;&nbsp; JniMethodInfo t;<br />
	&nbsp;&nbsp;&nbsp; if (JniHelper::getStaticMethodInfo(t, &quot;com/tonybai/game/GameDemoActivity&quot;, &quot;setupAds&quot;, &quot;()V&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;CallStaticVoidMethod(t.classID, t.methodID);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (t.env-&gt;ExceptionOccurred()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionDescribe();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionClear();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;DeleteLocalRef(t.classID);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	#endif<br />
	}</font></p>
<p>	在WelcomeScene的init方法中调用setupAds：</p>
<p>	<font face="Courier New">bool WelcomeScene::init()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; bool bRet = false;<br />
	&nbsp;&nbsp;&nbsp; do {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; AppDelegate *app = (AppDelegate*)Application::getInstance();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; app-&gt;<b>setupAds</b>();</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bRet=true;<br />
	&nbsp;&nbsp;&nbsp; } while(0);</p>
<p>	&nbsp;&nbsp;&nbsp; return bRet;<br />
	}</font></p>
<p>	在点击Start按钮时，隐藏Ad：</p>
<p>	<font face="Courier New">void WelcomeScene::menuStartCallback(Ref* pSender)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; AppDelegate *app = (AppDelegate*)Application::getInstance();</p>
<p>	&nbsp;&nbsp;&nbsp; <b>app-&gt;setAdVisible(false);</b><br />
	&nbsp;&nbsp;&nbsp; GameScene *gameScene = GameScene::create();<br />
	&nbsp;&nbsp;&nbsp; Director::getInstance()-&gt;replaceScene(gameScene);<br />
	}</font></p>
<p>	集成步骤到此就结束了，编译ok就可以部署到模拟器上运行测试一番了。</p>
<p>	<b>四、使用Cocos2d-x 3.0rc0引擎遇到的两个问题</b></p>
<p>	【问题1】用cocos2d-x 3.0rc0的cocos编译高版本引擎生成的工程遇到的问题</p>
<p>	&nbsp;cocos2d-x 3.0rc0生成的proj.android/build-cfg.json与高版本(rc1～正式版)&nbsp; cocos生成的工程的build-cfg.json稍有不同，用cocos2d-x 3.0rc0版cocos编译高版本cocos生成的project，会提示build_android组件无法找到&ldquo;copy_to_assets&rdquo;。 因此需要手动修改proj.android/build-cfg.json，将其中的：</p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; &quot;copy_resources&quot;: [<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;from&quot;: &quot;../Resources&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;to&quot;: &quot;&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; ],</font></p>
<p>	改为：</p>
<p>	<font face="Courier New">&nbsp;&nbsp; &quot;copy_to_assets&quot; :[<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;../Resources/&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ],</font></p>
<p>	【问题2】 W/dalvikvm(1194): dvmFindClassByName rejecting &#39;org/cocos2dx/lib/Cocos2dxHelper&#39;</p>
<p>	使用cocos2d-x 3.0rc0编译的项目，总是出现如下问题，但似乎这个错误还不影响程序的运行：</p>
<p>	<font face="Courier New">04-29 09:44:34.968: D/JniHelper(1194): JniHelper::setJavaVM(0xb8835730), pthread_self() = B897B518<br />
	04-29 09:44:34.968: W/dalvikvm(1194): dvmFindClassByName rejecting &#39;org/cocos2dx/lib/Cocos2dxHelper&#39;</font></p>
<p>	Google了一下，这个问题很多童鞋都遇到了，但给出的solution却不甚令我满意，于是我就求甚解的细致挖掘了一下，终于找到了一个让我还算满意 的答案。我们分析一下这两条日志，rejecting那条日志总是伴随setJavaVM后面出现的。可引擎什么时候setJavaVM了呢？显然是在 native_app_glue创建的子进程中，引擎需要attachCurrentThread，获得JniEnv时才做的。于是我们打开<font face="Courier New">cocos2d-x-3.0rc0/cocos/2d/platform/android/nativeactivity.cpp</font>一看究竟。</p>
<p>	在nativeactivity.cpp中有两处&quot;org/cocos2dx/lib/Cocos2dxHelper&quot;，我们先看engine_handle_cmd中的那处：</p>
<p>	<font face="Courier New">static void engine_handle_cmd(struct android_app* app, int32_t cmd)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230;. &#8230;.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case APP_CMD_INIT_WINDOW:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOG_RENDER_DEBUG(&quot;android_main : APP_CMD_INIT_WINDOW&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // The window is being shown, get it ready.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (engine-&gt;app-&gt;window != NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos_dimensions d = engine_init_display(engine);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((d.w &gt; 0) &amp;&amp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (d.h &gt; 0)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>cocos2d::JniHelper::setJavaVM(app-&gt;activity-&gt;vm);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::JniHelper::setClassLoaderFrom(app-&gt;activity-&gt;clazz);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // call Cocos2dxHelper.init()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::JniMethodInfo ccxhelperInit;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!cocos2d::JniHelper::getStaticMethodInfo(ccxhelperInit,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;<b>org/cocos2dx/lib/Cocos2dxHelper</b>&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;init&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;(Landroid/app/Activity;)V&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOGI(&quot;cocos2d::JniHelper::getStaticMethodInfo(ccxhelperInit) FAILED&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ccxhelperInit.env-&gt;CallStaticVoidMethod(ccxhelperInit.classID,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ccxhelperInit.methodID,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app-&gt;activity-&gt;clazz);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos_init(d, app);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; engine-&gt;animating = 1;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; engine_draw_frame(engine);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p>	初步可以断定，那两条日志就是执行到这里输出的，但为何dvmFindClassByName方法会rejecting &ldquo;org/cocos2dx/lib/Cocos2dxHelper&rdquo;这个类名呢？我们还得翻看dalvik虚拟机源码：</p>
<p>	<font face="Courier New">&nbsp;/dalvik/vm/native/InternalNative.cpp</p>
<p>	/*<br />
	&nbsp;* Find a class by name, initializing it if requested.<br />
	&nbsp;*/<br />
	ClassObject* dvmFindClassByName(StringObject* nameObj, Object* loader,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool doInit)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; ClassObject* clazz = NULL;<br />
	&nbsp;&nbsp;&nbsp; char* name = NULL;<br />
	&nbsp;&nbsp;&nbsp; char* descriptor = NULL;<br />
	&nbsp;&nbsp;&nbsp; if (nameObj == NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dvmThrowNullPointerException(&quot;name == null&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto bail;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; name = dvmCreateCstrFromString(nameObj);<br />
	&nbsp;&nbsp;&nbsp; /*<br />
	&nbsp;&nbsp;&nbsp;&nbsp; * We need to validate and convert the name (from x.y.z to x/y/z). This<br />
	&nbsp;&nbsp;&nbsp;&nbsp; * is especially handy for array types, since we want to avoid<br />
	&nbsp;&nbsp;&nbsp;&nbsp; * auto-generating bogus array classes.<br />
	&nbsp;&nbsp;&nbsp;&nbsp; */<br />
	&nbsp;&nbsp;&nbsp; if (!dexIsValidClassName(name, true)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>ALOGW(&quot;dvmFindClassByName rejecting &#39;%s&#39;&quot;, name);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dvmThrowClassNotFoundException(name);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto bail;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p>	我们的确找到了输出rejecting日志的地方，通过注释我们可以看到这个方法是用来验证名字对象，并将x.y.z形式的名字转换成x/y/z的。但引 擎中传入的就是&ldquo;x/y/z&rdquo;格式，因此这个方法输出了错误日志。我尝试将上面engine_handle_cmd中的&quot;<font face="Courier New">org/cocos2dx/lib/Cocos2dxHelper</font>&quot;改成&quot;<font face="Courier New">org.cocos2dx.lib.Cocos2dxHelper</font>&quot;，错误日志果然消失了。</p>
<p>	不过目前仍不能解释一点：为何在其他位置，比如在前面的AppDelegate::setupAds中，使用x/y/z格式的jni调用参数却没有输出错误日志！难道两个位置dalvik虚拟机使用的是不同的名字对象查找和加载方法？这个目前尚无定论。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/05/01/integrate-cocos2dx3rc0-with-admob/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
