<?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; Cocos2d-x</title>
	<atom:link href="http://tonybai.com/tag/cocos2d-x/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>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>
		<item>
		<title>Cocos2d-x 3.0多线程异步资源加载</title>
		<link>https://tonybai.com/2014/04/28/multithreaded-resource-loading-in-cocos2dx-3/</link>
		<comments>https://tonybai.com/2014/04/28/multithreaded-resource-loading-in-cocos2dx-3/#comments</comments>
		<pubDate>Sun, 27 Apr 2014 23:13:23 +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[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[内存计数]]></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=1525</guid>
		<description><![CDATA[Cocos2d-x从2.x版本到上周刚刚才发布的Cocos2d-x 3.0 Final版，其引擎驱动核心依旧是一个单线程的&#8220;死循环&#8221;，一旦某一帧遇到了&#8220;大活儿&#8221;，比如Size很大的纹理资源加载或网络IO或大量计算，画面将 不可避免出现卡顿以及响应迟缓的现象。从古老的Win32 GUI编程那时起，Guru们就告诉我们：别阻塞主线程(UI线程)，让Worker线程去做那些&#8220;大活儿&#8221;吧。 手机游戏，即便是休闲类的小游戏，往往也涉及大量纹理资源、音视频资源、文件读写以及网络通信，处理的稍有不甚就会出现画面卡顿，交互不畅的情况。虽然引 擎在某些方面提供了一些支持，但有些时候还是自己祭出Worker线程这个法宝比较灵活，下面就以Cocos2d-x 3.0 Final版游戏初始化为例（针对Android平台），说说如何进行多线程资源加载。 我们经常看到一些手机游戏，启动之后首先会显示一个带有公司Logo的闪屏画面(Flash Screen)，然后才会进入一个游戏Welcome场景，点击&#8220;开始&#8221;才正式进入游戏主场景。而这里Flash Screen的展示环节往往在后台还会做另外一件事，那就是加载游戏的图片资源，音乐音效资源以及配置数据读取，这算是一个&#8220;障眼法&#8221;吧，目的就是提高用 户体验，这样后续场景渲染以及场景切换直接使用已经cache到内存中的数据即可，无需再行加载。 一、为游戏添加FlashScene 在游戏App初始化时，我们首先创建FlashScene，让游戏尽快显示FlashScene画面： // AppDelegate.cpp bool AppDelegate::applicationDidFinishLaunching() { &#160;&#160;&#160; &#8230; &#8230; &#160;&#160;&#160; FlashScene* scene = FlashScene::create(); &#160;&#160;&#160; pDirector-&#62;runWithScene(scene); &#160;&#160;&#160; return true; } 在FlashScene init时，我们创建一个Resource Load Thread，我们用一个ResourceLoadIndicator作为渲染线程与Worker线程之间交互的媒介。 //FlashScene.h struct ResourceLoadIndicator { &#160;&#160;&#160; pthread_mutex_t mutex; &#160;&#160;&#160; bool load_done; &#160;&#160;&#160; void *context; }; class FlashScene : [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.cocos2d-x.org">Cocos2d-x</a>从<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">2.x版</a>本到上周刚刚才发布的<a href="http://www.cocos2d-x.org/news/215">Cocos2d-x 3.0 Final版</a>，其引擎驱动核心依旧是一个单线程的&ldquo;死循环&rdquo;，一旦某一帧遇到了&ldquo;大活儿&rdquo;，比如Size很大的纹理资源加载或网络IO或大量计算，画面将 不可避免出现卡顿以及响应迟缓的现象。从古老的Win32 GUI编程那时起，Guru们就告诉我们：别阻塞主线程(UI线程)，让Worker线程去做那些&ldquo;大活儿&rdquo;吧。</p>
<p>手机游戏，即便是休闲类的小游戏，往往也涉及大量纹理资源、音视频资源、文件读写以及网络通信，处理的稍有不甚就会出现画面卡顿，交互不畅的情况。虽然引 擎在某些方面提供了一些支持，但有些时候还是自己祭出Worker线程这个法宝比较灵活，下面就以Cocos2d-x 3.0 Final版游戏初始化为例（针对Android平台），说说如何进行多线程资源加载。</p>
<p>我们经常看到一些手机游戏，启动之后首先会显示一个带有公司Logo的闪屏画面(Flash Screen)，然后才会进入一个游戏Welcome场景，点击&ldquo;开始&rdquo;才正式进入游戏主场景。而这里Flash Screen的展示环节往往在后台还会做另外一件事，那就是加载游戏的图片资源，音乐音效资源以及配置数据读取，这算是一个&ldquo;障眼法&rdquo;吧，目的就是提高用 户体验，这样后续场景渲染以及场景切换直接使用已经cache到内存中的数据即可，无需再行加载。</p>
<p><b>一、为游戏添加FlashScene</b></p>
<p>在游戏App初始化时，我们首先创建FlashScene，让游戏尽快显示FlashScene画面：</p>
<p><font face="Courier New">// AppDelegate.cpp<br />
	bool AppDelegate::applicationDidFinishLaunching() {<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; FlashScene* scene = FlashScene::create();<br />
	&nbsp;&nbsp;&nbsp; pDirector-&gt;runWithScene(scene);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return true;<br />
	}</font></p>
<p>在FlashScene init时，我们创建一个Resource Load Thread，我们用一个ResourceLoadIndicator作为渲染线程与Worker线程之间交互的媒介。</p>
<p><font face="Courier New">//FlashScene.h</font></p>
<p><font face="Courier New">struct ResourceLoadIndicator {<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_t mutex;<br />
	&nbsp;&nbsp;&nbsp; bool load_done;<br />
	&nbsp;&nbsp;&nbsp; void *context;<br />
	};</font></p>
<p><font face="Courier New">class FlashScene : public Scene<br />
	{<br />
	public:<br />
	&nbsp;&nbsp;&nbsp; FlashScene(void);<br />
	&nbsp;&nbsp;&nbsp; ~FlashScene(void);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; virtual bool init();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; CREATE_FUNC(FlashScene);<br />
	&nbsp;&nbsp;&nbsp; bool getResourceLoadIndicator();<br />
	&nbsp;&nbsp;&nbsp; void setResourceLoadIndicator(bool flag);</font></p>
<p><font face="Courier New">private:<br />
	&nbsp;&nbsp;&nbsp;&nbsp; void updateScene(float dt);</font></p>
<p><font face="Courier New">private:<br />
	&nbsp;&nbsp;&nbsp;&nbsp; ResourceLoadIndicator rli;<br />
	};</font></p>
<p><font face="Courier New">// FlashScene.cpp<br />
	bool FlashScene::init()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; bool bRet = false;<br />
	&nbsp;&nbsp;&nbsp; do {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CC_BREAK_IF(!CCScene::init());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Size winSize = Director::getInstance()-&gt;getWinSize();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //FlashScene自己的资源只能同步加载了<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sprite *bg = Sprite::create(&quot;FlashSceenBg.png&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CC_BREAK_IF(!bg);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bg-&gt;setPosition(ccp(winSize.width/2, winSize.height/2));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this-&gt;addChild(bg, 0);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this-&gt;schedule(schedule_selector(FlashScene::updateScene)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; , 0.01f);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //start the resource loading thread<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rli.load_done = false;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rli.context = (void*)this;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_mutex_init(&amp;rli.mutex, NULL);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_attr_t attr;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_attr_init(&amp;attr);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_DETACHED);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_t thread;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_create(&amp;thread, &amp;attr,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; resource_load_thread_entry, &amp;rli);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bRet=true;<br />
	&nbsp;&nbsp;&nbsp; } while(0);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return bRet;<br />
	}</font></p>
<p><font face="Courier New">static void* resource_load_thread_entry(void* param)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; AppDelegate *app = (AppDelegate*)Application::getInstance();<br />
	&nbsp;&nbsp;&nbsp; ResourceLoadIndicator *rli = (ResourceLoadIndicator*)param;<br />
	&nbsp;&nbsp;&nbsp; FlashScene *scene = (FlashScene*)rli-&gt;context;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //load music effect resource<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //init from config files<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //load images data in worker thread<br />
	&nbsp;&nbsp;&nbsp; SpriteFrameCache::getInstance()-&gt;addSpriteFramesWithFile(<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; &quot;All-Sprites.plist&quot;);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //set loading done<br />
	&nbsp;&nbsp;&nbsp; scene-&gt;setResourceLoadIndicator(true);<br />
	&nbsp;&nbsp;&nbsp; return NULL;<br />
	}</font></p>
<p><font face="Courier New">bool FlashScene::getResourceLoadIndicator()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; bool flag;<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_lock(&amp;rli.mutex);<br />
	&nbsp;&nbsp;&nbsp; flag = rli.load_done;<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_unlock(&amp;rli.mutex);<br />
	&nbsp;&nbsp;&nbsp; return flag;<br />
	}</font></p>
<p><font face="Courier New">void FlashScene::setResourceLoadIndicator(bool flag)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_lock(&amp;rli.mutex);<br />
	&nbsp;&nbsp;&nbsp; rli.load_done = flag;<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_unlock(&amp;rli.mutex);<br />
	&nbsp;&nbsp;&nbsp; return;<br />
	}</font></p>
<p>我们在定时器回调函数中对indicator标志位进行检查，当发现加载ok后，切换到接下来的游戏开始场景：</p>
<p><font face="Courier New">void FlashScene::updateScene(float dt)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (getResourceLoadIndicator()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Director::getInstance()-&gt;replaceScene(<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; WelcomeScene::create());<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>到此，FlashScene的初始设计和实现完成了。Run一下试试吧。</p>
<p><b>二、崩溃</b></p>
<p>在<a href="http://genymotion.com/">GenyMotion</a>的4.4.2模拟器上，游戏运行的结果并没有如我期望，FlashScreen显现后游戏就异常崩溃退出了。</p>
<p>通过monitor分析游戏的运行日志，我们看到了如下一些异常日志：</p>
<p><font face="Courier New">threadid=24: thread exiting, not yet detached (count=0)<br />
	threadid=24: thread exiting, not yet detached (count=1)<br />
	threadid=24: native thread exited without detaching</font></p>
<p>很是奇怪啊，我们在创建线程时，明明设置了 PTHREAD_CREATE_DETACHED属性了啊：</p>
<p><font face="Courier New">pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_DETACHED);</font></p>
<p>怎么还会出现这个问题，而且居然有三条日志。翻看了一下引擎内核的代码TextureCache::addImageAsync，在线程创建以及线程主函数中也没有发现什么特别的设置。为何内核可以创建线程，我自己创建就会崩溃呢。Debug多个来回，问题似乎聚焦在<font face="Courier New">resource_load_thread_entry</font>中执行的任务。在我的代码里，我利用SimpleAudioEngine加载了音效资源、利用UserDefault读取了一些持久化的数据，把这两个任务去掉，游戏就会进入到下一个环节而不会崩溃。</p>
<p>SimpleAudioEngine和UserDefault能有什么共同点呢？Jni调用。没错，这两个接口底层要适配多个平台，而对于Android 平台，他们都用到了Jni提供的接口去调用Java中的方法。而Jni对多线程是有约束的。<a href="http://developer.android.com/training/articles/perf-jni.html">Android开发者官网</a>上有这么一段话：</p>
<p><font face="Courier New">All threads are Linux threads, scheduled by the kernel. They&#39;re usually started from managed code (using Thread.start), but they can also be created elsewhere and then attached to the JavaVM. For example, a thread started with pthread_create can be attached with the JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. <b>Until a thread is attached, it has no JNIEnv, and cannot make JNI calls</b>.</font></p>
<p>由此看来pthread_create创建的新线程默认情况下是不能进行Jni接口调用的，除非Attach到Vm，获得一个JniEnv对象，并且在线 程exit前要Detach Vm。好，我们来尝试一下，Cocos2d-x引擎提供了一些JniHelper方法，可以方便进行Jni相关操作。</p>
<p><font face="Courier New">#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)<br />
	#include &quot;platform/android/jni/JniHelper.h&quot;<br />
	#include &lt;jni.h&gt;<br />
	#endif</font></p>
<p><font face="Courier New">static void* resource_load_thread_entry(void* param)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; JavaVM *vm;<br />
	&nbsp;&nbsp;&nbsp; JNIEnv *env;<br />
	&nbsp;&nbsp;&nbsp; vm = JniHelper::getJavaVM();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; JavaVMAttachArgs thread_args;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; thread_args.name = &quot;Resource Load&quot;;<br />
	&nbsp;&nbsp;&nbsp; thread_args.version = JNI_VERSION_1_4;<br />
	&nbsp;&nbsp;&nbsp; thread_args.group = NULL;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; vm-&gt;<b>AttachCurrentThread</b>(&amp;env, &amp;thread_args);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; //Your Jni Calls<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; vm-&gt;<b>DetachCurrentThread</b>();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; return NULL;<br />
	}</font></p>
<p>关于什么是JavaVM，什么是JniEnv，Android Developer官方文档中是这样描述的：</p>
<p><font face="Courier New">The JavaVM provides the &quot;invocation interface&quot; functions, which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process, but <b>Android only allows one</b>.<br />
	The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv as the first argument.<br />
	The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads. </font></p>
<p><b>三、黑屏</b></p>
<p>上面的代码成功解决了线程崩溃的问题，但问题还没完，因为接下来我们又遇到了&ldquo;黑屏&rdquo;事件。所谓的&ldquo;黑屏&rdquo;，其实并不是全黑。但进入游戏 WelcomScene时，只有Scene中的LabelTTF实例能显示出来，其余Sprite都无法显示。显然肯定与我们在Worker线程加载纹理 资源有关了：</p>
<p><font face="Courier New">SpriteFrameCache::getInstance()-&gt;addSpriteFramesWithFile(&quot;All-Sprites.plist&quot;);</font></p>
<p>我们通过碎图压缩到一张大纹理的方式建立SpriteFrame，这是Cocos2d-x推荐的优化手段。但要想找到这个问题的根源，还得看monitor日志。我们的确发现了一些异常日志：</p>
<p><font face="Courier New">libEGL: call to OpenGL ES API with no current context (logged once per thread)</font></p>
<p>通过Google得知，只有Renderer Thread才能进行egl调用，因为egl的context是在Renderer Thread创建的，Worker Thread并没有EGL的context，在进行egl操作时，无法找到context，因此操作都是失败的，纹理也就无法显示出来。要解决这个问题就 得查看一下TextureCache::addImageAsync是如何做的了。</p>
<p>TextureCache::addImageAsync只是在worker线程进行了image数据的加载，而纹理对象Texture2D instance则是在addImageAsyncCallBack中创建的。也就是说纹理还是在Renderer线程中创建的，因此不会出现我们上面的 &ldquo;黑屏&rdquo;问题。模仿addImageAsync，我们来修改一下代码：</p>
<p><font face="Courier New">static void* resource_load_thread_entry(void* param)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; allSpritesImage = new Image();<br />
	&nbsp;&nbsp;&nbsp; allSpritesImage-&gt;initWithImageFile(&quot;All-Sprites.png&quot;);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">void FlashScene::updateScene(float dt)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (getResourceLoadIndicator()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // construct texture with preloaded images<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Texture2D *allSpritesTexture = TextureCache::getInstance()-&gt;<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; addImage(allSpritesImage, &quot;All-Sprites.png&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; allSpritesImage-&gt;release();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SpriteFrameCache::getInstance()-&gt;addSpriteFramesWithFile(<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; &quot;All-Sprites.plist&quot;, allSpritesTexture);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Director::getInstance()-&gt;replaceScene(WelcomeScene::create());<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>完成这一修改后，游戏画面就变得一切正常了，多线程资源加载机制正式生效。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/04/28/multithreaded-resource-loading-in-cocos2dx-3/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x 3.0rc2集成ShareSDK</title>
		<link>https://tonybai.com/2014/04/25/integrate-cocos2dx3rc2-with-sharesdk/</link>
		<comments>https://tonybai.com/2014/04/25/integrate-cocos2dx3rc2-with-sharesdk/#comments</comments>
		<pubDate>Thu, 24 Apr 2014 16:21:11 +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[Game-Engine]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[github]]></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[ShareSDK]]></category>
		<category><![CDATA[Wechat]]></category>
		<category><![CDATA[weibo]]></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=1521</guid>
		<description><![CDATA[给自己的手机游戏增加些社交分享功能，有助于游戏宣传和提升知名度，是一种不错的社交营销手段。国内这方面的第三方插件有不少，比如ShareSDK、友 盟分享组件、Baidu分享组件等，之前在研究2.2.2版本时，集成了ShareSDK这个组件，这次迁移到Cocos2d-x 3.0rc2依旧选择集成ShareSDK，这里就来说说集成的过程，遇到的一些问题以及解决方法。这里仅以Android平台游戏集成为例。 一、功能描述、SDK版本和帐号准备 功能大致是这样的：在游戏中设置一个按钮，点击这个按钮，弹出知名社交平台的分享图标集窗口，用户选择分享目标后，相关信息分享到对应的社交平台。分享结果通知通过Toast显示在屏幕的下方。 这次依旧使用ShareSDK for Android 2.3.7版本（ShareSDK-Android-2.3.7），Cocos2d-x的版本为3.0rc2。 集成前，你需要有一个基于Cocos2d-x 3.0rc2的可运行的Android平台游戏project，我们的集成就基于该project，这里我们的project名为GameDemo，GameDemo的源码结构大致是： GameDemo/ &#160;&#160;&#160; &#8211; Classes/ &#160;&#160;&#160; &#8211; proj.android/ &#160;&#160;&#160; &#8211; Resources/ &#160;&#160;&#160; &#8211; cocos2d/ &#160;&#160;&#160; &#8211; CMakeLists.txt &#160;&#160;&#160; &#8211; &#8230; &#8230; 使用ShareSDK前，你需要在各大主流社交平台（微信、微博）申请开发者帐号以及游戏接入权限(app_key、app_secret)等，当然在ShareSDK站点也应该有自己的帐号和应用AppKey，这些申请的审核需要几个工作日，甚至更长。 二、ShareSDK集成步骤 按照ShareSDK官方manual说法，Cocos2d-x集成ShareSDK有三种方式，之前在Cocos2d-x 2.2.2引擎中采用的是专用组件集成的方式，该组件(C2DXShareSDKSample)可以在这里下载（该组件近期已经fix了我之前发现的bug）。 1.&#160; jar包集成 这次我们主要做微博、微信的社交分享，因此只需要微博、微信相关jar包。在C2DXShareSDKSample/proj.android/libs下，我们找到以下几个jar包： &#160; -rw-rw-r&#8211; 1 tonybai tonybai&#160; 97K&#160; 4月&#160; 8 18:10 mframework.jar &#160; -rw-rw-r&#8211; 1 tonybai tonybai 112K&#160; 4月&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>给自己的手机游戏增加些社交分享功能，有助于游戏宣传和提升知名度，是一种不错的社交营销手段。国内这方面的第三方插件有不少，比如<a href="http://sharesdk.cn">ShareSDK</a>、<a href="http://www.umeng.com/component_social">友 盟分享组件</a>、<a href="http://developer.baidu.com/soc/share">Baidu分享组件</a>等，之前在研究<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">2.2.2版本</a>时，<a href="http://tonybai.com/2014/04/17/a-bug-from-sharesdk-componet-for-cocos2dx/">集成了ShareSDK</a>这个组件，这次迁移到<a href="http://tonybai.com/2014/04/23/changes-in-cocos2dx-3-rc2-for-android/">Cocos2d-x 3.0rc2</a>依旧选择集成ShareSDK，这里就来说说集成的过程，遇到的一些问题以及解决方法。这里仅以Android平台游戏集成为例。</p>
<p>	<b>一、功能描述、</b><b>SDK版本和帐号准备</b></p>
<p>	功能大致是这样的：在游戏中设置一个按钮，点击这个按钮，弹出知名社交平台的分享图标集窗口，用户选择分享目标后，相关信息分享到对应的社交平台。分享结果通知通过Toast显示在屏幕的下方。</p>
<p>	这次依旧使用ShareSDK for Android 2.3.7版本（ShareSDK-Android-2.3.7），Cocos2d-x的版本为<a href="http://www.cocos2d-x.org">3.0rc2</a>。</p>
<p>	集成前，你需要有一个基于Cocos2d-x 3.0rc2的可运行的Android平台游戏project，我们的集成就基于该project，这里我们的project名为GameDemo，GameDemo的源码结构大致是：</p>
<p>	<font face="Courier New">GameDemo/<br />
	&nbsp;&nbsp;&nbsp; &#8211; Classes/<br />
	&nbsp;&nbsp;&nbsp; &#8211; proj.android/<br />
	&nbsp;&nbsp;&nbsp; &#8211; Resources/<br />
	&nbsp;&nbsp;&nbsp; &#8211; cocos2d/<br />
	&nbsp;&nbsp;&nbsp; &#8211; CMakeLists.txt<br />
	&nbsp;&nbsp;&nbsp; &#8211; &#8230; &#8230;</font></p>
<p>	使用ShareSDK前，你需要在各大主流社交平台（<a href="http://open.weixin.qq.com/">微信</a>、<a href="http://open.weibo.com">微博</a>）申请开发者帐号以及游戏接入权限(app_key、app_secret)等，当然在ShareSDK站点也应该有自己的帐号和应用AppKey，这些申请的审核需要几个工作日，甚至更长。</p>
<p>	<b>二、ShareSDK集成步骤</b></p>
<p>	按照ShareSDK官方manual说法，Cocos2d-x集成ShareSDK有三种方式，之前在Cocos2d-x 2.2.2引擎中采用的是专用组件集成的方式，该组件(C2DXShareSDKSample)可以在<a href="https://github.com/ShareSDKPlatform/C2DXShareSDKSample">这里</a>下载（该组件近期已经fix了<a href="http://tonybai.com/2014/04/17/a-bug-from-sharesdk-componet-for-cocos2dx/">我之前发现的bug</a>）。</p>
<p>	<b>1.&nbsp; jar包集成</b></p>
<p>	这次我们主要做微博、微信的社交分享，因此只需要微博、微信相关jar包。在C2DXShareSDKSample/proj.android/libs下，我们找到以下几个jar包：</p>
<p>	<font face="Courier New">&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai&nbsp; 97K&nbsp; 4月&nbsp; 8 18:10 mframework.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai 112K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Core-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai&nbsp; 19K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-SinaWeibo-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai 4.3K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Wechat-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai&nbsp; 29K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Wechat-Core-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai 4.6K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Wechat-Favorite-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai 4.4K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Wechat-Moments-2.3.7.jar</font></p>
<p>	把这些jar包文件Copy到GameDemo/proj.android/libs下。</p>
<p>
	<b>2. 配置文件与资源部分集成</b></p>
<p>	修改GameDemo/proj.android/AndroidManifest.xml文件，在application标签下，添加如下Activity标签：</p>
<p>	&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;cn.sharesdk.framework.ShareSDKUIShell&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:configChanges=&quot;keyboardHidden|orientation|screenSize&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:screenOrientation=&quot;portrait&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@android:style/Theme.Translucent.NoTitleBar&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:windowSoftInputMode=&quot;stateHidden|adjustResize&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp; &lt;/activity&gt;<br />
	&nbsp;&nbsp;&nbsp; &lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;.wxapi.WXEntryActivity&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:configChanges=&quot;keyboardHidden|orientation|screenSize&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:exported=&quot;true&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:screenOrientation=&quot;portrait&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@android:style/Theme.Translucent.NoTitleBar&quot; /&gt;</font></p>
<p>	将C2DXShareSDKSample/proj.android/res下的如下目录中的文件复制到GameDemo/proj.android/res下：</p>
<p>	<font face="Courier New">&nbsp;&nbsp; drawable-hdpi/&nbsp; drawable-ldpi/&nbsp; drawable-mdpi/&nbsp;<br />
	&nbsp;&nbsp; drawable-xhdpi/&nbsp; layout/&nbsp; values/&nbsp; values-en/</font></p>
<p>	注意，类似icon.png这种文件就不要复制了，自己做一下判断就好。</p>
<p>
	<b>3. C++部分代码集成</b></p>
<p>	将C2DXShareSDKSample/Classes下的C2DXShareSDK文件夹Copy到GameDemo/Classes下面。</p>
<p>	由于Cocos2d-x 3.0rc2的类命名发生了变化，我们需要对C2DXShareSDK中使用到的引擎中的类名以及方法名进行修改。但实际上Cocos2d-x 3.0rc2考虑到了一些兼容性的问题，大部分名字通过<font face="Courier New">cocos2d/cocos/deprecated/CCDeprecated.h</font>中定义的typedef得以保留，虽然这些名字已经被建议deprecated了。rc2中CCObject被改名为Ref了，这个我们需要手工在C2DXShareSDK进行修改。</p>
<p>	另外ShareSDK组件在实现时大量使用了<font face="Courier New">CCDictionary</font>、<font face="Courier New">CCArray</font>和<font face="Courier New">CCString</font>，而这三个类在Cocos2d-x 3.0rc2中均被deprecated了，但我们依然可以使用，所以我们可以不做修改。但以后随着cocos2d-x版本的演进，这些类很可能被彻底移除出引擎，我们就需要重新使用其替代品进行实现了。</p>
<p>	此外我们还需要手工修改一下C2DXShareSDK/Android/JSON/CCJSONConverter.cpp文件中的<font face="Courier New">getObjJson</font>方 法，因为rc2中CCDictionary、CCString、CCArray这些类的真实名称都已经换成了__Dictionary、__String 和__Array，CCDictionary、CCString、CCArray只是些typedef，因此要像下面这样做些修改(如果你是集成 cocos2d-x 2.x.x版本，则无需做下面修改)：</p>
<p>	<font face="Courier New">cJSON * CCJSONConverter::getObjJson(Ref * obj)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; std::string s = typeid(*obj).name();<br />
	&nbsp;&nbsp;&nbsp; if(s.find(&quot;<b>__Dictionary</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateObject();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; convertDictionaryToJson((CCDictionary *)obj, json);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }else if(s.find(&quot;<b>__Array</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateArray();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; convertArrayToJson((CCArray *)obj, json);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }else if(s.find(&quot;<b>__String</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCString * s = (CCString *)obj;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateString(s-&gt;getCString());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }else if(s.find(&quot;<b>CCNumber</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCNumber * n = (CCNumber *)obj;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateNumber(n-&gt;getDoubleValue());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }else if(s.find(&quot;<b>CCNull</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateNull();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; CCLog(&quot;CCJSONConverter encountered an unrecognized type&quot;);<br />
	&nbsp;&nbsp;&nbsp; return NULL;<br />
	}</font></p>
<p>	CCNumber和CCNull是ShareSDK组件自己实现的类名，这里无需修改。</p>
<p>	接下来我们需要在AppDelegate.cpp中对ShareSDK做初始化了：</p>
<p>	<font face="Courier New">bool AppDelegate::applicationDidFinishLaunching() {<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; initShareSDK();<br />
	&nbsp;&nbsp;&nbsp; &#8230; ..<br />
	}</p>
<p>	void AppDelegate::initShareSDK()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; // sina weibo<br />
	&nbsp;&nbsp;&nbsp; CCDictionary *sinaConfigDict = CCDictionary::create();<br />
	&nbsp;&nbsp;&nbsp; sinaConfigDict-&gt;setObject(CCString::create(&quot;<i>YOUR_WEIBO_APPKEY</i>&quot;), &quot;app_key&quot;);<br />
	&nbsp;&nbsp;&nbsp; sinaConfigDict-&gt;setObject(CCString::create(&quot;<i>YOUR_WEBIO_APPSECRET</i>&quot;), &quot;app_secret&quot;);<br />
	&nbsp;&nbsp;&nbsp; sinaConfigDict-&gt;setObject(CCString::create(&quot;http://www.sharesdk.cn&quot;), &quot;redirect_uri&quot;);<br />
	&nbsp;&nbsp;&nbsp; C2DXShareSDK::setPlatformConfig(C2DXPlatTypeSinaWeibo, sinaConfigDict);</p>
<p>	&nbsp;&nbsp;&nbsp; // wechat<br />
	&nbsp;&nbsp;&nbsp; CCDictionary *wcConfigDict = CCDictionary::create();<br />
	&nbsp;&nbsp;&nbsp; wcConfigDict-&gt;setObject(CCString::create(&quot;<i>YOUR_WECHAT_APPID</i>&quot;), &quot;app_id&quot;);<br />
	&nbsp;&nbsp;&nbsp; C2DXShareSDK::setPlatformConfig(C2DXPlatTypeWeixiSession, wcConfigDict);<br />
	&nbsp;&nbsp;&nbsp; C2DXShareSDK::setPlatformConfig(C2DXPlatTypeWeixiTimeline, wcConfigDict);<br />
	&nbsp;&nbsp;&nbsp; C2DXShareSDK::setPlatformConfig(C2DXPlatTypeWeixiFav, wcConfigDict);</p>
<p>	&nbsp;&nbsp;&nbsp; C2DXShareSDK::open(CCString::create(&quot;YOUR_SHARESDK_APPKEY&quot;), false);<br />
	}</font></p>
<p>	在Share按钮的事件回调函数中调用ShareSDK的接口进行社交平台分享：</p>
<p>	<font face="Courier New">void GameScene::menuShareCallback(Ref* sender)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; Dictionary *content = Dictionary::create();</p>
<p>	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;ShareSDK for Cocos2d-x 3.0rc2社交分享测试。&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; , &quot;content&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;ShareSDK分享测试&quot;), &quot;title&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;http://tonybai.com&quot;), &quot;titleUrl&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;http://tonybai.com&quot;), &quot;url&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;Tony Bai&quot;), &quot;site&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;http://tonybai.com&quot;), &quot;siteUrl&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::createWithFormat(&quot;%s&quot;, <i>YOUR_LOCAL_IMAGE_PAT</i>H)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; , &quot;image&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::createWithFormat(&quot;%d&quot;, C2DXContentTypeNews)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; , &quot;type&quot;);</p>
<p>	&nbsp;&nbsp;&nbsp; C2DXShareSDK::showShareMenu(NULL, content, CCPointMake(100, 100),<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; C2DXMenuArrowDirectionLeft, shareResultHandler);<br />
	}</font></p>
<p>	<font face="Courier New">void shareResultHandler(C2DXResponseState state,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C2DXPlatType platType,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Dictionary *shareInfo,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Dictionary *error)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; AppDelegate *app = (AppDelegate*)Application::getInstance();<br />
	&nbsp;&nbsp;&nbsp; switch (state) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case C2DXResponseStateSuccess:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCLog(&quot;Share Ok&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app-&gt;showShareResultToast(&quot;分享成功&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case C2DXResponseStateFail:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app-&gt;showShareResultToast(&quot;分享失败&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCLog(&quot;Share Failed&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	showShareResultToast实现如下：</p>
<p>	<font face="Courier New">void AppDelegate::showShareResultToast(const char *msg)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; JniMethodInfo t;<br />
	&nbsp;&nbsp;&nbsp; if (JniHelper::getStaticMethodInfo(t, &quot;YOUR_ACTIVITY_NAME&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;showShareResultToast&quot;, &quot;(Ljava/lang/String;)V&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jstring jmsg = t.env-&gt;NewStringUTF(msg);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;CallStaticVoidMethod(t.classID, t.methodID, jmsg);<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 />
	}</font></p>
<p>
	<b>4. Java部分代码集成</b></p>
<p>	在GameDemo/proj.android/src下面建立cn/sharesdk路径，将C2DXShareSDKSample /proj.android/src/cn/sharesdk下的onekeyshare和ShareSDKUtils.java Copy到GameDemo/proj.android/src/cn/sharesdk下面。</p>
<p>	将ShareSDK-Android-2.3.7.zip解压后的ShareSDK for Android/Src/wxapi Copy到GameDemo/proj.android/src/com.tonybai.game/下。</p>
<p>	修改GameDemo/proj.android/src/com.tonybai.game/GameDemoActivity.java文件：</p>
<p>	<font face="Courier New">import android.widget.Toast;<br />
	import cn.sharesdk.ShareSDKUtils;<br />
	&#8230;</p>
<p>	public class GameDemoActivity extends Cocos2dxActivity {</p>
<p>	&nbsp;&nbsp;&nbsp; private static Context context;</p>
<p>	&nbsp;&nbsp;&nbsp; private static Handler notifyHandler = 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; switch (msg.what) {<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; String message = (String) msg.obj;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Toast.makeText(context, message,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Toast.LENGTH_SHORT).show();<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; default:<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; context = this;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ShareSDKUtils.prepare();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ShareSDKUtils.initSDK(&quot;YOUR_SHARESDK_APPKEY&quot;, true);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static void showShareResultToast(String result) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 1;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = result;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; public void onDestroy() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ShareSDKUtils.stopSDK();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onDestroy();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	<b>三、问题与解决方法</b></p>
<p>	按照上面的集成方法修改后，通过cocos编译app，在模拟器运行GameDemo，点击Share，理论上屏幕下方会出现ShareSDK的分享窗口，选择&ldquo;新浪微博&rdquo;图标，会打开&ldquo;图文分享&rdquo;内容窗口，点击窗口右上角的&ldquo;分享&rdquo;即可。</p>
<p>	【<b>问题1</b>】&ldquo;图文分享&rdquo;窗口内容可编辑，并且总是弹出软键盘，影响体验。<br />
	&nbsp;<br />
	&nbsp;期望：内容不可编辑，默认不弹出软键盘<br />
	&nbsp;解决方法：<br />
	&nbsp;&nbsp;&nbsp; &nbsp; 打开proj.android/src/cn/sharesdk/onekeyshare/EditPage.java，做如下修改：</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 将窗口的软输入方式默认改为SOFT_INPUT_STATE_HIDDEN。</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp; <font face="Courier New">public void setActivity(Activity activity) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.setActivity(activity);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (dialogMode) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; activity.setTheme(android.R.style.Theme_Dialog);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; activity.requestWindowFeature(Window.FEATURE_NO_TITLE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; activity.getWindow().setSoftInputMode(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <u><b>WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);//default: hidden</b></u><br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; 在initPageView中增加一行：etContent.setKeyListener(null)。让窗口内容无法修改。<br />
	&nbsp;&nbsp;&nbsp; private void initPageView() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 文字输入区域<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent = new EditText(getContext());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent.setGravity(Gravity.LEFT | Gravity.TOP);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent.setBackgroundDrawable(null);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent.setText(String.valueOf(reqData.get(&quot;text&quot;)));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>etContent.setKeyListener(null);//make the edittext uneditable</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent.setLayoutParams(lpEt);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	<b>【问题2】</b>向微博分享，点击&ldquo;分享&rdquo;后，过一会程序异常停止。</p>
<p>	&nbsp;原因分析：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 通过调试观察，发现ShareSDK在解析从Weibo收到的Json包时出现内存违法访问。具体位置是在解析一个数组对象时出现的问题。 ShareSDK用CCArray来存储Json中的数组对象。该问题在cocos2d-x 2.2.2版本中不会出现，但在cocos2d-x 3.0rc2版本中会出现。经代码对比发现，3.0rc2版本中的CCArray的实现与2.2.2 CCArray实现有很大不同，似乎是做了较大重构，暂不能确定是否是3.0rc2版本中CCArray实现的bug。</p>
<p>	&nbsp;解决方法：由于后续的分享结果通知成功与否只需要根据分享的状态来决定，因此我们只需解析出&quot;status&quot;、&ldquo;action&rdquo;和&ldquo;platform&rdquo; 这三个CCNumber类型字段的值即可。CCArray类型的对象我们并不需要，因此我们只需绕过对Array类型字段的解析和存储即可，修改如下：</p>
<p>	<font face="Courier New">// Classes/C2DXShareSDK/Android/JSON/CCJSONConverter.cpp</p>
<p>	void CCJSONConverter::convertJsonToDictionary(cJSON *json, CCDictionary *dictionary)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; dictionary-&gt;removeAllObjects();<br />
	&nbsp;&nbsp;&nbsp; cJSON * j = json-&gt;child;<br />
	&nbsp;&nbsp;&nbsp; while (j) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>if (j-&gt;type == cJSON_Number) {</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Ref * obj = getJsonObj(j);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dictionary-&gt;setObject(obj, j-&gt;string);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>}</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; j = j-&gt;next;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	<b>四、其他</b></p>
<p>	在使用ShareSDK做社交分享时，注意下面两个现象：<br />
	1) 第一次进行微博或微信分享时，会打开授权页面，授权后才能分享成功；<br />
	2) 微信分享窗口只有在手机联网状态下才能打开。如果手机无法联网，那微信好友、朋友圈和收藏分享将无法打开分享窗口，也不会有什么提示。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/04/25/integrate-cocos2dx3rc2-with-sharesdk/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x 3.0rc2针对Android平台的变动</title>
		<link>https://tonybai.com/2014/04/23/changes-in-cocos2dx-3-rc2-for-android/</link>
		<comments>https://tonybai.com/2014/04/23/changes-in-cocos2dx-3-rc2-for-android/#comments</comments>
		<pubDate>Wed, 23 Apr 2014 15:28:40 +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[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[内存计数]]></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=1517</guid>
		<description><![CDATA[《Hello, Cocos2d-x 3.0》一文发出后没多久，我就迫不及待地将手头的一个习作尝试从2.2.2版本迁移到3.0rc0引擎上。 核心代码迁移相对顺利，大致流程如下： &#160; * 创建项目 &#160;&#160;&#160; 1) cd cocos2d-x-3.0rc0； &#160;&#160;&#160; 2) 执行setup.py，设置引擎依赖的环境变量，脚本会将COCOS_CONSOLE_ROOT和ANT_ROOT写入到~/.bash_profile中； 执行source ~/.bash_profile使得环境变量生效； &#160;&#160;&#160; 3) 在cocos2d-x-3.0rc0下建立projects目录； &#160;&#160;&#160; 4) 利用cocos2d-console工具建立新项目： cocos new GameDemo -p com.tonybai.game.gamedemo -l cpp -d ./projects &#160;&#160;&#160; 5) cd ./projects/GameDemo，我们可以看到项目目录结构如下： &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; bin/&#160; Classes/&#160; CMakeLists.txt&#160; cocos2d/&#160; proj.android/&#160; &#160;&#160;&#160;&#160;&#160; proj.ios_mac/&#160; proj.linux/&#160; proj.win32/&#160; Resources/ &#160;&#160;&#160; 6) 执行cocos compile -p android -j [...]]]></description>
			<content:encoded><![CDATA[<p>《<a href="http://tonybai.com/2014/04/22/hello-cocos2dx-3-rc0/">Hello, Cocos2d-x 3.0</a>》一文发出后没多久，我就迫不及待地将手头的一个习作尝试从2.2.2版本迁移到3.0rc0引擎上。</p>
<p>	核心代码迁移相对顺利，大致流程如下：</p>
<p>	&nbsp; <b>* 创建项目</b></p>
<p>	&nbsp;&nbsp;&nbsp; 1) cd cocos2d-x-3.0rc0；<br />
	&nbsp;&nbsp;&nbsp; 2) 执行<font face="Courier New">setup.py</font>，设置引擎依赖的环境变量，脚本会将<font face="Courier New">COCOS_CONSOLE_ROOT</font>和<font face="Courier<br />
      New">ANT_ROOT</font>写入到<font face="Courier New">~/.bash_profile</font>中； 执行<font face="Courier New">source ~/.bash_profile</font>使得环境变量生效；<br />
	&nbsp;&nbsp;&nbsp; 3) 在cocos2d-x-3.0rc0下建立projects目录；<br />
	&nbsp;&nbsp;&nbsp; 4) 利用cocos2d-console工具建立新项目： <font face="Courier New">cocos new GameDemo -p com.tonybai.game.gamedemo -l cpp -d ./projects</font><br />
	&nbsp;&nbsp;&nbsp; 5) cd ./projects/GameDemo，我们可以看到项目目录结构如下：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <font face="Courier New">bin/&nbsp; Classes/&nbsp; CMakeLists.txt&nbsp; cocos2d/&nbsp; proj.android/&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proj.ios_mac/&nbsp; proj.linux/&nbsp; proj.win32/&nbsp; Resources/</font><br />
	&nbsp;&nbsp;&nbsp; 6) 执行<font face="Courier New">cocos compile -p android -j 4&nbsp; &#8211;ap 19 -m release</font>，这个Demo的apk就会被生成，大致就是一个cpp-empty-test；<br />
	&nbsp;&nbsp;&nbsp;<br />
	<b>&nbsp; * 代码移植</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 代码移植的主要工作包括：<br />
	&nbsp;&nbsp;&nbsp; 1) 改名<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 带有CC前缀的类名大都要将前缀去掉；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 各主要类的单例方法sharedXXXX都改为getInstance；</p>
<p>	&nbsp;&nbsp;&nbsp; 2) 菜单、按钮事件处理 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 由menu_selector(GameScene::menuStartCallback) 改为CC_CALLBACK_1(GameScene::menuStartCallback, this)；</p>
<p>	&nbsp;&nbsp;&nbsp; 3) 触屏事件处理</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 在Cocos2d-x 2.2.2中，我们直接使用Layer的setTouchEnabled(true)，并Override 三个触屏事件处理函数；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 在新版引擎中，我们需要建立事件Listener，并将Listener注册到全局EventDispatcher中，诸如：</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <font face="Courier New">auto listener = EventListenerTouchOneByOne::create();<br />
	&nbsp; &nbsp; &nbsp; &nbsp; listener-&gt;setSwallowTouches(true);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; listener-&gt;onTouchBegan = CC_CALLBACK_2(GameLayer::onTouchBegan, this);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; listener-&gt;onTouchMoved = CC_CALLBACK_2(GameLayer::onTouchMoved, this);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; listener-&gt;onTouchEnded = CC_CALLBACK_2(GameLayer::onTouchEnded, this);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Director::getInstance()-&gt;getEventDispatcher()-&gt;addEventListenerWithSceneGraphPriority(listener, this);</font></p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 然后将这里的三个事件处理方法实现出来即可。</p>
<p>	核心功能迁移后，GameDemo在<a href="http://www.genymotion.com/">genymotion</a> 4.4 Android模拟器以及真机上都能正常运行，在模拟器上能保持40左右的帧率，在真机上帧率一直在60左右。玩了一会后，感觉引擎渲染性能的确有提升， 而且这种提升是可以在真机上直观感受到的。</p>
<p>	不过好景不长，我又尝试将GameDemo在genymotion 2.3.7 Android上运行，这回得到的结果却是：<b>黑屏</b>。 又将Cocos2d-x 3.0rc0自带的cpp-empty-test编译后放到模拟器上运行，得到了同样的黑屏结果，显然这可能是rc0的一个问题。在Cocos2d-x forum上粗略搜到的结果是：升级到最新版本可以解决黑屏问题。于是到官方下载目前最新发布版Cocos2d-x 3.0rc2。这里也吐槽一下：cocos2d-x引擎包Size太大了，似乎也没有提供什么patch文件，导致每发一个版本都要下载几百M的包。官方<a href="https://github.com/cocos2d/cocos2d-x"> git repository</a>也太大了，尝试clone了几次都失败了，最终还只能下载源码的zip包。</p>
<p>	Cocos2d-x 3.0rc2下载解压后，先编译了一下cpp-empty-test，然后部署到Android 2.3.7上运行，这回&ldquo;黑屏&rdquo;的确不见了，看来rc2修正了这个问题。接下来就是将我的GameDemo移植到rc2上了。</p>
<p>	我用解压后的&ldquo;cocos2d-x 3.0rc2&rdquo;替换GameDemo下的cocos2d，然后运行cocos compile编译，install到模拟器行运行，程序启动失败，从monitor logcat中看到一行错误日志：</p>
<p>	&nbsp;&nbsp;&nbsp; &ldquo;<font face="Courier New">ANativeActivity_onCreate not found</font>&rdquo;</p>
<p>	怎么会呢？<font face="Courier New">ANativeActivity_onCreate</font>是由NDK的 native_app_glue static library提供的，怎么会找不到呢？</p>
<p>	于是乎打开GameDemo/cocos2d/cocos/2d/platform/android/Android.mk打算查看一下究竟：</p>
<p>	<font face="Courier New">LOCAL_WHOLE_STATIC_LIBRARIES&nbsp;&nbsp;&nbsp; := cocos_png_static cocos_jpeg_static cocos_tiff_static cocos_webp_static</p>
<p>
	include $(BUILD_STATIC_LIBRARY)</p>
<p>	$(call import-module,jpeg/prebuilt/android)<br />
	$(call import-module,png/prebuilt/android)<br />
	$(call import-module,tiff/prebuilt/android)<br />
	$(call import-module,webp/prebuilt/android)</font></p>
<p>	Android.mk内容中居然没有将native_app_glue列入，又翻看了一下cocos2d-x 3.0rc0中的同位置Android.mk，后者是有native_app_glue的库依赖的。难道是rc2这块忘记了？于是我尝试将 native_app_glue依赖加上：</p>
<p>	<font face="Courier New">LOCAL_WHOLE_STATIC_LIBRARIES&nbsp;&nbsp; := android_native_app_glue cocos_png_static cocos_jpeg_static cocos_tiff_static cocos_webp_static</p>
<p>	include $(BUILD_STATIC_LIBRARY)</p>
<p>	$(call import-module,jpeg/prebuilt/android)<br />
	$(call import-module,png/prebuilt/android)<br />
	$(call import-module,tiff/prebuilt/android)<br />
	$(call import-module,webp/prebuilt/android)<br />
	$(call import-module,android/native_app_glue)</font></p>
<p>	再次尝试编译，不过这次连编译都没能通过，错误的build结果如下：</p>
<p>	<font face="Courier New">/home1/tonybai/android-dev/adt-bundle-linux-x86_64/android-ndk-r9c/sources/android/native_app_glue/android_native_app_glue.c:232: error: undefined reference to &#39;<b>android_main</b>&#39;<br />
	collect2: error: ld returned 1 exit status<br />
	make: *** [obj/local/armeabi/libgamedemo.so] Error 1</font></p>
<p>	从结果来看，链接器没能找到native_app_glue中<font face="Courier New">android_main</font>对 应的函数体定义。android_main可是cocos2d-x 3.0引擎提供的实现啊。于是乎再次进入到rc2引擎代码中查找原因，结果却让我很是吃惊：<b><font face="Courier<br />
        New">&ldquo;NativeActivity被引擎移除了&rdquo;！</font></b>cocos2d/cocos/2d/platform /android目录下面已经没有了nativeactivity.h和nativeactivity.cpp了：</p>
<p>	<font face="Courier New">$ ls -F cocos2d/cocos/2d/platform/android<br />
	Android.mk&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCApplication.h&nbsp; CCDevice.cpp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCFileUtilsAndroid.h&nbsp; CCGLView.cpp&nbsp; CCPlatformDefine.h&nbsp; java/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jni/<br />
	CCApplication.cpp&nbsp; CCCommon.cpp&nbsp;&nbsp;&nbsp;&nbsp; CCFileUtilsAndroid.cpp&nbsp; CCGL.h&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCGLView.h&nbsp;&nbsp;&nbsp; CCStdC.h&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; javaactivity.cpp</font></p>
<p>	我们看到了一个新文件：javaactivity.cpp，打开该文件，我们发现了和cocos2d-x 2.2.2版本类似的名字：Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit。难道rc2针对 Android平台的引擎入口代码回退到2.x版本的设计了？于是乎赶紧进到<font face="Courier New">/cocos2d/cocos/2d/platform/android/java/src/org/cocos2dx/lib</font>目 录下一看究竟。</p>
<p>	果不其然，一切看起来都那么的熟悉：Cocos2dxActivity.java、Cocos2dxGLSurfaceView.java、 Cocos2dxRenderer.java&#8230;.。自此可以断定，rc2中Android平台的引擎设计退回到了2.x版：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; &#8211; 你的GameActivity要集成Cocos2dxActivity；<br />
	&nbsp;&nbsp;&nbsp; &#8211; mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer())时，GLThread(渲染线程)诞生<br />
	&nbsp;&nbsp;&nbsp; &#8211; 死循环调用Cocos2dxRenderer.onDrawFrame<br />
	&nbsp;&nbsp;&nbsp; &#8211; 引擎逻辑就在Cocos2dxRenderer.onDrawFrame中被执行。</font></p>
<p>	关于2.2.2版Cocos2dx引擎的结构说明可以参考我的《<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">Hello, Cocos2d-x</a>》一文。</p>
<p>	回到了2.2.2版本设计的引擎在性能上是否会像rc0那样给人以直观提升的感觉呢，即便渲染器是新写的？真机测试的结果表明，没有直观感觉到提 升。难道是Native Thread(pthread_create创建）和Java Thread之间的差别？不得而知，后续慢慢体会吧。</p>
<p>	另外要提一句：javaactivity.cpp将以往2.2.2版本放在项目jni中的 Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit挪到了引擎中，本来就是基本不变的代码， 放在引擎中的确更好。rc2的设计回归也有一定好处，一是以前对引擎的认识还适用，二呢就是适合集成在2.2.2版本中的第三方工具的方法应该同样适合3.0rc2版本，这样移 植成本估计会小些。这样我们针对新的3.0引擎，重点还是去关注渲染器、事件分发机制以及物理引擎的变化吧。</p>
<p>
	最后要做的一件事，就是将上一篇blog的名字做下修改，那篇文章的分析只能对3.0rc0版本有效了，对后续版本无效，已经不能代表3.0的引 擎结构了。事实上NativeActivity是在rc1就被<a href="https://github.com/cocos2d/cocos2d-x/commit/98dfa2c5091cae4ab9e590eabeacca59e46542f8">移除</a>了，这种较大的改动让人始料不急。这么大的改动，这么短时间发布，让人对目前的 3.0引擎，至少是Android版本引擎的质量表示些许担忧啊。不知道3.0正式版中这块的代码会变啥样，拭目以待吧。</p>
<p>	BTW，rc2版本cpp-empty-test在Android 2.3.7模拟器上的帧数在10帧以下，我的Demo也只有5帧，而在4.4版本模拟器上，可以达到40帧，还好还好。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/04/23/changes-in-cocos2dx-3-rc2-for-android/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Hello, Cocos2d-x 3.0rc0</title>
		<link>https://tonybai.com/2014/04/22/hello-cocos2dx-3-rc0/</link>
		<comments>https://tonybai.com/2014/04/22/hello-cocos2dx-3-rc0/#comments</comments>
		<pubDate>Tue, 22 Apr 2014 10:45:33 +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[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[内存计数]]></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=1510</guid>
		<description><![CDATA[Cocos2d-x 3.0版本已经发布了rc2，这让这段时间用熟了Cocos2d-x 2.2.2的我也有些蠢蠢欲动。按照触控科技主创人员在CocoaChina2014大会上的讲解，Cocos2d-x 3.0版本相比2.x版本在各方面都有不错的提升，于是乎就想把手头上的一款习作移植到3.0版本引擎下，看看运行效果如何。不过在移植之前，我先来看看 3.0与2.0相比在整体代码结构以及引擎驱动核心方面到底有哪些变化。一旦搞定这些原理，迁移什么都不是问题了。这里以Cocos2d-x 3.0rc0版的Android平台引擎为例。 一、从NativeActivity开始 Cocos2d-x 2.x版本中，游戏的Main Activity继承于引擎实现的Cocos2dxActivity（见《Hello，Cocos2d-x》），Cocos2dxActivity将一个 GLSurfaceView实例set给Window对象，并在为GLSurfaceView设置Renderer实例时创建Renderer Thread(渲染线程）。而Java代码则通过jni将游戏引擎中的C++代码引入。 Cocos2d-x 3.0版本则进一步摆脱Java束缚，当然也有新渲染器设计方面的考虑，3.0版本直接使用了Android NDK中提供的NativeActivity，我们游戏的主Activity(比如cpp-empty-test中的Cocos2dxActivity) 直接从NativeActivity继承： // tests/cpp-empty-test/proj.android/src/org/cocos2dx/cpp_empty_test/Cocos2dxActivity.java public class Cocos2dxActivity extends NativeActivity { &#160;&#160;&#160; @Override &#160;&#160;&#160; protected void onCreate(Bundle savedInstanceState) { &#160;&#160;&#160;&#160;&#160;&#160;&#160; // TODO Auto-generated method stub &#160;&#160;&#160;&#160;&#160;&#160;&#160; super.onCreate(savedInstanceState); &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#8230; &#8230; &#160;&#160;&#160;&#160;&#160;&#160;&#160; //2.Set the format of window &#160;&#160;&#160;&#160;&#160;&#160;&#160; // getWindow().setFormat(PixelFormat.TRANSLUCENT); &#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/207">rc2</a>，这让这段时间用熟了Cocos2d-x 2.2.2的我也有些蠢蠢欲动。按照<a href="http://www.chukong-inc.com/">触控科技</a>主创人员在CocoaChina2014大会上的讲解，Cocos2d-x 3.0版本相比2.x版本在各方面都有不错的提升，于是乎就想把手头上的一款习作移植到3.0版本引擎下，看看运行效果如何。不过在移植之前，我先来看看 3.0与2.0相比在整体代码结构以及引擎驱动核心方面到底有哪些变化。一旦搞定这些原理，迁移什么都不是问题了。这里以Cocos2d-x 3.0rc0版的Android平台引擎为例。</p>
<p><b>一、从NativeActivity开始</b></p>
<p>Cocos2d-x 2.x版本中，游戏的Main Activity继承于引擎实现的Cocos2dxActivity（见《<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">Hello，Cocos2d-x</a>》），Cocos2dxActivity将一个 GLSurfaceView实例set给Window对象，并在为GLSurfaceView设置Renderer实例时创建Renderer Thread(渲染线程）。而Java代码则通过jni将游戏引擎中的C++代码引入。</p>
<p>Cocos2d-x 3.0版本则进一步摆脱Java束缚，当然也有新渲染器设计方面的考虑，3.0版本直接使用了Android NDK中提供的NativeActivity，我们游戏的主Activity(比如cpp-empty-test中的Cocos2dxActivity) 直接从NativeActivity继承：</p>
<p><font face="Courier New">// tests/cpp-empty-test/proj.android/src/org/cocos2dx/cpp_empty_test/Cocos2dxActivity.java</font><br />
	<font face="Courier New">public class Cocos2dxActivity extends NativeActivity {</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; protected void onCreate(Bundle savedInstanceState) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // TODO Auto-generated method stub<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onCreate(savedInstanceState);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //2.Set the format of window<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // getWindow().setFormat(PixelFormat.TRANSLUCENT);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>可以看出这里的Cocos2dxActivity啥也没做，因此整个Cocos2d-x 3.0游戏的起点就应该是<font face="Courier New">NativeActivity了</font>。</p>
<p>NativeActivity是Android专为使用NDK开发Android应用而实现的一个Activity类，通过使用 NativeActivity，开发者可以最大程度地摆脱Java代码的编写，尽可能的使用C++代码。</p>
<p><b>二、NativeActivity的原理</b></p>
<p>NativeActivity在Android 2.3版本引入，其核心方法依旧是onCreate，我们一起来看一下(省略部分与分析无关的代码)：</p>
<p><font face="Courier New">// NativeActivity.java(Android&nbsp; 4.4.2_r1)</font></p>
<p><font face="Courier New">@Override<br />
	&nbsp;&nbsp;&nbsp; protected void onCreate(Bundle savedInstanceState) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>String libname = &quot;main&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String funcname = &quot;ANativeActivity_onCreate&quot;;</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ActivityInfo ai;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mNativeContentView = new NativeContentView(this);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mNativeContentView.mActivity = this;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setContentView(mNativeContentView);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mNativeContentView.requestFocus();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mNativeContentView.getViewTreeObserver()<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; .addOnGlobalLayoutListener(this);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ai = getPackageManager().getActivityInfo(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getIntent().getComponent(),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PackageManager.GET_META_DATA);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (ai.metaData != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String ln = ai.metaData.getString(META_DATA_LIB_NAME);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (ln != null) libname = ln;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ln = ai.metaData.getString(META_DATA_FUNC_NAME);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (ln != null) funcname = ln;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (PackageManager.NameNotFoundException e) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new RuntimeException(&quot;Error getting activity info&quot;, e);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String path = null;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; File libraryFile = new File(ai.applicationInfo.nativeLibraryDir,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.mapLibraryName(libname));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (libraryFile.exists()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; path = libraryFile.getPath();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (path == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new IllegalArgumentException(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;Unable to find native library: &quot; + libname);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; byte[] nativeSavedState = savedInstanceState != null<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>mNativeHandle = loadNativeCode</b>(path, funcname, Looper.myQueue(),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getAbsolutePath(getFilesDir()), getAbsolutePath(getObbDir()),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getAbsolutePath(getExternalFilesDir(null)),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Build.VERSION.SDK_INT, getAssets(), nativeSavedState);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mNativeHandle == 0) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new IllegalArgumentException(&quot;Unable to load native library: &quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + path);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onCreate(savedInstanceState);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>从NativeActivity的onCreate代码我们大致可以看出，NativeActivity在游戏对应的原生库(.so)中查找名为<font face="Courier New">&quot;ANativeActivity_onCreate&quot;</font>的函数，并执行该函数。其执行 是通过loadNativeCode这个Jni方法实现的。loadNativeCode在 /core/jni/android_app_NativeActivity.cpp中有实现：</p>
<p>&nbsp;/core/jni/android_app_NativeActivity.cpp<br />
	<font face="Courier New">static jint<br />
	loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jobject messageQueue,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jstring internalDataDir, jstring externalDataDir, int sdkVersion,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jobject jAssetMgr, jbyteArray savedState)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; LOG_TRACE(&quot;loadNativeCode_native&quot;);<br />
	&nbsp;&nbsp;&nbsp; const char* pathStr = env-&gt;GetStringUTFChars(path, NULL);<br />
	&nbsp;&nbsp;&nbsp; NativeCode* code = NULL;<br />
	&nbsp;&nbsp;&nbsp; void* handle = dlopen(pathStr, RTLD_LAZY);<br />
	&nbsp;&nbsp;&nbsp; env-&gt;ReleaseStringUTFChars(path, pathStr);<br />
	&nbsp;&nbsp;&nbsp; if (handle != NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const char* funcStr = env-&gt;GetStringUTFChars(funcName, NULL);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; code = new NativeCode(handle, (ANativeActivity_createFunc*)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>dlsym</b>(handle, funcStr));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; env-&gt;ReleaseStringUTFChars(funcName, funcStr);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (code-&gt;createActivityFunc == NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOGW(&quot;ANativeActivity_onCreate not found&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete code;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font><br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; code-&gt;<b>createActivityFunc</b>(code, rawSavedState, rawSavedSize);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (rawSavedState != NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; env-&gt;ReleaseByteArrayElements(savedState, rawSavedState, 0);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; return (jint)code;<br />
	}</font></p>
<p>做过系统编程的朋友想必对dlsym都很熟系，这个函数用来从一个打开的.so中(dlopen)获得某个函数对应的代码地址。 code-&gt;createActivityFunc则是执行这个函数。</p>
<p>我们在强化一下，费了半天劲儿找到并执行的这个函数的名字是：<font face="Courier New"><b>ANativeActivity_onCreate</b></font>。 如果你要使用NativeActivity，你就必须提供一份ANativeActivity_onCreate函数的实现。在该函数的实现中， 你要为Activity注册各种生命周期事件以及其他输入事件的回调函数，比如onStart、onResume、onDestroy等。NDK 官方文档中有详细的说明。</p>
<p>不过这样一来，所有的事件处理均在NativeActivity所在的主线程里执行，为了不阻塞主线程的页面刷新以及交互响应，我们需要将这些回 调函数实现的短小精悍，不能拖泥带水，不能&ldquo;干重活儿&rdquo;。以前使用SDK时，Android SDK提供了AsyncTask, Handler, Runnable, Thread等诸多手段帮助在后台处理一些&ldquo;重量级&rdquo;的事情，但在NDK中，我们该如何处理呢？NDK也为我们提供了一种方案：<font face="Courier New"> android_native_app_glue</font>。</p>
<p>android_native_app_glue大致做了这么几件事：<br />
	1、实现了<font face="Courier New">ANativeActivity_onCreate</font>函数，注册了 Callback函数；<br />
	2、创建一个新的子Thread，用于干重活儿<br />
	3、在Main Thread和新线程之间建立了一个管道，用于Main Thread给新线程传递各种事件，以便后者读取并处理。</p>
<p>可以说<font face="Courier New">native_app_glue</font>的存在，进一步降低了 NativeActivity的使用门槛，否则以上诸事均要有开发人员自行实现。</p>
<p>下面结合源码做简单说明：</p>
<p><font face="Courier New">// android-ndk-r9c/sources/android/native_app_glue/android_native_app_glue.c</font></p>
<p><font face="Courier New">void ANativeActivity_onCreate(ANativeActivity* activity,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void* savedState, size_t savedStateSize) {<br />
	&nbsp;&nbsp;&nbsp; LOGV(&quot;Creating: %p\n&quot;, activity);<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onDestroy = onDestroy;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onStart = onStart;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onResume = onResume;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onSaveInstanceState = onSaveInstanceState;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onPause = onPause;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onStop = onStop;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onConfigurationChanged = onConfigurationChanged;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onLowMemory = onLowMemory;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onWindowFocusChanged = onWindowFocusChanged;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onNativeWindowCreated = onNativeWindowCreated;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onNativeWindowDestroyed = onNativeWindowDestroyed;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onInputQueueCreated = onInputQueueCreated;<br />
	&nbsp;&nbsp;&nbsp; activity-&gt;callbacks-&gt;onInputQueueDestroyed = onInputQueueDestroyed;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; activity-&gt;instance = <b>android_app_create</b>(activity, savedState, savedStateSize);<br />
	}</font></p>
<p><font face="Courier New">static struct android_app* android_app_create(ANativeActivity* activity,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void* savedState, size_t savedStateSize) {<br />
	&nbsp;&nbsp;&nbsp; struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app));<br />
	&nbsp;&nbsp;&nbsp; memset(android_app, 0, sizeof(struct android_app));<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;activity = activity;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; pthread_mutex_init(&amp;android_app-&gt;mutex, NULL);<br />
	&nbsp;&nbsp;&nbsp; pthread_cond_init(&amp;android_app-&gt;cond, NULL);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; int msgpipe[2];<br />
	&nbsp;&nbsp;&nbsp; if (<b>pipe(msgpipe)</b>) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOGE(&quot;could not create pipe: %s&quot;, strerror(errno));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return NULL;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;msgread = msgpipe[0];<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;msgwrite = msgpipe[1];</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; pthread_attr_t attr;<br />
	&nbsp;&nbsp;&nbsp; pthread_attr_init(&amp;attr);<br />
	&nbsp;&nbsp;&nbsp; pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_DETACHED);<br />
	&nbsp;&nbsp;&nbsp; <b>pthread_create</b>(&amp;android_app-&gt;thread, &amp;attr, <b>android_app_entry</b>, android_app);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // Wait for thread to start.<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_lock(&amp;android_app-&gt;mutex);<br />
	&nbsp;&nbsp;&nbsp; while (!android_app-&gt;running) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_cond_wait(&amp;android_app-&gt;cond, &amp;android_app-&gt;mutex);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_unlock(&amp;android_app-&gt;mutex);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return android_app;<br />
	}</font></p>
<p>上面的android_app_create创建了子线程，建立了两个线程的pipe，新线程的入口是<font face="Courier&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;<br />
      New">android_app_entry</font>：</p>
<p><font face="Courier New">static void* android_app_entry(void* param) {<br />
	&nbsp;&nbsp;&nbsp; struct android_app* android_app = (struct android_app*)param;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; android_app-&gt;config = AConfiguration_new();<br />
	&nbsp;&nbsp;&nbsp; AConfiguration_fromAssetManager(android_app-&gt;config,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android_app-&gt;activity-&gt;assetManager);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; print_cur_config(android_app);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; android_app-&gt;cmdPollSource.id = LOOPER_ID_MAIN;<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;cmdPollSource.app = android_app;<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;cmdPollSource.process = process_cmd;<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;inputPollSource.id = LOOPER_ID_INPUT;<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;inputPollSource.app = android_app;<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;inputPollSource.process = process_input;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ALooper* looper = ALooper_prepare(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);<br />
	&nbsp;&nbsp;&nbsp; ALooper_addFd(looper, android_app-&gt;msgread,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOOPER_ID_MAIN,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ALOOPER_EVENT_INPUT, NULL,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;android_app-&gt;cmdPollSource);<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;looper = looper;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; pthread_mutex_lock(&amp;android_app-&gt;mutex);<br />
	&nbsp;&nbsp;&nbsp; android_app-&gt;running = 1;<br />
	&nbsp;&nbsp;&nbsp; pthread_cond_broadcast(&amp;android_app-&gt;cond);<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_unlock(&amp;android_app-&gt;mutex);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <b>android_main(android_app);</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; android_app_destroy(android_app);<br />
	&nbsp;&nbsp;&nbsp; return NULL;<br />
	}</font></p>
<p>新线程建立了事件处理设施(looper)，并通知主线程（通过条件变量）app正式开始运行了(running = 1)，之后进入<font face="Courier New"><b>android_main</b></font>。</p>
<p>Cocos2d-x 3.0采用的就是<font face="Courier New">android_native_app_glue</font>这 种方案，而<font face="Courier New">android_main</font>则是Cocos2d-x 3.0引擎层的入口。</p>
<p><font face="Courier New">//cocos/2d/platform/android/Android.mk</font></p>
<p><font face="Courier New">LOCAL_WHOLE_STATIC_LIBRARIES&nbsp;&nbsp;&nbsp; := <b>android_native_app_glue</b> cocos_png_static cocos_jpeg_static cocos_tiff_static cocos_webp_static<br />
	$(call import-module,<b>android/native_app_glue</b>)</font></p>
<p>三<b>、走进引擎</b></p>
<p>从android_main函数开始，我们就进入了Cocos2d-x 3.0引擎的范畴。android_main函数比较长，我们挑重点说：</p>
<p><font face="Courier New">cocos/2d/platform/android/nativeactivity.cpp</font></p>
<p><font face="Courier New">void android_main(struct android_app* state) {<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; memset(&amp;engine, 0, sizeof(engine));<br />
	&nbsp;&nbsp;&nbsp; state-&gt;userData = &amp;engine;<br />
	&nbsp;&nbsp;&nbsp; state-&gt;onAppCmd = engine_handle_cmd;<br />
	&nbsp;&nbsp;&nbsp; state-&gt;onInputEvent = engine_handle_input;<br />
	&nbsp;&nbsp;&nbsp; state-&gt;inputPollSource.process = process_input;<br />
	&nbsp;&nbsp;&nbsp; engine.app = <b>state</b>;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // Prepare to monitor accelerometer<br />
	&nbsp; &nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; while (1) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Read all pending events.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int ident;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int events;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct android_poll_source* source;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // If not animating, we will block forever waiting for events.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // If animating, we loop until all events are read, then continue<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // to draw the next frame of animation.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while ((ident=<b>ALooper_pollAll</b>(engine.animating ? 0 : -1,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL, &amp;events,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (void**)&amp;source)) &gt;= 0) {</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Process this event.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (source != NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; source-&gt;process(state, source);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Check if we are exiting.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (state-&gt;destroyRequested != 0) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; engine_term_display(&amp;engine);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memset(&amp;engine, 0, sizeof(engine));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s_methodInitialized = false;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>return</b>;<br />
	&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; if (engine.animating) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Done with events; draw next animation frame.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; engine.state.angle += .01f;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (engine.state.angle &gt; 1) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; engine.state.angle = 0;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Drawing is throttled to the screen update rate, so there<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // is no need to do timing here.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOG_RENDER_DEBUG(&quot;android_main : engine.animating&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>engine_draw_frame(&amp;engine);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOG_RENDER_DEBUG(&quot;android_main : !engine.animating&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>android_main有些像cocos2d-x 2.2.2中GLThread的guardedRun方法，里面基本上就是一个死循环(<font face="Courier New">while (1)</font>)，简化后的逻辑大致如下：</p>
<p><font face="Courier New">void android_main(struct android_app* state) {<br />
	&nbsp;&nbsp;&nbsp; while (1) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Do Main Thread Event Processing &amp; Input Event Processing;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (engine.animating) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // draw next animation frame 画下一帧<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; engine_draw_frame(&amp;engine);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>而引擎的初始化和帧渲染就是在这个死循环中一步步完成的。</p>
<p>引擎的初始化始于APP_CMD_INIT_WINDOW事件，在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; struct engine* engine = (struct engine*)app-&gt;userData;<br />
	&nbsp;&nbsp;&nbsp; switch (cmd) {<br />
	&nbsp;&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; // 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 = <b>engine_init_display</b>(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; cocos2d::JniHelper::setJavaVM(app-&gt;activity-&gt;vm);<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);</font></p>
<p><font face="Courier New">&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; &quot;org/cocos2dx/lib/Cocos2dxHelper&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; &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; &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);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>cocos_init</b>(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; <b>engine-&gt;animating = 1;</b><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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>当收到主线程通知的窗口建立事件时，engine_handle_cmd的APP_CMD_INIT_WINDOW事件处理函数主要做了两件事：<br />
	1、调用engine_init_display初始化EGL；<br />
	2、调用cocos_init初始化引擎的主要角色。</p>
<p>这里进入到引擎初始化的前提是&ldquo;<font face="Courier New">engine-&gt;app-&gt;window != NULL</font>&rdquo;。而app-&gt;window的设置是在native_app_glue中进行的，大致流程是：<br />
	Main Thread：<br />
	<font face="Courier New">onNativeWindowCreated<br />
	&nbsp;&nbsp;&nbsp; -&gt; android_app_set_window<br />
	&nbsp;&nbsp;&nbsp; -&gt; android_app-&gt;pendingWindow = window;<br />
	&nbsp;&nbsp;&nbsp; -&gt; android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);</font></p>
<p>Sub Thread:<br />
	<font face="Courier New">process_cmd<br />
	&nbsp;&nbsp;&nbsp; -&gt; android_app_pre_exec_cmd<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; -&gt; android_app-&gt;window = android_app-&gt;pendingWindow;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; engine_handle_cmd(即app-&gt;onAppCmd回调)，此时android_app-&gt;window != NULL</font>。</p>
<p><b>四、引擎初始化</b></p>
<p>前面说过，引擎初始化包括两部分：engine_init_display和cocos_init，我们分别来说说。</p>
<p>1、<b><font face="Courier New">engine_init_display</font></b></p>
<p><font face="Courier New">//cocos/2d/platform/android/nativeactivity.cpp</font></p>
<p><font face="Courier New">static cocos_dimensions engine_init_display(struct engine* engine)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; cocos_dimensions r;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; EGLDisplay display = <b>eglGetDisplay</b>(EGL_DEFAULT_DISPLAY);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <b>eglInitialize</b>(display, 0, 0);<br />
	&nbsp;&nbsp;&nbsp; <b>eglChooseConfig</b>(display, attribs, &amp;config, 1, &amp;numConfigs);<br />
	&nbsp;&nbsp;&nbsp; <b>eglGetConfigAttri</b>b(display, config, EGL_NATIVE_VISUAL_ID, &amp;format);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ANativeWindow_setBuffersGeometry(engine-&gt;app-&gt;window, 0, 0, format);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; surface = <b>eglCreateWindowSurface</b>(display, config, engine-&gt;app-&gt;window, NULL);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; const EGLint eglContextAttrs[] =<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EGL_CONTEXT_CLIENT_VERSION, 2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EGL_NONE<br />
	&nbsp;&nbsp;&nbsp; };</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; context = <b>eglCreateContext</b>(display, config, NULL, eglContextAttrs);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (<b>eglMakeCurrent</b>(display, surface, surface, context) == EGL_FALSE) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOGW(&quot;Unable to eglMakeCurrent&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return r;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; eglQuerySurface(display, surface, EGL_WIDTH, &amp;w);<br />
	&nbsp;&nbsp;&nbsp; eglQuerySurface(display, surface, EGL_HEIGHT, &amp;h);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; engine-&gt;display = display;<br />
	&nbsp;&nbsp;&nbsp; engine-&gt;context = context;<br />
	&nbsp;&nbsp;&nbsp; engine-&gt;surface = surface;<br />
	&nbsp;&nbsp;&nbsp; engine-&gt;width = w;<br />
	&nbsp;&nbsp;&nbsp; engine-&gt;height = h;<br />
	&nbsp;&nbsp;&nbsp; engine-&gt;state.angle = 0;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; r.w = w;<br />
	&nbsp;&nbsp;&nbsp; r.h = h;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return r;<br />
	}</font></p>
<p>这段代码应该是典型的EGL初始化流程，几乎每本有关EGL或opengl es的教程中都会有类似描述。个人对opengl以及EGL了解不多，从一些书籍或网络资料中大致得到如下一些理解：</p>
<p>首先，Android下每个Activity都会有对应窗口(Window)以及View，View就是显示在屏幕上的内容。NativeActivity初始化时设置了一个NativeContentView（View的子类）：</p>
<p><font face="Courier New">// android/app/NativeActivity.java</font></p>
<p><font face="Courier New">static class NativeContentView extends View {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NativeActivity mActivity;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public NativeContentView(Context context) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super(context);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public NativeContentView(Context context,<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; AttributeSet attrs) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super(context, attrs);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">protected void onCreate(Bundle savedInstanceState) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mNativeContentView = new NativeContentView(this);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mNativeContentView.mActivity = this;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setContentView(mNativeContentView);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p>但Cocos2d-x显然不会使用这个View，而是直接在窗口用opengl绘图。EGL大致有三个元素：Context、Display以及 Surface。它们之间的大致关系是：EGL通过Context指挥opengl在Surface画布（一种帧缓冲FrameBuffer）上绘制，绘 制完成后再Swap到窗口的Display显示器上去，这样我们就能看到绘制的图像了。2D游戏引擎的渲染器用的都是这个原理。关于上述EGL初始化的具 体调用含义这里就不赘述了，大家如要深入了解，可以找本OpenGL ES相关的书去看看。</p>
<p><b>2、cocos_init</b></p>
<p><font face="Courier New">static void cocos_init(cocos_dimensions d, struct android_app* app)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; LOGI(&quot;cocos_init(&#8230;)&quot;);<br />
	&nbsp;&nbsp;&nbsp; pthread_t thisthread = pthread_self();<br />
	&nbsp;&nbsp;&nbsp; LOGI(&quot;pthread_self() = %X&quot;, thisthread);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; cocos2d::FileUtilsAndroid::setassetmanager(app-&gt;activity-&gt;assetManager);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; auto director = cocos2d::Director::getInstance();<br />
	&nbsp;&nbsp;&nbsp; auto glview = director-&gt;getOpenGLView();<br />
	&nbsp;&nbsp;&nbsp; if (!glview)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; glview = cocos2d::GLView::create(&quot;Android app&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; glview-&gt;setFrameSize(d.w, d.h);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; director-&gt;setOpenGLView(glview);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos_android_app_init(app);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::Application::getInstance()-&gt;run();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; else<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::GL::invalidateStateCache();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::ShaderCache::getInstance()-&gt;reloadDefaultShaders();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::DrawPrimitives::init();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::VolatileTextureMgr::reloadAllTextures();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::EventCustom foregroundEvent(EVENT_COME_TO_FOREGROUND);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; director-&gt;getEventDispatcher()-&gt;dispatchEvent(&amp;foregroundEvent);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; director-&gt;setGLDefaultValues();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>分析过Cocos2d-x 2.x版本引擎结构的朋友对这段代码一定比较眼熟，没错，在2.x版本中这段代码是放在游戏项目的proj.android/jni/下的，在jni方法<font face="Courier New">Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit</font>中我们可以看到类似代码。</p>
<p>在cocos_init中我们看到了Cocos2d-x游戏引擎的一个重要角色Director的创建和初始化：</p>
<p>auto director = cocos2d::Director::getInstance();</p>
<p><font face="Courier New">//cocos/2d/CCDirector.cpp</font></p>
<p><font face="Courier New">bool Director::init(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; setDefaultValues();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <b>_openGLView = nullptr;</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; _contentScaleFactor = 1.0f;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // scheduler<br />
	&nbsp;&nbsp;&nbsp; _scheduler = new Scheduler();<br />
	&nbsp;&nbsp;&nbsp; // action manager<br />
	&nbsp;&nbsp;&nbsp; _actionManager = new ActionManager();<br />
	&nbsp;&nbsp;&nbsp; _scheduler-&gt;scheduleUpdate(_actionManager,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; Scheduler::PRIORITY_SYSTEM, false);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; _eventDispatcher = new EventDispatcher();<br />
	&nbsp;&nbsp;&nbsp; _eventAfterDraw = new EventCustom(EVENT_AFTER_DRAW);<br />
	&nbsp;&nbsp;&nbsp; _eventAfterDraw-&gt;setUserData(this);<br />
	&nbsp;&nbsp;&nbsp; _eventAfterVisit = new EventCustom(EVENT_AFTER_VISIT);<br />
	&nbsp;&nbsp;&nbsp; _eventAfterVisit-&gt;setUserData(this);<br />
	&nbsp;&nbsp;&nbsp; _eventAfterUpdate = new EventCustom(EVENT_AFTER_UPDATE);<br />
	&nbsp;&nbsp;&nbsp; _eventAfterUpdate-&gt;setUserData(this);<br />
	&nbsp;&nbsp;&nbsp; _eventProjectionChanged = new EventCustom(EVENT_PROJECTION_CHANGED);<br />
	&nbsp;&nbsp;&nbsp; _eventProjectionChanged-&gt;setUserData(this);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //init TextureCache<br />
	&nbsp;&nbsp;&nbsp; initTextureCache();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <b>_renderer = new Renderer;</b><br />
	&nbsp;&nbsp;&nbsp; _console = new Console;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return true;<br />
	}</font></p>
<p>诸多引擎基础设施都是在Director::init中被初始化的，这里最重要的就是Renderer了，这个就是Cocos2d-x 3.0新实现的渲染器。</p>
<p>Director初始化后，_openGLView == NULL，后续cocos_init调用cocos2d::GLView::create(&quot;Android app&quot;)创建GLView，并set到Director中。这个View似乎更多是用来辅助处理屏幕适配以及触屏事件处理的。</p>
<p>cocos_init最后调用了cocos_android_app_init(app)，这个函数实现在你的游戏工程中，以cpp-empty- test为例，在tests/cpp-empty-test/proj.android/jni/main.cpp中我们看到了该函数的实现：</p>
<p><font face="Courier New">void cocos_android_app_init (struct android_app* app) {<br />
	&nbsp;&nbsp;&nbsp; LOGD(&quot;cocos_android_app_init&quot;);<br />
	&nbsp;&nbsp;&nbsp; AppDelegate *pAppDelegate = new AppDelegate();<br />
	}</font></p>
<p>我们已经进入游戏业务逻辑层了。和Cocos2d-x 2.x版本一样，Classes/AppDelegate.cpp中的 AppDelegate::applicationDidFinishLaunching依旧是我们初始化我们游戏业务逻辑层的入口。而这一入口函数是在 cocos_init中的cocos2d::Application::getInstance()-&gt;run()调用时被调用的。</p>
<p><font face="Courier New">// /cocos/2d/platform/android/CCApplication.cpp<br />
	int Application::run()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; // Initialize instance and cocos2d.<br />
	&nbsp;&nbsp;&nbsp; if (! applicationDidFinishLaunching())<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return -1;<br />
	}</font></p>
<p>至此，我们又回到了熟悉的游戏业务逻辑层，也就是你的游戏project中。</p>
<p><b>五、到底发生了哪些重要变化</b></p>
<p>之前听说Cocos2d-x 3.0引擎的一个重要改造就是尽可能利用多线程，利用硬件的多核来提升游戏渲染性能。这给我的错觉是Renderer Thread完全独立出去，只负责渲染。但实际发布的版本似乎并不是这么回事。Cocos2d-x 2.x版本是两个线程，3.0版本依旧是两个线程，从cpp-empty-test运行的logcat日志也能看出来：</p>
<p><font face="Courier New">04-21 07:36:52.779&nbsp; <b>1522&nbsp; 1522</b> D dalvikvm: Late-enabling CheckJNI<br />
	04-21 07:36:52.783&nbsp; 1522&nbsp; 1522 I dalvikvm: Enabling JNI app bug workarounds for target SDK version 9&#8230;<br />
	04-21 07:36:52.783&nbsp;&nbsp; 561&nbsp;&nbsp; 573 I ActivityManager: Start proc org.cocos2dx.cpp_empty_test for activity org.cocos2dx.cpp_empty_test/.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cocos2dxActivity: pid=1522 uid=10056 gids={50056}<br />
	04-21 07:36:53.047&nbsp; <b>1522&nbsp; 1535</b> D libEGL&nbsp; : loaded /system/lib/egl/libEGL_genymotion.so<br />
	04-21 07:36:53.047&nbsp; 1522&nbsp; 1535 D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : HostConnection::get() New Host Connection established 0xb918a7c8, tid 1535<br />
	04-21 07:36:53.071&nbsp; 1522&nbsp; 1535 D libEGL&nbsp; : loaded /system/lib/egl/libGLESv1_CM_genymotion.so<br />
	04-21 07:36:53.083&nbsp; 1522&nbsp; 1535 D libEGL&nbsp; : loaded /system/lib/egl/libGLESv2_genymotion.so<br />
	04-21 07:36:53.143&nbsp; 1522&nbsp; 1535 D JniHelper: JniHelper::setJavaVM(0xb903a730), pthread_self() = B9180250<br />
	04-21 07:36:53.155&nbsp; 1522&nbsp; 1535 I cocos2dx/nativeactivity.cpp: cocos_init(&#8230;)<br />
	&#8230; &#8230;<br />
	04-21 07:36:53.395&nbsp; 1522&nbsp; 1535 D main&nbsp;&nbsp;&nbsp; : cocos_android_app_init<br />
	04-21 07:36:53.419&nbsp; 1522&nbsp; 1535 D CCFileUtilsAndroid.cpp: relative path = ipadhd/CloseNormal.png<br />
	04-21 07:36:53.427&nbsp; 1522&nbsp; 1535 D CCFileUtilsAndroid.cpp: relative path = ipadhd/CloseSelected.png<br />
	04-21 07:36:53.439&nbsp; 1522&nbsp; 1535 D cocos2d-x debug info: cocos2d: fullPathForFilename: No file found at Arial. Possible missing file.<br />
	04-21 07:36:53.447&nbsp; 1522&nbsp; 1535 D dalvikvm: GC_FOR_ALLOC freed 64K, 4% free 3455K/3584K, paused 7ms, total 7ms<br />
	04-21 07:36:53.467&nbsp; 1522&nbsp; 1535 D CCFileUtilsAndroid.cpp: relative path = ipadhd/HelloWorld.png<br />
	04-21 07:36:54.003&nbsp; 1522&nbsp; 1535 I cocos2dx/nativeactivity.cpp: engine_draw_frame(&#8230;)<br />
	04-21 07:36:54.003&nbsp; 1522&nbsp; 1535 I cocos2dx/nativeactivity.cpp: pthread_self() = B9180250<br />
	04-21 07:36:54.003&nbsp; <b>1522&nbsp; 1535</b> I cocos2dx/nativeactivity.cpp: engine_draw_frame : just called cocos&#39; mainLoop()<br />
	04-21 07:36:54.051&nbsp; 1522&nbsp; 1535 I cocos2dx/nativeactivity.cpp: android_main : engine.animating<br />
	04-21 07:36:54.051&nbsp; 1522&nbsp; 1535 I cocos2dx/nativeactivity.cpp: engine_draw_frame(&#8230;)<br />
	04-21 07:36:54.051&nbsp; 1522&nbsp; 1535 I cocos2dx/nativeactivity.cpp: pthread_self() = B9180250</font><br />
	&#8230; &#8230;<br />
	<font face="Courier New">04-21 07:36:56.507&nbsp; <b>1522&nbsp; 1535</b> I Process : Sending signal. PID: 1522 SIG: 9</font></p>
<p>可以看出NativeActivity所在的主线程号为1522，但绝大多数工作都在1535这个渲染线程，也就是native_app_glue库中创 建的那个线程。Scene Graph管理和Renderer::render依旧都在该Thread内完成，这似乎也很难有效并充分的利用起多核的效能啊。cpp-empty- test在我的genymotion模拟器上跑时，帧数始终在50帧左右。</p>
<p>不过Renderer的确是重写的，并且将2.x版本中Scene Graph的管理与渲染之间的耦合解耦开来。每帧按Scene Graph Visit Node时并不真正执行渲染，而只是构造DrawCommand，并插入到Renderer的DrawCommand队列中：</p>
<p><font face="Courier New">// draw</font></p>
<p><font face="Courier New">void Sprite::draw(Renderer *renderer, const kmMat4 &amp;transform, bool transformUpdated)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; // Don&#39;t do calculate the culling if the transform was not updated<br />
	&nbsp;&nbsp;&nbsp; _insideBounds = transformUpdated ? isInsideBounds() : _insideBounds;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if(_insideBounds)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _quadCommand.init(_globalZOrder, _texture-&gt;getName(), _shaderProgram, _blendFunc, &amp;_quad, 1, transform);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; renderer-&gt;addCommand(&amp;_quadCommand);<br />
	#if CC_SPRITE_DEBUG_DRAW<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _customDebugDrawCommand.init(_globalZOrder);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; renderer-&gt;addCommand(&amp;_customDebugDrawCommand);<br />
	#endif //CC_SPRITE_DEBUG_DRAW<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>在Director::drawScene尾部我们能看到真正的渲染动作render()被调用：</p>
<p><font face="Courier New">void Director::drawScene()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; // draw the scene<br />
	&nbsp;&nbsp;&nbsp; if (_runningScene)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _runningScene-&gt;<b>visit</b>(_renderer, identity, false);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _eventDispatcher-&gt;dispatchEvent(_eventAfterVisit);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <b>_renderer-&gt;render();</b><br />
	&nbsp;&nbsp;&nbsp; _eventDispatcher-&gt;dispatchEvent(_eventAfterDraw);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmGLPopMatrix();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; _totalFrames++;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // swap buffers<br />
	&nbsp;&nbsp;&nbsp; if (_openGLView)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>_openGLView-&gt;swapBuffers();</b><br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (_displayStats)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; calculateMPF();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>这里我们看到了 _openGLView-&gt;swapBuffers()，但该方法的具体实现为空，真正swapBuffers调用在外层：</p>
<p><font face="Courier New">static void engine_draw_frame(struct engine* engine)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; LOG_RENDER_DEBUG(&quot;engine_draw_frame(&#8230;)&quot;);<br />
	&nbsp;&nbsp;&nbsp; pthread_t thisthread = pthread_self();<br />
	&nbsp;&nbsp;&nbsp; LOG_RENDER_DEBUG(&quot;pthread_self() = %X&quot;, thisthread);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (engine-&gt;display == NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // No display.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOGW(&quot;engine_draw_frame : No display.&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; dispatch_pending_runnables();<br />
	&nbsp;&nbsp;&nbsp; <b>cocos2d::Director::getInstance()-&gt;mainLoop();</b><br />
	&nbsp;&nbsp;&nbsp; LOG_RENDER_DEBUG(&quot;engine_draw_frame : just called cocos&#39; mainLoop()&quot;);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; /* // Just fill the screen with a color. */<br />
	&nbsp;&nbsp;&nbsp; /* glClearColor(((float)engine-&gt;state.x)/engine-&gt;width, engine-&gt;state.angle, */<br />
	&nbsp;&nbsp;&nbsp; /*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((float)engine-&gt;state.y)/engine-&gt;height, 1); */<br />
	&nbsp;&nbsp;&nbsp; /* glClear(GL_COLOR_BUFFER_BIT); */</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (s_pfEditTextCallback &amp;&amp; editboxText)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s_pfEditTextCallback(editboxText, s_ctx);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(editboxText);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; editboxText = NULL;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <b>eglSwapBuffers(engine-&gt;display, engine-&gt;surface);</b><br />
	}</font></p>
<p>还记得android_main中的&ldquo;死循环&rdquo;么？那个死循环在每一帧都会调用engine_draw_frame方法，而这恰是<b>整个Cocos2d-x 3.0引擎的驱动中心</b>。</p>
<p>通过汇集各个Node的DrawCommand而不是直接Draw，新渲染器可以做一些优化，比如Batch Renderer等。这在上一版本引擎中较难实现，或者只能显式的通过CCSpriteBatchNode实现。更多的好处可以参考官方说明，或待日后使 用引擎时挖掘。</p>
<p><b>六、其他</b></p>
<p>Cocos2d-x 3.0引擎的C++部分采用了<a href="http://en.wikipedia.org/wiki/C++11">C++ 11</a>标准中的语法，因此如果你要编译Linux版本游戏，你需要升级你的gcc编译器到4.7以上版本。但如果只构建Android 游戏，Android NDK(r9c以后版本)早为我们准备好了arm和x86平台的4.8版本的g++编译器了。</p>
<p>Cocos2d-x 3.0的内存管理依旧沿用内存计数机制，如果你理解了2.x版本的内存管理，理解3.0版本应该不会有太大问题。</p>
<p><b>七、参考资料</b><br />
	&nbsp;<br />
	&nbsp; &#8211; Android NDK源码（r9c)<br />
	&nbsp; -&nbsp; Cocos2d-x 3.0rc1源码<br />
	&nbsp; &#8211; Android SDK源码（4.4.2_r1)<br />
	&nbsp; &#8211; 《<a href="http://book.douban.com/subject/24672232">Android Native Development Kit Cookbook &#8212; A step-by-step tutorial with more than 60 concise recipes on Android NDK development skills</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/04/22/hello-cocos2dx-3-rc0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ShareSDK Cocos2d-x专用组件的一个Bug</title>
		<link>https://tonybai.com/2014/04/17/a-bug-from-sharesdk-componet-for-cocos2dx/</link>
		<comments>https://tonybai.com/2014/04/17/a-bug-from-sharesdk-componet-for-cocos2dx/#comments</comments>
		<pubDate>Wed, 16 Apr 2014 21:38:02 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Game-Engine]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[MobileGame]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[ShareSDK]]></category>
		<category><![CDATA[Wechat]]></category>
		<category><![CDATA[weibo]]></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=1504</guid>
		<description><![CDATA[近期研究了一下Game App做社交分享，最后选择了ShareSDK来集成，不仅是因为ShareSDK支持国内外主流社交平台，更重要的是ShareSDK提供了专门的 cocos2d-x集成方案，有专门的文档和代码Demo供开发者参考。 文档中提到了三种集成方式：纯Java方式、plugin-x方式以及Cocos2d-x专用组件方式，这里选择了ShareSDK Cocos2d-x专用组件（v2.3.7版本)的方式。按照文档中描述的步骤进行的相对顺利，在各个社交平台的appkey生效后，我们对demo app进行了测试，居然发现app经常随机性的崩溃，有时甚至是每次都崩溃，经过深入分析，发现这是ShareSDK Cocos2d-x专用组件的一个严重Bug，下面详细说明一下Bug的产生原因以及Fix方法。 一、App崩溃的场景和代码位置 发生崩溃的场景如下： &#160;&#160;&#160; App Demo中有一个&#34;Share&#34;按钮，点击该按钮，App Demo向已经授权的社交平台分享一些Test Content，而App Demo就在收到分享结果应答时发生了崩溃。 代码位置大致如下： void AppDemo::onShareClick(CCObject* sender) { &#160;&#160;&#160; &#8230; &#8230; &#160;&#160;&#160; C2DXShareSDK::showShareMenu(NULL, content, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CCPointMake(100, 100), &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; C2DXMenuArrowDirectionLeft, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; shareResultHandler); } void shareResultHandler(C2DXResponseState state, C2DXPlatType platType, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CCDictionary *shareInfo, CCDictionary *error) { &#160;&#160;&#160; switch (state) { &#160;&#160;&#160;&#160;&#160;&#160;&#160; case C2DXResponseStateSuccess: &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CCLog(&#34;Share Ok&#34;); [...]]]></description>
			<content:encoded><![CDATA[<p>近期研究了一下Game App做社交分享，最后选择了<a href="http://www.sharesdk.cn">ShareSDK</a>来集成，不仅是因为ShareSDK支持国内外主流社交平台，更重要的是ShareSDK提供了专门的<a href="http://www.cocos2d-x.org"> cocos2d-x</a>集成方案，有专门的<a href="http://wiki.sharesdk.cn/cocos2d-x%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E6%8C%87%E5%8D%97">文档</a>和<a href="https://github.com/ShareSDKPlatform/C2DXShareSDKSample">代码Demo</a>供开发者参考。</p>
<p>文档中提到了三种集成方式：纯Java方式、plugin-x方式以及Cocos2d-x专用组件方式，这里选择了ShareSDK Cocos2d-x专用组件（v2.3.7版本)的方式。按照文档中描述的步骤进行的相对顺利，在各个社交平台的appkey生效后，我们对demo app进行了测试，居然发现app经常随机性的崩溃，有时甚至是每次都崩溃，经过深入分析，发现这是ShareSDK Cocos2d-x专用组件的一个严重Bug，下面详细说明一下Bug的产生原因以及Fix方法。</p>
<p><b>一、App崩溃的场景和代码位置</b></p>
<p>发生崩溃的场景如下：<br />
	&nbsp;&nbsp;&nbsp; App Demo中有一个&quot;Share&quot;按钮，点击该按钮，App Demo向已经授权的社交平台分享一些Test Content，而App Demo就在收到分享结果应答时发生了崩溃。</p>
<p>代码位置大致如下：</p>
<p><font face="Courier New">void AppDemo::onShareClick(CCObject* sender)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; C2DXShareSDK::showShareMenu(NULL, 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; CCPointMake(100, 100),<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; C2DXMenuArrowDirectionLeft,<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; <b>shareResultHandler</b>);<br />
	}</font></p>
<p><font face="Courier New">void shareResultHandler(C2DXResponseState state, C2DXPlatType platType,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCDictionary *shareInfo, CCDictionary *error)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; switch (state) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case C2DXResponseStateSuccess:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCLog(&quot;Share Ok&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case C2DXResponseStateFail:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCLog(&quot;Share Failed&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>崩溃的位置大致就在回调<font face="Courier New">shareResultHandler</font>前后的某个位 置，比较随机。</p>
<p><b>二、现象分析</b></p>
<p>通过查看Eclipse logcat窗口的调试日志，我们发现一些规律，一些在&ldquo;Share Ok后的崩溃打印出如下日志：</p>
<p><font face="Courier New">04-16 01:28:33.890: D/cocos2d-x debug info(1748): Share Ok<br />
	04-16 01:28:34.090: D/cocos2d-x debug info(1748): Assert failed: reference count should greater than 0<br />
	04-16 01:28:34.090: E/cocos2d-x assert(1748): /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/temp/AppDemo/proj.android/../../../../../cocos2dx/cocoa/CCObject.cpp function:release line:81<br />
	04-16 01:28:34.130: A/libc(1748): Fatal signal 11 (SIGSEGV) at 0&#215;00000003 (code=1), thread 1829 (Thread-122)</font></p>
<p>猜测一下，似乎是某个CCObject在真正Release前已经被释放了，然后后续被引用时触发内存非法访问。Cocos2d-x采用的是内存 计数的内存管理机制，在我的《<a href="http://tonybai.com/2014/03/18/cocos2dx-memory-management/">Cocos2d-x内存管理-绕不过去的坎</a>》一文中有描述。了解Cocos2d-x的内存管理机制是理解这个Bug 的前提条件。</p>
<p><b>三、原因分析</b></p>
<p>看来不得不挖掘一下ShareSDK组件的代码了。AppDemo中ShareSDK组件的代码分为两个部分：AppDemo/Classes /C2DXShareSDK和AppDemo/proj.android/src/cn/sharesdk。前者是C++代码，后面则是Java 代码，两者通过jni调用联系在一起。我们重点来找出分享应答返回来时的关键联系。</p>
<p>集成ShareSDK的Cocos2d-x程序会在主Activity的onCreate方法中调用<font face="Courier&lt;br /&gt;&lt;br /&gt;<br />
      New">ShareSDKUtils.prepare()</font>;</p>
<p>我们来看看prepare方法的实现：</p>
<p><font face="Courier New">//AppDemo/proj.android/src/cn/sharesdk/ShareSDKUtils.java</font></p>
<p><font face="Courier New">public class ShareSDKUtils {<br />
	&nbsp;&nbsp;&nbsp; private static boolean DEBUG = true;<br />
	&nbsp;&nbsp;&nbsp; private static Context context;<br />
	&nbsp;&nbsp;&nbsp; private static PlatformActionListener paListaner;<br />
	&nbsp;&nbsp;&nbsp; private static Hashon hashon;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; public static void prepare() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UIHandler.prepare();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context = Cocos2dxActivity.getContext().getApplicationContext();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hashon = new Hashon();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; final Callback cb = new Callback() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public boolean handleMessage(Message msg) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>onJavaCallback</b>((String) msg.obj);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<br />
	&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; paListaner = new PlatformActionListener() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onComplete(Platform platform, int action, HashMap&lt;String, Object&gt; res) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (DEBUG) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;onComplete&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(res == null ? &quot;&quot; : res.toString());<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; HashMap&lt;String, Object&gt; map = new HashMap&lt;String, Object&gt;();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; map.put(&quot;platform&quot;, ShareSDK.platformNameToId(platform.getName()));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; map.put(&quot;action&quot;, action);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; map.put(&quot;status&quot;, 1); // Success = 1, Fail = 2, Cancel = 3<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; map.put(&quot;res&quot;, res);<br />
	&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; msg.obj = hashon.fromHashMap(map);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>UIHandler.sendMessage(msg, cb);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p>可以看出监听Complete事件的listener将message的处理都交给了cb，而cb调用了<font face="Courier&lt;br /&gt;&lt;br /&gt;<br />
      New">onJavaCallback</font>方法。</p>
<p><font face="Courier New">onJavaCallback</font>方法是jni导出的方法，它的实现在 AppDemo/Classes/C2DXShareSDK/Android/ShareSDKUtils.cpp里面。</p>
<p><font face="Courier New">JNIEXPORT void JNICALL Java_cn_sharesdk_ShareSDKUtils_onJavaCallback<br />
	&nbsp; (JNIEnv * env, jclass thiz, jstring resp) {<br />
	&nbsp;&nbsp;&nbsp; CCJSONConverter* json = CCJSONConverter::sharedConverter();<br />
	&nbsp;&nbsp;&nbsp; const char* ccResp = env-&gt;GetStringUTFChars(resp, JNI_FALSE);<br />
	&nbsp;&nbsp;&nbsp; CCLog(&quot;ccResp = %s&quot;, ccResp);<br />
	&nbsp;&nbsp;&nbsp; CCDictionary* dic = json-&gt;<b>dictionaryFrom</b>(ccResp);<br />
	&nbsp;&nbsp;&nbsp; env-&gt;ReleaseStringUTFChars(resp, ccResp);<br />
	&nbsp;&nbsp;&nbsp; CCNumber* status = (CCNumber*) dic-&gt;objectForKey(&quot;status&quot;); // Success = 1, Fail = 2, Cancel = 3<br />
	&nbsp;&nbsp;&nbsp; CCNumber* action = (CCNumber*) dic-&gt;objectForKey(&quot;action&quot;); //&nbsp; 1 = ACTION_AUTHORIZING,&nbsp; 8 = ACTION_USER_INFOR,9 = ACTION_SHARE<br />
	&nbsp;&nbsp;&nbsp; CCNumber* platform = (CCNumber*) dic-&gt;objectForKey(&quot;platform&quot;);<br />
	&nbsp;&nbsp;&nbsp; CCDictionary* res = (CCDictionary*) dic-&gt;objectForKey(&quot;res&quot;);<br />
	&nbsp;&nbsp;&nbsp; // TODO add codes here<br />
	&nbsp;&nbsp;&nbsp; if(1 == status-&gt;getIntValue()){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; callBackComplete(action-&gt;getIntValue(), platform-&gt;getIntValue(), res);<br />
	&nbsp;&nbsp;&nbsp; }else if(2 == status-&gt;getIntValue()){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; callBackError(action-&gt;getIntValue(), platform-&gt;getIntValue(), res);<br />
	&nbsp;&nbsp;&nbsp; }else{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; callBackCancel(action-&gt;getIntValue(), platform-&gt;getIntValue(), res);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; dic-&gt;autorelease();<br />
	}</font></p>
<p>这就是两块代码的关键联系。而问题似乎就出在<font face="Courier New">onJavaCallback</font>方 法里，因为我们看到了该方法中使用了Cocos2d-x的数据结构类。</p>
<p>我们来看一下onJavaCallback方法是在哪个线程里执行的。Cocos2d-x App至少有两个线程，一个UI Thread（Activity），一个Render Thread。显然onJavaCallback是在UI Thread中被执行的。但是我们知道Cocos2d-x的AutoreleasePool是在Render Thread中管理的，并在帧切换时进行释放操作的。</p>
<p>我们似乎闻到了问题的味道。Cocos2d-x基本上算是一个&quot;单线程&quot;游戏架构，所有的渲染操作、渲染树节点逻辑管理、绝大多数游戏逻辑都在 Render Thread中进行，UI Thread更多的是接收系统事件，并传递给Render Thread处理。Cocos2d-x的内存管理在这样的&ldquo;单线程&rdquo;背景下是没有大问题的，都是串行操作，不存在thread racing的情况。但一旦另外一个线程也调用内存管理接口进行对象内存操作时，问题就出现了，Cocos2d-x的内存池管理不是线程安全的。</p>
<p>我们回到上面代码，重点看一下json转dic的方法，该方法将分享应答字符串转换为内部的dictionary结构：</p>
<p><font face="Courier New">//AppDemo/Classes/C2DXShareSDK/Android/JSON/CCJSONConverter.cpp</font></p>
<p><font face="Courier New">CCDictionary * CCJSONConverter::dictionaryFrom(const char *str)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_Parse(str);<br />
	&nbsp;&nbsp;&nbsp; if (!json || json-&gt;type!=cJSON_Object) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (json) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON_Delete(json);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return NULL;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; CCAssert(json &amp;&amp; json-&gt;type==cJSON_Object, &quot;CCJSONConverter:wrong json format&quot;);<br />
	&nbsp;&nbsp;&nbsp; <b>CCDictionary * dictionary = CCDictionary::create();</b><br />
	&nbsp;&nbsp;&nbsp; <b>convertJsonToDictionary</b>(json, dictionary);<br />
	&nbsp;&nbsp;&nbsp; cJSON_Delete(json);<br />
	&nbsp;&nbsp;&nbsp; return dictionary;<br />
	}</font></p>
<p><font face="Courier New">void CCJSONConverter::convertJsonToDictionary(cJSON *json, CCDictionary *dictionary)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; dictionary-&gt;removeAllObjects();<br />
	&nbsp;&nbsp;&nbsp; cJSON * j = json-&gt;child;<br />
	&nbsp;&nbsp;&nbsp; while (j) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCObject * obj = <b>getJsonObj</b>(j);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dictionary-&gt;setObject(obj, j-&gt;string);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; j = j-&gt;next;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">CCObject * CCJSONConverter::getJsonObj(cJSON * json)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; switch (json-&gt;type) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case cJSON_Object:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCDictionary * dictionary = CCDictionary::create();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; convertJsonToDictionary(json, dictionary);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return dictionary;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case cJSON_Array:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCArray * array = CCArray::create();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; convertJsonToArray(json, array);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return array;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case cJSON_String:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCString * string = CCString::create(json-&gt;valuestring);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return string;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case cJSON_Number:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCNumber * number = CCNumber::create(json-&gt;valuedouble);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return number;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case cJSON_True:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCNumber * boolean = CCNumber::create(1);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return boolean;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case cJSON_False:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCNumber * boolean = CCNumber::create(0);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return boolean;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case cJSON_NULL:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCNull * null = CCNull::create();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return null;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCLog(&quot;CCJSONConverter encountered an unrecognized type&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return NULL;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>可以看出整个解析过程，都直接用的是传统的Cocos2d-x对象构造方法：create。在每个对象的create中，代码都会调用该对象的 autorelease方法。而这个方法本身就是线程不安全的，且即便autorelease调用ok，在下一帧切换时，这些对象将都会被release 掉，如果在UI Thread中再引用这些对象的地址，那势必造成内存的非法访问，而引发程序崩溃。</p>
<p><b>四、Fix方法</b></p>
<p>可能有朋友会问，create后，我retain一下可否？答案是否。因此create的创建不是线程安全的，create和retain两个调 用之间存在时间差，而在这段时间内，该对象就有可能被render thread释放掉。</p>
<p>Fix方法很简单，就是在UI Thread中不使用Cocos2d-x的内存管理机制，我们用传统的new来替代create，并将 Java_cn_sharesdk_ShareSDKUtils_onJavaCallback最后的autorelease改为release，这样就 不用劳烦Render Thread来帮我们释放内存了。CCDictionary的destructor调用时还会将Dictionarny内部所有Element自动释放 掉。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/04/17/a-bug-from-sharesdk-componet-for-cocos2dx/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x内存管理-绕不过去的坎</title>
		<link>https://tonybai.com/2014/03/18/cocos2dx-memory-management/</link>
		<comments>https://tonybai.com/2014/03/18/cocos2dx-memory-management/#comments</comments>
		<pubDate>Mon, 17 Mar 2014 17:32:32 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cocos2d]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Eclipse]]></category>
		<category><![CDATA[Game-Engine]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[GLSurfaceView]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[NDK]]></category>
		<category><![CDATA[opengl]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></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>

		<guid isPermaLink="false">http://tonybai.com/?p=1502</guid>
		<description><![CDATA[Cocos2d-x引擎的核心是用C++编写的，那对于所有使用该引擎的游戏开发人员来说，内存管理是一道绕不过去的坎。 关于Cocos2d-x内存管理，网上已经有了许多参考资料，有些资料写的颇为详实，因为在内存管理这块我不想多费笔墨，只是更多的将思路描述清 楚。 一、对象内存引用计数 Cocos2d-x内存管理的基本原理就是对象内存引用计数，Cocos2d-x将内存引用计数的实现放在了顶层父类CCObject中，这里将涉及引用计数的CCObject的成员和方法摘录出来： class CC_DLL CCObject : public CCCopying { public: &#160;&#160; &#8230; &#8230; protected: &#160;&#160;&#160; // count of references &#160;&#160;&#160; unsigned int&#160;&#160;&#160;&#160;&#160;&#160;&#160; m_uReference; &#160;&#160;&#160; // count of autorelease &#160;&#160;&#160; unsigned int&#160;&#160;&#160;&#160;&#160;&#160;&#160; m_uAutoReleaseCount; public: &#160;&#160;&#160; void release(void); &#160;&#160;&#160; void retain(void); &#160;&#160;&#160; CCObject* autorelease(void); &#160;&#160;&#160; &#8230; &#8230;. } CCObject::CCObject(void) : m_nLuaID(0) , m_uReference(1) // [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://cocos2d-x.org/">Cocos2d-x</a>引擎的核心是用C++编写的，那对于所有使用该引擎的游戏开发人员来说，内存管理是一道绕不过去的坎。</p>
<p>	关于Cocos2d-x内存管理，网上已经有了许多参考资料，有些资料写的颇为详实，因为在内存管理这块我不想多费笔墨，只是更多的将思路描述清 楚。</p>
<p>	<b>一、对象内存引用计数</b></p>
<p>	Cocos2d-x内存管理的基本原理就是对象内存引用计数，Cocos2d-x将内存引用计数的实现放在了顶层父类CCObject中，这里将涉及引用计数的CCObject的成员和方法摘录出来：</p>
<p>	<font face="Courier New">class CC_DLL CCObject : public CCCopying<br />
	{<br />
	public:<br />
	&nbsp;&nbsp; &#8230; &#8230;<br />
	protected:<br />
	&nbsp;&nbsp;&nbsp; // count of references<br />
	&nbsp;&nbsp;&nbsp; unsigned int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_uReference;<br />
	&nbsp;&nbsp;&nbsp; // count of autorelease<br />
	&nbsp;&nbsp;&nbsp; unsigned int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_uAutoReleaseCount;<br />
	public:<br />
	&nbsp;&nbsp;&nbsp; void release(void);<br />
	&nbsp;&nbsp;&nbsp; void retain(void);<br />
	&nbsp;&nbsp;&nbsp; CCObject* autorelease(void);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;.<br />
	}</font></p>
<p>	<font face="Courier New">CCObject::CCObject(void)<br />
	: m_nLuaID(0)<br />
	, m_uReference(1) // when the object is created, the reference count of it is 1<br />
	, m_uAutoReleaseCount(0)<br />
	{<br />
	&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p>	<font face="Courier New">void CCObject::release(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCAssert(m_uReference &gt; 0, &quot;reference count should greater than 0&quot;);<br />
	&nbsp;&nbsp;&nbsp; &#8211;m_uReference;</p>
<p>	&nbsp;&nbsp;&nbsp; if (m_uReference == 0)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete this;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</p>
<p>	void CCObject::retain(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCAssert(m_uReference &gt; 0, &quot;reference count should greater than 0&quot;);</p>
<p>	&nbsp;&nbsp;&nbsp; ++m_uReference;<br />
	}</p>
<p>	CCObject* CCObject::autorelease(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCPoolManager::sharedPoolManager()-&gt;addObject(this);<br />
	&nbsp;&nbsp;&nbsp; return this;<br />
	}</font></p>
<p>	先不考虑autorelease与m_uAutoReleaseCount（后续细说）。计数的核心字段是m_uReference，可以看到：</p>
<p>	<font face="Courier New">* 当一个Object初始化（被new出来时），m_uReference = 1；<br />
	* 当调用该Object的retain方法时，m_uReference++；<br />
	* 当调用该Object的release方法时，m_uReference&#8211;，若m_uReference减后为0，则delete该Object。</font></p>
<p>
	二、<b>手工对象内存管理</b></p>
<p>	在上述对象内存引用计数的原理下，我们得出以下Cocos2d-x下手工对象内存管理的基本模式：</p>
<p>	<font face="Courier New">CCObject *obj = new CCObject();<br />
	obj-&gt;init();<br />
	&#8230;. &#8230;<br />
	obj-&gt;release();</font></p>
<p>	在Cocos2d-x中CCDirector就是一个手工内存管理的典型：</p>
<p>	<font face="Courier New">CCDirector* CCDirector::sharedDirector(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (!s_SharedDirector)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>s_SharedDirector = new CCDisplayLinkDirector();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s_SharedDirector-&gt;init();</b><br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; return s_SharedDirector;<br />
	}</font></p>
<p>	<font face="Courier New">void CCDirector::purgeDirector()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; // delete CCDirector<br />
	&nbsp;&nbsp;&nbsp; <b>release();</b><br />
	}</font></p>
<p>	<b>三、自动对象内存管理</b></p>
<p>	所谓的&ldquo;自动对象内存管理&rdquo;，指的就是哪些不再需要的object将由Cocos2d-x引擎替你释放掉，而无需你手工再调用Release方法。</p>
<p>	自动对象内存管理显然也要遵循内存引用计数规则，只有当object的计数变为0时，才会释放掉对象的内存。</p>
<p>	自动对象内存管理的典型模式如下：</p>
<p>	<font face="Courier New">CCYourClass *CCYourClass::create()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCYourClass*pRet = new CCYourClass();<br />
	&nbsp;&nbsp;&nbsp; if (pRet &amp;&amp; pRet-&gt;init())<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pRet-&gt;autorelease();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return pRet;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; else<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CC_SAFE_DELETE(pRet);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return NULL;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	一般我们通过一个单例模式创建对象，与手工模式不同的地方在于init后多了一个<font face="Courier New">autorelease</font>调用。这里再把autorelease调用的实现摘录一遍：</p>
<p>	<font face="Courier New">CCObject* CCObject::autorelease(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCPoolManager::sharedPoolManager()-&gt;addObject(this);<br />
	&nbsp;&nbsp;&nbsp; return this;<br />
	}</font></p>
<p>	追溯addObject方法：</p>
<p>	<font face="Courier New">// cocoa/CCAutoreleasePool.cpp</p>
<p>	void CCPoolManager::addObject(CCObject* pObject)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; getCurReleasePool()-&gt;addObject(pObject);<br />
	}</p>
<p>	void CCAutoreleasePool::addObject(CCObject* pObject)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; m_pManagedObjectArray-&gt;addObject(pObject);</p>
<p>	&nbsp;&nbsp;&nbsp; CCAssert(pObject-&gt;m_uReference &gt; 1, &quot;reference count should be greater than 1&quot;);<br />
	&nbsp;&nbsp;&nbsp; <b>++(pObject-&gt;m_uAutoReleaseCount);</b><br />
	&nbsp;&nbsp;&nbsp; <b>pObject-&gt;release();</b> // no ref count, in this case autorelease pool added.<br />
	}</p>
<p>	// cocoa/CCArray.cpp<br />
	void CCArray::addObject(CCObject* object)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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; ccArrayAppendObjectWithResize(data, object);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;</p>
<p>	// support/data_support/ccCArray.cpp<br />
	void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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; ccArrayEnsureExtraCapacity(arr, 1);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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; ccArrayAppendObject(arr, object);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	}</p>
<p>	void ccArrayAppendObject(ccArray *arr, CCObject* object)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCAssert(object != NULL, &quot;Invalid parameter!&quot;);<br />
	&nbsp;&nbsp;&nbsp; <b>object-&gt;retain();</b><br />
	&nbsp;&nbsp;&nbsp; arr-&gt;arr[arr->num] = object;<br />
	&nbsp;&nbsp;&nbsp; arr-&gt;num++;<br />
	}</p>
<p>	调用层次挺深，涉及的类也众多，这里归纳总结一下。</p>
<p>	Cocos2d-x的自动对象内存管理基于对象引用计数以及CCAutoreleasePool（自动释放池）。引用计数前面已经说过了，这里单说自动释放池。Cocos2d-x关于自动对象内存管理的基本类层次结构如下：</p>
<p>	&nbsp;&nbsp;&nbsp; CCPoolManager类 (自动释放池管理器)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; CCArray*&nbsp;&nbsp;&nbsp; m_pReleasePoolStack; （自动释放池栈，存放CCAutoreleasePool类实例）<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; CCAutoreleasePool类<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; CCArray*&nbsp;&nbsp;&nbsp; m_pManagedObjectArray;</font> （受管对象数组）</p>
<p>	CCObject关于内存计数以及自动管理有两个字段：<font face="Courier New">m_uReference和m_uAutoReleaseCount</font>。前面在手工管理模式下，我只提及了m_uReference，是m_uAutoReleaseCount该亮相的时候了。我们沿着自动释放对象的创建步骤来看看不同阶段，这两个重要字段的值都是啥，代表的是啥含义：</p>
<p>	<font face="Courier New">CCYourClass*pRet = new CCYourClass();&nbsp;&nbsp;&nbsp; m_uReference = 1； m_uAutoReleaseCount = 0；<br />
	pRet-&gt;init()；&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_uReference = 1； m_uAutoReleaseCount = 0；<br />
	pRet-&gt;autorelease();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; m_pManagedObjectArray-&gt;addObject(pObject); </font><font face="Courier New">m_uReference = 2； m_uAutoReleaseCount = 0；</font><br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; ++(pObject-&gt;m_uAutoReleaseCount);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_uReference = 2； m_uAutoReleaseCount = 1；<br />
	&nbsp;&nbsp;&nbsp; pObject-&gt;release();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_uReference = 1； m_uAutoReleaseCount = 1； </font></p>
<p>	在调用autorelease之前，两个值与手工模式并无差别，在autorelease后，m_uReference值没有变，但m_uAutoReleaseCount被加1。</p>
<p>	m_uAutoReleaseCount这个字段的名字很容易让人误解，以为是个计数器，但实际上绝大多数时刻它是一个标识的角色，以前版本代码中有一个布尔字段m_bManaged，似乎后来被m_uAutoReleaseCount替换掉了，因此<b>m_uAutoReleaseCount兼有m_bManaged的含义</b>， 也就是说该object是否在自动释放池的控制之下，如果在自动释放池的控制下，自动释放池会定期调用该object的release方法，直到该 object内存计数降为0，被真正释放。否则该object不能被自动释放池自动释放内寸，需手工release。这个理解非常重要，再后面我们能用到 这个理解。</p>
<p>	<b>四、自动释放时机</b></p>
<p>	通过autorelease我们已经将object放入autoreleasePool中，那究竟何时对象会被释放呢？答案是每帧执行一次自动内存对象释放操作。</p>
<p>	在&ldquo;<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">Hello，Cocos2d-x</a>&rdquo;一文中，我们讲过整个Cocos2d-x引擎的驱动机制在于GLThread的guardedRun函数，后者会 &ldquo;死循环&rdquo;式（实际帧绘制频率受到屏幕vertsym信号的影响）的调用Render的onDrawFrame方法实现，而最终程序会进入 CCDirector::mainLoop方法中，也就是说mainLoop的执行频率是每帧一次。我们再来看看mainLoop的实现：</p>
<p>	<font face="Courier New">void CCDisplayLinkDirector::mainLoop(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (m_bPurgeDirecotorInNextLoop)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_bPurgeDirecotorInNextLoop = false;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; purgeDirector();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; else if (! m_bInvalid)<br />
	&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; drawScene();</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // release the objects<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCPoolManager::sharedPoolManager()-&gt;pop();<br />
	&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	这次我们要关注的不是drawScene，而是 CCPoolManager::sharedPoolManager()-&gt;pop()，显然在游戏未退出 (m_bPurgeDirecotorInNextLoop决定）的条件下，CCPoolManager的pop方法每帧执行一次，这就是自动释放池执行 的起点。</p>
<p>	<font face="Courier New">void CCPoolManager::pop()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (! m_pCurReleasePool)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp; int nCount = m_pReleasePoolStack-&gt;count();</p>
<p>	&nbsp;&nbsp;&nbsp; <b>m_pCurReleasePool-&gt;clear();</b></p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(nCount &gt; 1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pReleasePoolStack-&gt;removeObjectAtIndex(nCount-1);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack-&gt;objectAtIndex(nCount &#8211; 2);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	真正释放对象的方法是<font face="Courier New">m_pCurReleasePool-&gt;clear()</font>。</p>
<p>	<font face="Courier New">void CCAutoreleasePool::clear()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if(m_pManagedObjectArray-&gt;count() &gt; 0)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCObject* pObj = NULL;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(!pObj)<br />
	&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; <b>&#8211;(pObj-&gt;m_uAutoReleaseCount);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>m_pManagedObjectArray-&gt;removeAllObjects();</b><br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	<font face="Courier New">void CCArray::removeAllObjects()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	{&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; ccArrayRemoveAllObjects(data);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	}</p>
<p>
	void ccArrayRemoveAllObjects(ccArray *arr)&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; while( arr-&gt;num &gt; 0 )&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>(arr-&gt;arr[--arr->num])-&gt;release();</b>&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;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	} </font></p>
<p>	不出预料，当前自动释放池遍历每个&ldquo;受控制&rdquo;Object，&#8211;m_uAutoReleaseCount，并调用该object的release方法。</p>
<p>	我们接着按释放流程来看看m_uAutoReleaseCount和m_uReference值的变化：</p>
<p>	<font face="Courier New">CCPoolManager::sharedPoolManager()-&gt;pop()；&nbsp; m_uReference = 0； m_uAutoReleaseCount = 0；</font></p>
<p>
	<b>五、自动释放池的初始化</b></p>
<p>	自动释放池本身是何时出现的呢？回顾一下Cocos2d-x引擎的初始化过程（android版），引擎初始化实在Render的onSurfaceCreated方法中进行的，我们不难追踪到以下代码：</p>
<p>	<font face="Courier New">//hellocpp/jni/hellocpp/main.cpp<br />
	Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit {<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; //这里CCDirector第一次被创建<br />
	&nbsp;&nbsp;&nbsp; if (<b>!CCDirector::sharedDirector()-&gt;getOpenGLView()</b>)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCEGLView *view = CCEGLView::sharedOpenGLView();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; view-&gt;setFrameSize(w, h);</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 />
	}</font><br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp;<br />
	CCDirector* CCDirector::sharedDirector(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (!s_SharedDirector)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s_SharedDirector = new CCDisplayLinkDirector();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>&nbsp; s_SharedDirector-&gt;init();</b>&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; return s_SharedDirector;<br />
	}</p>
<p>
	bool CCDirector::init(void)</font><br />
	<font face="Courier New">{<br />
	&nbsp;&nbsp;&nbsp; setDefaultValues();</font></p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; // create autorelease pool<br />
	&nbsp;&nbsp;&nbsp; <b>CCPoolManager::sharedPoolManager()-&gt;push();</b></font></p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; return true;<br />
	}</font></p>
<p>	<b>六、探寻Cocos2d-x内核对象的自动化内存释放</b></p>
<p>	前面我们基本了解了Cocos2D-x的自动化内存释放原理。如果你之前翻看过一些Cocos2d-x的内核源码，你会发现很多内核对象都是通过单例模式create出来的，也就是说都使用了autorelease将自己放入自动化内存释放池中被管理。</p>
<p>	比如我们在HelloCpp中看到过这样的代码：</p>
<p>	<font face="Courier New">//HelloWorldScene.cpp<br />
	bool HelloWorld::init() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp; &#8230;. &#8230;.<br />
	&nbsp;&nbsp;&nbsp; // add &quot;HelloWorld&quot; splash screen&quot;<br />
	&nbsp;&nbsp;&nbsp; <b>CCSprite* pSprite = CCSprite::create(&quot;HelloWorld.png&quot;);</b></p>
<p>	&nbsp;&nbsp;&nbsp; // position the sprite on the center of the screen<br />
	&nbsp;&nbsp;&nbsp; pSprite-&gt;setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));</p>
<p>	&nbsp;&nbsp;&nbsp; // add the sprite as a child to this layer<br />
	&nbsp;&nbsp;&nbsp; <b>this-&gt;addChild(pSprite, 0);</b><br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p>	CCSprite采用自动化内存管理模式create object（cocos2dx/sprite_nodes/CCSprite.cpp），之后将自己加入到HelloWorld这个CCLayer实例 中。按照上面的分析，create结束后，CCSprite object的m_uReference = 1； m_uAutoReleaseCount = 1。一旦如此，那么在下一帧时，该object就会被CCPoolManager释放掉。但我们在屏幕上依旧可以看到该Sprite的存在，这是怎么回事呢？</p>
<p>	问题的关键就在<font face="Courier New">this-&gt;addChild(pSprite, 0)</font>这行代码中。addChild方法实现在CCLayer的父类CCNode中：</p>
<p>	<font face="Courier New">//&nbsp; cocos2dx/base_nodes/CCNode.cpp<br />
	void CCNode::addChild(CCNode *child, int zOrder, int tag)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; if( ! m_pChildren )<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this-&gt;childrenAlloc();<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; <b>this-&gt;insertChild(child, zOrder);</b></p>
<p>	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</p>
<p>	void CCNode::insertChild(CCNode* child, int z)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; m_bReorderChildDirty = true;<br />
	&nbsp;&nbsp;&nbsp; ccArrayAppendObjectWithResize(m_pChildren-&gt;data, child);<br />
	&nbsp;&nbsp;&nbsp; child-&gt;_setZOrder(z);<br />
	}</p>
<p>	void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; ccArrayEnsureExtraCapacity(arr, 1);<br />
	&nbsp;&nbsp;&nbsp; ccArrayAppendObject(arr, object);<br />
	}</p>
<p>	void ccArrayAppendObject(ccArray *arr, CCObject* object)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCAssert(object != NULL, &quot;Invalid parameter!&quot;);<br />
	&nbsp;&nbsp;&nbsp; object-&gt;retain();<br />
	&nbsp;&nbsp;&nbsp; arr-&gt;arr[arr->num] = object;<br />
	&nbsp;&nbsp;&nbsp; arr-&gt;num++;<br />
	}</font></p>
<p>	又是一系列方法调用，最终我们来到了ccArrayAppendObject方法中，看到了陌生而又眼熟的retain方法调用。</p>
<p>	在本文开始我们介绍CCObject时，我们知道retain是CCObject的一个方法，用于增加m_uReference计数。而实际上retain还隐含着&ldquo;保留&rdquo;这层意思。</p>
<p>	在完成this-&gt;addChild(pSprite, 0)调用后，CSprite object的<font face="Courier New">m_uReference = 2； m_uAutoReleaseCount = 1</font>，这很关键。</p>
<p>	我们在脑子里再过一下自动释放池释放object的过程：&#8211;m_uReference, &#8211;m_uAutoReleaseCount。一帧之后，两个值变成了<font face="Courier New">m_uReference = 1； m_uAutoReleaseCount = 0</font>。还记得前面说过的m_uAutoReleaseCount的另外一个非计数含义么，那就是表示该object是否&ldquo;<b>受控</b>&rdquo;，现在值为0，显然不再受自动释放池的控制了，后续即便再执行100次内存自动释放，也不会影响到该object的存活。</p>
<p>	后续要想释放这个&ldquo;精灵&rdquo;，我们还是需要手工调用release，或再调用其autorelease方法。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/03/18/cocos2dx-memory-management/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Hello, Cocos2d-x</title>
		<link>https://tonybai.com/2014/03/11/hello-cocos2dx/</link>
		<comments>https://tonybai.com/2014/03/11/hello-cocos2dx/#comments</comments>
		<pubDate>Tue, 11 Mar 2014 12:33:45 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Activity]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[C11]]></category>
		<category><![CDATA[Cocos2d]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Eclipse]]></category>
		<category><![CDATA[Game-Engine]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[GLSurfaceView]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[NDK]]></category>
		<category><![CDATA[opengl]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></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>

		<guid isPermaLink="false">http://tonybai.com/?p=1495</guid>
		<description><![CDATA[女儿从两岁半开始接触iPad，在这个年龄段也只有一些幼教类游戏适合她玩。虽然知道iPad玩久了对视力有伤害，但有时候还真拗不过果果，索性 也就让她玩一会儿。之前对智能终端上的东西不是很在意，也没啥兴趣，这大概与当年在大学时做Win32 GUI开发的糟糕经历多多少少有点关系。不过智能终端是大势所趋，历史的潮流不能违抗。虽然自己并非以Android/iOS编程为主业，但适当学习学习 总归没有坏处，万一作出一个像&#34;Flappy Bird&#34;的游戏，爆发一下，还是蛮Happy的。于是在开始学习实践之前给自己定了一个小目标：今年六一儿童节送给女儿一款自己制作的小游戏。 智能终端上的游戏目前风头正劲，试问哪个智能手机上没有几款企鹅公司出品的游戏呢！之前从未涉猎过游戏开发，但知道游戏开发前要挑选一款合适的游 戏引擎，自己从头开始敲代码的时代已经out了。在寻觅游戏引擎之前，我需要回答三道摆在我面前的选择题： &#160;&#160;&#160; 1、2D引擎还是3D引擎？ &#160;&#160;&#160; 2、平台专用引擎还是跨平台引擎？ &#160;&#160;&#160; 3、收费引擎还是开源引擎？ 作为入门级选手，2D游戏显然更适合上手一些，另外适合果果这个年龄段的幼教类的游戏也多以2D游戏居多。3D游戏本身也太难了，不仅要 Programming能力，还要3D建模能力，这些学习起来周期就太长了；一直是Ubuntu Fans，手头没有Mac Book，这样开发iOS程序变成一件糟心的事，在Ubuntu下搭建iOS App开发环境繁杂的很，即便是虚拟机也懒得尝试。但从游戏体验来看，还是在iPad上玩更好一些，因此最好引擎能跨平台，以便后续迁移到iOS上；开源 和用开源惯了，收费的引擎目前不在考虑范围之内。综上，我要寻找的是一款开源的、跨平台的Mobile 2D Game Engine。 于是我找到了Cocos2d-x！Cocos2d-x是Cocos2d-iphone的C++跨平台分支，由于是国人创立的，在国内有着较大的用 户群，引擎资料也较多，社区十分活跃。国内已经出版了多本有关Cocos2d-x的中文书籍，比如《Cocos2d-x高级开发教程:制作自己的 &#8220;捕鱼达人&#8221;》 、《Cocos2d-x权威指南》 等都还不错。更重要的是Cocos2d-x自带了丰富的例子，供初学者&#8220;临摹学习&#8221;，其中cocos2d-x-2.2.2/samples/Cpp /TestCpp这个例子几乎涵盖了该引擎的绝大多数功能。下面就开启Cocos2d-x的入门之旅（For Android）。 一、引擎安装 试验环境： &#160;&#160; Ubuntu 12.04.1 x86_64 &#160;&#160; gcc 4.6.3 &#160;&#160; javac 1.7.0_21 &#160;&#160; java &#34;1.7.0_21&#34; HotSpot 64-bit Server VM &#160;&#160; adt-bundle-linux-x86_64-20131030.zip &#160;&#160; android-ndk-r9d-linux-x86_64.tar.bz2 Cocos2d-x官网目前提供2.2.2稳定版以及3.0beta2版的下载（当然你也可以下载到更老的版本）。由于3.0改变较大，资料不 多，且对编译器等版本的要求较高(需要支持C++ 11标准)，因此这里依旧以2.2.2版本作为学习目标。Cocos2d-x-2.2.2下载后解压到某个目录：比如/home1/tonybai/android-dev/cocos2d-x-2.2.2。 [...]]]></description>
			<content:encoded><![CDATA[<p>女儿从两岁半开始接触iPad，在这个年龄段也只有一些幼教类游戏适合她玩。虽然知道iPad玩久了对视力有伤害，但有时候还真拗不过果果，索性 也就让她玩一会儿。之前对智能终端上的东西不是很在意，也没啥兴趣，这大概与当年在大学时做Win32 GUI开发的糟糕经历多多少少有点关系。不过智能终端是大势所趋，历史的潮流不能违抗。虽然自己并非以<a href="http://tonybai.com/2011/05/24/develop-android-app-in-command-line-method/">Android</a>/iOS编程为主业，但适当学习学习 总归没有坏处，万一作出一个像&quot;Flappy Bird&quot;的游戏，爆发一下，还是蛮Happy的。于是在开始学习实践之前给自己定了一个小目标：今年六一儿童节送给女儿一款自己制作的小游戏。</p>
<p>智能终端上的游戏目前风头正劲，试问哪个智能手机上没有几款<a href="http://qq.com">企鹅公司</a>出品的游戏呢！之前从未涉猎过游戏开发，但知道游戏开发前要挑选一款合适的游 戏引擎，自己从头开始敲代码的时代已经out了。在寻觅游戏引擎之前，我需要回答三道摆在我面前的选择题：</p>
<p><i><font face="Courier New">&nbsp;&nbsp;&nbsp; 1、2D引擎还是3D引擎？<br />
	&nbsp;&nbsp;&nbsp; 2、平台专用引擎还是跨平台引擎？<br />
	&nbsp;&nbsp;&nbsp; 3、收费引擎还是开源引擎？</font></i></p>
<p>作为入门级选手，2D游戏显然更适合上手一些，另外适合果果这个年龄段的幼教类的游戏也多以2D游戏居多。3D游戏本身也太难了，不仅要 Programming能力，还要3D建模能力，这些学习起来周期就太长了；一直是<a href="http://tonybai.com/tag/Ubuntu">Ubuntu</a> Fans，手头没有Mac Book，这样开发iOS程序变成一件糟心的事，在Ubuntu下搭建iOS App开发环境繁杂的很，即便是虚拟机也懒得尝试。但从游戏体验来看，还是在iPad上玩更好一些，因此最好引擎能跨平台，以便后续迁移到iOS上；开源 和用开源惯了，收费的引擎目前不在考虑范围之内。综上，我要寻找的是一款开源的、跨平台的Mobile 2D Game Engine。</p>
<p>于是我找到了<a href="http://cocos2d-x.org/">Cocos2d-x</a>！Cocos2d-x是<a href="http://www.cocos2d-iphone.org/‎">Cocos2d-iphone</a>的C++跨平台分支，由于是国人创立的，在国内有着较大的用 户群，引擎资料也较多，社区十分活跃。国内已经出版了多本有关Cocos2d-x的中文书籍，比如《<a href="http://book.douban.com/subject/24704115/‎">Cocos2d-x高级开发教程:制作自己的 &ldquo;捕鱼达人&rdquo;</a>》 、《<a href="http://book.douban.com/subject/23820911/">Cocos2d-x权威指南</a>》 等都还不错。更重要的是Cocos2d-x自带了丰富的例子，供初学者&ldquo;临摹学习&rdquo;，其中cocos2d-x-2.2.2/samples/Cpp /TestCpp这个例子几乎涵盖了该引擎的绝大多数功能。下面就开启Cocos2d-x的入门之旅（For Android）。</p>
<p><b>一、引擎安装</b></p>
<p>试验环境：<br />
	<font face="Courier New"><i>&nbsp;&nbsp; Ubuntu 12.04.1 x86_64<br />
	&nbsp;&nbsp; gcc 4.6.3<br />
	&nbsp;&nbsp; javac 1.7.0_21<br />
	&nbsp;&nbsp; java &quot;1.7.0_21&quot; HotSpot 64-bit Server VM<br />
	&nbsp;&nbsp; adt-bundle-linux-x86_64-20131030.zip<br />
	&nbsp;&nbsp; android-ndk-r9d-linux-x86_64.tar.bz2</i></font></p>
<p>Cocos2d-x官网目前提供2.2.2稳定版以及3.0beta2版的下载（当然你也可以下载到更老的版本）。由于3.0改变较大，资料不 多，且对编译器等版本的要求较高(需要支持<a href="http://en.wikipedia.org/wiki/C++11‎">C++ 11标准</a>)，因此这里依旧以2.2.2版本作为学习目标。Cocos2d-x-2.2.2下载后解压到某个目录：比如<font face="Courier New">/home1/tonybai/android-dev/cocos2d-x-2.2.2</font>。 如果仅是用Cocos2d-x开发Android版本游戏，则不需要做什么编译工作。Android Game Project会在Project build时自动用NDK的编译器编译C++代码，并与NDK链接。如果你想早点看看Cocos2d-x sample中的例子运行起来到底是什么样子的，你可以在Ubuntu下编译出Linux版本的游戏：在cocos2d-x-2.2.2下执行<font face="Courier New">make-all-linux-project.sh</font>即可。编译需要一段时间，编译成 功后，我们可以进入到&ldquo;<font face="Courier New">cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.linux/bin/release</font>&rdquo; 下执行&ldquo;HelloCpp&rdquo;这个可执行文件，一个最简单的Cocos2d-x游戏就会展现在你的面前了。</p>
<p>Android sample project的构建稍微复杂些：</p>
<p>首先在Eclipse中添加libcocos2dx Library project from existed code（注意：不Copy到workspace，原地建立）。该Project的代码路径为cocos2d-x-2.2.2/cocos2dx/platform /android/java。在project.properties和AndroidManifest.xml适当修改你所使用的api版本， 以让编译通过。我这里用的是 target=android-19。</p>
<p>然后，设置NDK_ROOT环境变量(比如<font face="Courier New">export NDK_ROOT=&#39;/home1/tonybai/android-dev/adt-bundle-linux-x86_64/android-ndk-r9c&#39;</font>)， 供build_native.sh使用。</p>
<p>最后添加游戏project。在Eclipse中添加HelloCpp project from existed code，位置<font face="Courier New">cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android</font>（注 意：不Copy到Workspace中，原地建立）。在HelloCpp的project.properties中添加&ldquo;<font face="Courier New">android.library.reference.1=../../../../cocos2dx/platform/android /java</font>&rdquo;。同样别忘了在project.properties和AndroidManifest.xml适当修改你所使用 的api版本，以让编译通过。</p>
<p>如果一切顺利的话，你会在Console窗口看到&ldquo;<font face="Courier New">**** Build Finished ****</font>&rdquo;。Problems窗口显示&ldquo;<font face="Courier New">0 errors</font>&ldquo;。 启动Android模拟器，Run Application，同样的HelloCpp画面会呈现在模拟器上。</p>
<p>Cocos2d-x是建构在OpenGL技术之上的。对于Android平台而言，Android SDK已经完全封装了opengl es 1.1/2.0的API（<font face="Courier New">android.opengl.*;javax.microedition.khronos.egl.*;javax.microedition.khronos.opengles.*</font>）， 引擎完全可以建立在这个之上，无需C++代码。但Cocos2d-x是一个跨平台的2D游戏引擎，核心选择了用C++代码实现(iOS提供的C绑 定，不提供Java绑定；Android则提供了Java和C绑定)，因此 在开发Android平台的2D游戏时，引擎部分是SDK与NDK交相互应，比如GLThread的创建和管理用的是SDK的 GLSurfaceView和GLThread，但真正的Surface绘制部分则是回调Cocos2d-x用C++编写的绘制实现（链接NDK 中的库）。</p>
<p><b>二、Cocos2d-x Android工程代码组织结构</b></p>
<p>以samples/Cpp/HelloApp的Android工程为例，Android版的Cocos2d-x工程与普通android应用程序 差别 不大，核心部分只是多了一个jni目录和一个build_native.sh脚本文件。其中jni目录下存放的是Java和C++调用转换的&ldquo;胶 水&rdquo;代码；build_native.sh则是用于编译jni下C++代码以及 cocos2dx_static library代码的构建脚本。</p>
<p>HelloCpp的构建过程摘要如下：</p>
<p><font face="Courier New">**** Build of configuration Default for project HelloCpp ****</font></p>
<p><font face="Courier New"><u><b>bash /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/build_native.sh</b></u><br />
	NDK_ROOT = /home1/tonybai/android-dev/adt-bundle-linux-x86_64/android-ndk-r9c<br />
	COCOS2DX_ROOT = /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/../../../..<br />
	APP_ROOT = /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/..<br />
	APP_ANDROID_ROOT = /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android<br />
	+ /home1/tonybai/android-dev/adt-bundle-linux-x86_64/android-ndk-r9c/ndk-build -C /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.androidNDK_MODULE_PATH=/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/../../../..:/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/../../../../cocos2dx/platform/third_party/android/prebuilt<br />
	Using prebuilt externals<br />
	Android NDK: WARNING:/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/../../../../cocos2dx<b>/Android.mk</b>:cocos2dx_static: LOCAL_LDLIBS is always ignored for static libraries&nbsp;&nbsp;<br />
	make: Entering directory `/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android&#39;<br />
	[armeabi] Compile++ thumb: hellocpp_shared &lt;= main.cpp<br />
	[armeabi] Compile++ thumb: hellocpp_shared &lt;= AppDelegate.cpp<br />
	[armeabi] Compile++ thumb: hellocpp_shared &lt;= HelloWorldScene.cpp<br />
	[armeabi] Compile++ thumb: cocos2dx_static &lt;= CCConfiguration.cpp<br />
	[armeabi] Compile++ thumb: cocos2dx_static &lt;= CCScheduler.cpp<br />
	&nbsp;&#8230; &#8230;<br />
	[armeabi] Compile++ thumb: cocos2dx_static &lt;= CCTouch.cpp<br />
	[armeabi] StaticLibrary&nbsp; : libcocos2d.a<br />
	[armeabi] Compile thumb&nbsp; : cpufeatures &lt;= cpu-features.c<br />
	[armeabi] StaticLibrary&nbsp; : libcpufeatures.a<br />
	[armeabi] SharedLibrary&nbsp; : libhellocpp.so<br />
	[armeabi] Install&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : libhellocpp.so =&gt; libs/armeabi/libhellocpp.so<br />
	make: Leaving directory `/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android&#39;</font></p>
<p><font face="Courier New">**** Build Finished ****</font></p>
<p>指挥NDK编译的则是jni下的Android.mk文件，其角色类似于Makefile。</p>
<p><b>三、Cocos2d-x Android工程代码阅读</b></p>
<p>单独将如何阅读代码拿出来，是为了后面分析引擎的驱动流程做准备工作。学习类似Cocos2d-x这样的游戏引擎，仅仅停留在游戏逻辑层代码是不 能很好的把握引擎本质的，因此适当的挖掘引擎实现实际上对于理解和使用 引擎都是大有裨益的。</p>
<p>以一个Cocos2d-x Android工程为例，它的游戏逻辑代码以及涉及的引擎代码涵盖在一下路径下（还是以HelloCpp的Android工程为例）：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 项目层：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; * cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/src&nbsp; 主Activity的实现；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; * cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/jni/hellocpp&nbsp; Cocos2dxRenderer类的nativeInit实现，用于引出Application的入口；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; * cocos2d-x-2.2.2/samples/Cpp/HelloCpp/Classes 你的游戏逻辑，以C++代码形式呈现；<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 引擎层：<br />
	&nbsp; &nbsp; &nbsp; &nbsp; * cocos2d-x-2.2.2/cocos2dx/platform/android/java/src 引擎层对Android Activity、GLSurfaceView以及Render的封装<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * cocos2d-x-2.2.2/cocos2dx/platform/android/jni 对应上面封装的native method实现<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * cocos2d-x-2.2.2/cocos2dx、cocos2d-x-2.2.2/cocos2dx/platform、cocos2d-x- 2.2.2/cocos2dx/platform/android&nbsp;&nbsp; cocos2dx引擎的核心实现(针对android平台)</font></p>
<p>后续的代码分析也将从这两个层次、六处位置出发。</p>
<p><b>四、从Activity开始</b></p>
<p>之前多少了解了一些Android App开发的知识，Android App都是始于Activity的。游戏也是App的一种，因此在Android平台上，Cocos2d-x游戏也是从Activity开始的。于是 Activity，确切的说是Cocos2dxActivity是我们这次引擎驱动机制分析的出发点。</p>
<p>回顾Android Activity的Lifecycle，Activity启动的顺序是：<font face="Courier New">Activity.onCreate -&gt; Activity.onStart() -&gt; Activity.onResume()</font>。接下来我们将按照 这条主线进行引擎驱动机制的分析。</p>
<p>HelloCpp.java中的HelloCpp这个Activity完全无所作为，仅仅是继承其父类Cocos2dxActivity的实现罢 了。</p>
<p><font face="Courier New">// HelloCpp.java<br />
	public class HelloCpp extends Cocos2dxActivity{<br />
	&nbsp;&nbsp;&nbsp; protected void onCreate(Bundle savedInstanceState){<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.onCreate(savedInstanceState);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p>我们来看Cocos2dxActivity类。</p>
<p><font face="Courier New">// Cocos2dxActivity.java</font></p>
<p><font face="Courier New">@Override<br />
	protected void onCreate(final Bundle savedInstanceState) {<br />
	&nbsp;&nbsp;&nbsp; super.onCreate(savedInstanceState);<br />
	&nbsp;&nbsp;&nbsp; sContext = this;<br />
	&nbsp;&nbsp;&nbsp; this.mHandler = new Cocos2dxHandler(this);<br />
	&nbsp;&nbsp;&nbsp; this.init();<br />
	&nbsp;&nbsp;&nbsp; Cocos2dxHelper.init(this, this);<br />
	}&nbsp;</font></p>
<p><font face="Courier New">public void init() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // FrameLayout<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ViewGroup.LayoutParams framelayout_params =<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,<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; ViewGroup.LayoutParams.FILL_PARENT);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FrameLayout framelayout = new FrameLayout(this);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; framelayout.setLayoutParams(framelayout_params);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Cocos2dxGLSurfaceView<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.mGLSurfaceView = this.onCreateView();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230;add to FrameLayout<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; framelayout.addView(this.mGLSurfaceView);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Set framelayout as the content view<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setContentView(framelayout);<br />
	}</font></p>
<p>从上面代码可以看出，onCreate调用的init方法才是Cocos2dxActivity初始化的核心。在init方法 中，Cocos2dxActivity创建了一个Framelayout实例，并将该实例作为content View赋给了Cocos2dxActivity的实例。Framelayout实例也并不孤单，一个设置了Cocos2dxRenderer实例的 GLSurfaceView被Added to it。而Cocos2d-x引擎的初始化已经悄悄地在这几行代码间完成了，至于初始化的细节我们后续再做分析。</p>
<p>接下来是onResume方法，它的实现如下：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; protected void onResume() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onResume();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cocos2dxHelper.onResume();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.mGLSurfaceView.onResume();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>onResume调用了View的onResume()。</p>
<p><font face="Courier New">// Cocos2dxGLSurfaceView：<br />
	&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; public void onResume() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onResume();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.queueEvent(new Runnable() {<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 run() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleOnResume();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>Cocos2dxGLSurfaceView将该事件打包放到队列里，扔给了另外一个线程去执行（后续会详细说明这个线程），对应的方法在 Cocos2dxRenderer class中。</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; public void handleOnResume() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cocos2dxRenderer.nativeOnResume();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>Render实际上调用的是native方法。</p>
<p>&nbsp;&nbsp;&nbsp; <font face="Courier New">JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeOnResume() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (CCDirector::sharedDirector()-&gt;getOpenGLView()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCApplication::sharedApplication()-&gt;applicationWillEnterForeground();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>applicationWillEnterForeground方法在你的AppDelegate.cpp中；</p>
<p><font face="Courier New">void AppDelegate::applicationWillEnterForeground() {<br />
	&nbsp;&nbsp;&nbsp; CCDirector::sharedDirector()-&gt;startAnimation();//</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // if you use SimpleAudioEngine, it must resume here<br />
	&nbsp;&nbsp;&nbsp; // SimpleAudioEngine::sharedEngine()-&gt;resumeBackgroundMusic();<br />
	}</font></p>
<p><font face="Courier New">这里仅是重新获得了一下时间罢了。</font></p>
<p><b>五、Render Thread(渲染线程)</b> <b>- GLThread</b></p>
<p>游戏引擎要兼顾UI事件和屏幕帧刷新。Android的OpenGL应用采用了UI线程(Main Thread) +&nbsp; 渲染线程(Render Thread)的模式。Activity活在Main Thread(主线程)中，也叫做UI线程。该线程负责捕获与用户交互的信息和事件，并与渲染(Render)线程交互。比如当用户接听电话、切换到其他 程序时，渲染线程必须知道发生了 这些事件，并作出即时的处理，而这些事件及处理方式都是由主线程中的Activity以及其装载的View传递给渲染线程的。我们在Cocos2dx的框 架代码中看不到渲染线程的诞生过程，这是因为这一过程是在Android SDK层实现的。</p>
<p>我们回顾一下Cocos2dxActivity.init方法的关键代码：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // Cocos2dxGLSurfaceView<br />
	&nbsp;&nbsp;&nbsp; this.mGLSurfaceView = this.onCreateView();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // &#8230;add to FrameLayout<br />
	&nbsp;&nbsp;&nbsp; framelayout.addView(this.mGLSurfaceView);<br />
	&nbsp;&nbsp;&nbsp; this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; // Set framelayout as the content view<br />
	&nbsp;&nbsp;&nbsp; setContentView(framelayout);</font></p>
<p>Cocos2dxGLSurfaceView是 android.opengl.GLSurfaceView的子类。在android 上做原生opengl es 2.0编程的人应该都清楚GLSurfaceView的重要性。但渲染线程并非是在Cocos2dxGLSurfaceView实例化时被创建的，而是在 setRenderer的时候。</p>
<p>我们来看Cocos2dxGLSurfaceView.setCocos2dxRenderer的实现：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; public void setCocos2dxRenderer(final Cocos2dxRenderer renderer) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.mCocos2dxRenderer = renderer;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.setRenderer(this.mCocos2dxRenderer);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>setRender是Cocos2dxGLSurfaceView父类GLSurfaceView实现的方法。在Android SDK GLSurfaceView.java文件中，我们看到：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">public void setRenderer(Renderer renderer) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; checkRenderThreadState();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mEGLConfigChooser == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mEGLConfigChooser = new SimpleEGLConfigChooser(true);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mEGLContextFactory == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mEGLContextFactory = new DefaultContextFactory();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mEGLWindowSurfaceFactory == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	<b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mRenderer = renderer;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mGLThread = new GLThread(mThisWeakRef);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mGLThread.start();</b><br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>GLThread的实例是在这里被创建并开始执行的。至于渲染线程都干了些什么，我们可以通过其run方法看到：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void run() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setName(&quot;GLThread &quot; + getId());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (LOG_THREADS) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.i(&quot;GLThread&quot;, &quot;starting tid=&quot; + getId());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; guardedRun();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (InterruptedException e) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // fall thru and exit normally<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } finally {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sGLThreadManager.threadExiting(this);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p>run方法并没有给我们带来太多有价值的东西，真正有价值的信息藏在guardedRun方法中。guardedRun是这个源文件中规模最为庞 大的方法，但抽取其核心结构后，我们发现它大致就是一个死循环，以下是摘要式的伪代码：</p>
<p><font face="Courier New">while (true) {<br />
	&nbsp;&nbsp; synchronized (sGLThreadManager) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while (true) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;. &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (! mEventQueue.isEmpty()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; event = mEventQueue.remove(0);<br />
	&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; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;<br />
	&nbsp;&nbsp; }//end of synchronized (sGLThreadManager)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (event != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; event.run();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; event = null;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br />
	&nbsp;&nbsp; }&nbsp;&nbsp;</font></p>
<p><font face="Courier New">&nbsp;&nbsp; if needed<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);</font></p>
<p><font face="Courier New">&nbsp;&nbsp; if needed<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; view.mRenderer.onSurfaceChanged(gl, w, h);</font></p>
<p><font face="Courier New">&nbsp;&nbsp; if needed<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; view.mRenderer.onDrawFrame(gl);<br />
	}</font></p>
<p><font face="Courier New">在这里我们看到了event、Renderer的三个回调方法onSurfaceCreated、onSurfaceChanged以及 onDrawFrame，后续我们会对这三个函数做详细分析的。</font></p>
<p><b>六、游戏逻辑的入口</b></p>
<p>在HelloCpp的Classes下有好多C++代码文件（涉及具体的游戏逻辑），在HelloCpp的android project jni目录下也有Jni胶水代码，那么这些代码是如何和引擎一起互动生效的呢？</p>
<p>上面讲到过，涉及到画面的一些渲染都是在GLThread中进行的，这涉及到onSurfaceCreated、 onSurfaceChanged以及onDrawFrame三个方法。我们看看 Cocos2dxRenderer.onSurfaceCreated方法的实现，该方法会在Surface被首次渲染时调用：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.mLastTickInNanoSeconds = System.nanoTime();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>该方法继续调用HelloCpp工程jni目录下的nativeInit代码:</p>
<p><font face="Courier New">void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv*&nbsp; env, jobject thiz, jint w, jint h)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (!CCDirector::sharedDirector()-&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; view-&gt;setFrameSize(w, h);</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; else<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ccGLInvalidateStateCache();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCShaderCache::sharedShaderCache()-&gt;reloadDefaultShaders();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ccDrawInit();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCTextureCache::reloadAllTextures();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCNotificationCenter::sharedNotificationCenter()-&gt;postNotification(EVENT_COME_TO_FOREGROUND, NULL);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCDirector::sharedDirector()-&gt;setGLDefaultValues();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>这似乎让我们看到了游戏逻辑的入口了：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; CCEGLView *view = CCEGLView::sharedOpenGLView();<br />
	&nbsp;&nbsp;&nbsp; view-&gt;setFrameSize(w, h);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; AppDelegate *pAppDelegate = new AppDelegate();<br />
	&nbsp;&nbsp;&nbsp; CCApplication::sharedApplication()-&gt;run();</font></p>
<p>继续追踪CCApplication::run方法：</p>
<p><font face="Courier New">int CCApplication::run()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; // Initialize instance and cocos2d.<br />
	&nbsp;&nbsp;&nbsp; if (! applicationDidFinishLaunching())<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return -1;<br />
	}</font></p>
<p>applicationDidFinishLaunching，没错这就是游戏逻辑的入口了。我们得回到Samples代码目录中去找到对应方法 的实现。</p>
<p><font face="Courier New">//cocos2d-x-2.2.2/samples/Cpp/HelloCpp/Classes/AppDelegate.cpp</font></p>
<p><font face="Courier New">bool AppDelegate::applicationDidFinishLaunching() {</font><br />
	<font face="Courier New">&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;setOpenGLView(pEGLView);<br />
	&nbsp;&nbsp;&nbsp; CCSize frameSize = pEGLView-&gt;getFrameSize();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // turn on display FPS<br />
	&nbsp;&nbsp;&nbsp; pDirector-&gt;setDisplayStats(true);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // set FPS. the default value is 1.0/60 if you don&#39;t call this<br />
	&nbsp;&nbsp;&nbsp; pDirector-&gt;setAnimationInterval(1.0 / 60);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // create a scene. it&#39;s an autorelease object<br />
	&nbsp;&nbsp;&nbsp; CCScene *pScene = HelloWorld::scene();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // run<br />
	&nbsp;&nbsp;&nbsp; pDirector-&gt;runWithScene(pScene);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return true;<br />
	}</font></p>
<p>的确，在<font face="Courier New">applicationDidFinishLaunching中我们做了很多引擎参 数的设置。</font>接下来大管家CCDirector实例登场，并运行了HelloWorld Scene的实例。但这依旧是初始化的一部分，虽然方法名让人听起来像是某种持续连贯行为：</p>
<p><font face="Courier New">//cocos2d-x-2.2.2/cocos2dx/CCDirector.cpp</font></p>
<p><font face="Courier New">void CCDirector::runWithScene(CCScene *pScene)</font><br />
	<font face="Courier New">{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font><br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; pushScene(pScene);<br />
	&nbsp;&nbsp;&nbsp; startAnimation();<br />
	}</font></p>
<p><font face="Courier New">void CCDisplayLinkDirector::startAnimation(void)</font><br />
	<font face="Courier New">{<br />
	&nbsp;&nbsp;&nbsp; if (CCTime::gettimeofdayCocos2d(m_pLastUpdate, NULL) != 0)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCLOG(&quot;cocos2d: DisplayLinkDirector: Error on gettimeofday&quot;);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; m_bInvalid = false;<br />
	}</font></p>
<p>两个方法均只是初始化了某些数据成员变量，并未真正将引擎驱动起来。</p>
<p><b>七、驱动引擎</b></p>
<p>之所以游戏画面是运动的，那是因为屏幕以较高的帧数刷新的缘故，这样人眼就会看到连续的动作，就和电影的放映原理是一样的。在Cocos2d-x 引擎中这些驱动屏幕刷新的代码在哪里呢？</p>
<p>我们回顾一下之前谈到的GLThread线程，我们说过画面渲染的工作都是由它来完成的。GLThread的核心是guardedRun函数，该 函数以&ldquo;死循环&rdquo;的方式调用Cocos2dxRender.onDrawFrame方法对画面进行持续渲染。</p>
<p>我们来看看引擎实现的Cocos2dxRender.onDrawFrame方法：</p>
<p><font face="Courier New">public void onDrawFrame(final GL10 gl) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * FPS controlling algorithm is not accurate, and it will slow down FPS<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * on some devices. So comment FPS controlling code.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; final long nowInNanoSeconds = System.nanoTime();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; final long interval = nowInNanoSeconds &#8211; this.mLastTickInNanoSeconds;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // should render a frame when onDrawFrame() is called or there is a<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &quot;ghost&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cocos2dxRenderer.nativeRender();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // fps controlling<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (interval &lt; Cocos2dxRenderer.sAnimationInterval) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // because we render it before, so we should sleep twice time interval<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>Thread.sleep</b>((Cocos2dxRenderer.sAnimationInterval &#8211; interval) / Cocos2dxRenderer.NANOSECONDSPERMICROSECOND);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (final Exception e) {<br />
	&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; this.mLastTickInNanoSeconds = nowInNanoSeconds;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>这个方法实现得比较奇怪，似乎修改过多次，但最后还是决定只保留了一个方法调用： Cocos2dxRenderer.nativeRender()。从注释掉的代码来看，似乎是想在这个方法中通过Thread.sleep来控制 Render Thread渲染的帧率。但由于控制的不理想，索性就不控制了，让guardedRun真正变成了dead loop。但从HelloCpp Sample运行时的状态显示，画面始终保持在60帧左右，让人十分诧异。据说Cocos2d-x 3.0版本重新设计了渲染这块的机制。(后记：在Android上虽然没有帧数控制，但真正的渲染帧率实际上还受到&quot;垂直同步&quot;信号 &#8211; vertical sync的影响。在游戏中，也许强劲的显卡迅速的绘制完一屏的图像，但是没有垂直同步信号的到达，显卡无法绘制下一屏，只有等vsync信号到达，才可以绘制。这样fps实际上要要受到操作系统刷新率值的制约）。</p>
<p>nativeRender从命名来看，这显然是一个C++编写的函数实现。我们只能到jni目录下寻找。</p>
<p><font face="Courier New">cocos2d-x-2.2.2/cocos2dx/platform/android/jni/ Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::CCDirector::sharedDirector()-&gt;mainLoop();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>nativeRender也很简洁，直接调用了CCDirector的mainLoop，也就是说每帧渲染过程中真正干活地是 CCDirector::mainLoop。到此我们终于找到了引擎渲染的驱动器：GLThead::guardedRun，以&ldquo;死循环&rdquo;的方式刷新着画面，让我们感受到&ldquo;动&rdquo;的魅力。</p>
<p><b>八、mainLoop</b></p>
<p>进一步我们来看看mainLoop所做的工作。mainLoop是CCDirector类的一个纯虚函数，CCDirector的子类CCDisplayLinkDirector真正实现了 它：</p>
<p><font face="Courier New">//CCDirector.cpp<br />
	void CCDisplayLinkDirector::mainLoop(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (m_bPurgeDirecotorInNextLoop)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_bPurgeDirecotorInNextLoop = false;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; purgeDirector();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; else if (! m_bInvalid)<br />
	&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; drawScene();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // release the objects<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCPoolManager::sharedPoolManager()-&gt;pop();<br />
	&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">void CCDirector::drawScene(void)</font><br />
	<font face="Courier New">{<br />
	&nbsp;&nbsp;&nbsp; // calculate &quot;global&quot; dt<br />
	&nbsp;&nbsp;&nbsp; calculateDeltaTime();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //tick before glClear: issue #533<br />
	&nbsp;&nbsp;&nbsp; if (! m_bPaused)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pScheduler-&gt;update(m_fDeltaTime);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; /* to avoid flickr, nextScene MUST be here: after tick and before draw.<br />
	&nbsp;&nbsp;&nbsp;&nbsp; XXX: Which bug is this one. It seems that it can&#39;t be reproduced with v0.9 */<br />
	&nbsp;&nbsp;&nbsp; if (m_pNextScene)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setNextScene();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmGLPushMatrix();</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;visit();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // draw the notifications node<br />
	&nbsp;&nbsp;&nbsp; if (m_pNotificationNode)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pNotificationNode-&gt;visit();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (m_bDisplayStats)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; showStats();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmGLPopMatrix();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; m_uTotalFrames++;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // swap buffers<br />
	&nbsp;&nbsp;&nbsp; if (m_pobOpenGLView)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pobOpenGLView-&gt;swapBuffers();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (m_bDisplayStats)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; calculateMPF();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>帧渲染由mainLoop调用的drawScene()完成，drawScene方法根据Scene下的渲染树，根据node的最新属性逐个渲染 node，并调整各个Node的调度定时器数据，细节这里就不详细说明了。</p>
<p><b>九、UI线程与GLThread的交互</b></p>
<p>用户的屏幕触控动作由UI线程捕捉到，该类事件需要传递给引擎，并由GLThread根据各个画面元素的最新状态重新绘制画面。UI线程负责处理用户交互 事件，并将特定的事件通知GLThread处理。UI线程通过Cocos2dxGLSurfaceView的queueEvent方法，将事件以及处理方 法传递给GLThread执行的。</p>
<p>Cocos2dxGLSurfaceView的queueEvent方法继承自其父类GLSurfaceView：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; public void queueEvent(Runnable r) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mGLThread.queueEvent(r);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>而GLThread的queueEvent方法实现如下：</p>
<p><font face="Courier New">public void queueEvent(Runnable r) {<br />
	&nbsp;&nbsp;&nbsp; if (r == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new IllegalArgumentException(&quot;r must not be null&quot;);<br />
	&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; synchronized(sGLThreadManager) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>mEventQueue.add(r);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sGLThreadManager.notifyAll();</b><br />
	&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;<br />
	} </font></p>
<p>该方法将event互斥地放入EventQueue，并通知阻塞在Queue上的线程取货。</p>
<p>运行着的GLThread实例在guardedRun中会从event队列中取出runnable event并run的。<br />
	&nbsp;&nbsp;<br />
	<font face="Courier New">while (true) {<br />
	&nbsp;&nbsp;&nbsp; synchronized (sGLThreadManager) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while (true) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mShouldExit) {<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;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (! mEventQueue.isEmpty()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>event = mEventQueue.remove(0);</b><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; }&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;&#8230;.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; if (event != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>event.run();</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; event = null;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br />
	&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	} </font></p>
<p>Activity的各种事件Pause、Resume、Stop以及View的各种屏幕触控事件都是通过queueEvent传递给GLThread执行的，比如：View的onKeyDown方法：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //Cocos2dxGLSurfaceView.java<br />
	&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch (pKeyCode) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case KeyEvent.KEYCODE_BACK:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case KeyEvent.KEYCODE_MENU:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.queueEvent(new Runnable() {<br />
	&nbsp;&nbsp;&nbsp;&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;&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;&nbsp;&nbsp;&nbsp; Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyDown(pKeyCode);<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; });<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true;<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; return super.onKeyDown(pKeyCode, pKeyEvent);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><b>十、小结</b></p>
<p>有了以上的对Cocos2d-x引擎的理解后，再编写游戏代码就更加游刃有余了，至少出现问题时，我们知道应该在哪里查找了。就像对汽车的发动机了如指掌 后，一旦发生动力故障，我们基本知道排除的方法。但对发动机了解的再透彻，也不能代表就能设计和生产出好车，游戏也是这样，对引擎了解是一码事，设计和实 现出好游戏是另外一码事。学习引擎只是编写游戏的起点而已。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/03/11/hello-cocos2dx/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
