Corona SDK 入門 part 1

Corona 是一個跨手機平台的SDK, 有免費版及付費版. 若你打算上架App Store, 就一定要用付費版. 官網

理論上可以用在商業程式或遊戲, 看過API後我認為比較偏遊戲開發.

Corona用的是Lua language, 是一種直譯語言, 在遊戲界應該尚算流行. 弱型別語言, 可以用類似物件導向的寫法, 但我覺得還是偏非物件導向, 要封裝及傳遞變數並不是很直覺.

開發環境我是用LDT, 設定上不算複雜, 但也繼承了一些Eclipse的毛病, 總是會遇到問題. 設定方法如下

1) Add a new interpreter for corona simulator in the Lua/interpreter preference page by selecting "add" and selecting the Corona Simulator 
executable as Interpreter executable, adding "-debug" to "Interpreter arguments" and uncheck the option "Accept -e as argument". 
2013-12-16_0004392013-12-16_000453 
2) You should still add the following code at the begging of your project: 
if (system.getInfo("environment")=="simulator") then
require("debugger")()
end 
2013-12-16_000630
3) Copy the debugger.lua file by clicking on the top menu
"Run/Debug Configuration" and then create a "Lua Attach to application"
launch configuration and click on the link "Lua Debbuger Client" and
select "Corona SDK/Ressources" folder. 
2013-12-16_000803
4) Then go to top menu "Run/Debug Configuration" and create a "Lua Application" launch configuration which will use the created Corona interpreter 
as runtime interpreter and launch it in debug mode.
2013-12-16_000837

來源

啟動LDT時記得用管理員權限, 不然你做上面4 steps時會有問題

UPDATE : Corona Editor 1.0 已推出, 自動完成功能 樂勝

每一個程式都要有main.lua, 裡面的variable 雖然用local宣告, 但我看文件覺得那其實是global. 因為Lua不需要main function, 所以main.lua裡的local, 其實整個main都可以用得到.

至於其他檔案看不看得到?要怎樣定義類別及分檔案?這就是我覺得lua很不物件導向的原因, part 2再續…

Advertisements

Android 開發Part4

.R

Android project裡面的values, layout等xml定義資源, 都會自動編譯成.R . 但Eclipse的ADK並沒有很好的處理, 在你引用.R時, Eclipse的自動建議會幫你import錯誤的Android.R. 但其實應該是你目前專案的才對, 這時候要自己輸入import my.package.R. my.package填你建立專案時的包裝名稱

Clean Project

也是跟資源相關係問題, 有時候就算你xml定義好, 也正確的import, 還是會有找不到資源的問題, 這時候可以做兩件事. 先檢查你的xml是否有語法問題. 確認都正確後, 利用Eclipse選單的Project>Clean..去重新編譯資源檔

Empty Adapter

當你想清除原本已綁定(inflate)的view物件, 例如spinner, 網路上找到的view.item.clear()跟view.setAdapter(null)都是錯誤的, 我不知道為甚麼他們要這樣寫, android從2.2.3到4.3.1的API都沒有這兩個定義. 後來我的做法是產生一個沒有內容的adapter, 例如沒有字串的字串陣列, 然後setAdapter(empty)

Localization

若你想支援Localization, 最好是從第一行程式開始就把所有字串都利用values裡的strings.xml參考, 不然之後會找字串找得很痛苦. 包括menu裡的字串也要從strings.xml讀取

GetView(row)

Adapter裡的一個方法, google提供的範例沒有說明原來方法的第二個參數其實是view object的cache (convertView), 檢查這個若不是null, 就直接回傳這個, 可以減少重覆inflate, 有較好的performance

Code Version

每一次想要上傳至Google Play都要改, 簡單的處理方法就是+1, 不然會上傳失敗

Export key

上傳Google Play一定要用的key, 產生時記得年期選100年或以上, 不然會上傳失敗. 雖然我不知道100年跟永久有甚麼分別, Google也不見得可以維持100年…

Android 開發Part3

前一篇Part2

Part3的內容 – asynchronous task, view inflation, file I/O.

AsyncTask

為了不影響介面反應, 長時間執行事件需要在背景執行, 利用AsyncTask是不錯的方法。

先看一下官方文件, 這裡最重要的就是泛型宣告, 三個泛型分別用在執行, 進度, 完成三個不同的事件處理器. 這裡要先了解一下AsyncTask的生命週期. 在AsyncTask裡啟動後, 首先會執行doInBackground, 這就是背景方法, 長時間事項要在這個handler裡面進行, 系統會安排背景執行緒去處理. 過程中onProgressUpdate會被呼叫, 用來通知UI事件還在處理中, 這個方法可以不實作. 最後在doInBackground完成後, onPostExecute會被呼叫, 這方法通常是用來把結果顯示於介面. onProgressUpdate和onPostExecute都是介面執行緒負責, 所以可以用來更新介面.

有這個了解之後, 就可以回到泛型, 第一個是doInBackground的參數陣列物件的型別, 第二個是onProgressUpdate的, 第三個是doInBackground傳給onPostExecute的運作結果, 所以doInBackground的return type會跟onPostExecute的參數相同.

這就是AsyncTask的主要功能, 接下來看幾段sample code

MainActivity.cs 產生並啟動AsyncTask

MyAsyncTask task = new MyAsyncTask(this);

task.execute(“Hello”, “World”);

MyAsyncTask.cs定義MyAsyncTask

Public class MyAsyncTask extends AsyncTask<String, Void, String>

{

//skip constructor def

@Override

protected String doInBackground(String… params)

{

String hello = params[0];

String world = params[1];

return hello + “ “ + world;

}

@Override

protected void onPostExecute(String result)

{

TextView view = mainActivity.findViewById(R.Id.mytextview);

view.SetText(result);

}

}

這兩段程式, 首先mainActivity會建立並呼叫MyAsyncTask, 在constructor把自己傳給MyAsyncTask, 讓task可以更新介面, 並給兩個字串參數. MyAsyncTask把兩個參數組合並把結果顯示在一個TextView.

資料綁定Inflation

Android把資料綁定稱為inflation, 資料綁定就是把資料利用指定的介面模版, 是應用程式常見的概念. Android把模版看成是氣球, 把資料吹進去成型, 是很貼切的稱呼.下面是一個最精簡的sample

MyActivity.cs

public class MyActivity extends Activity

{

ArrayAdapter<String> adapter = new ArrayAdapter<String> (this, android.R.layout.simple_spinner_item, new String[] {“water”,“coffee”, “tea”});

Spinner s = findViewById(R.Id.spinner1);

s.setAdapter(adapter);

}

這個程式碼會把三個字串送進一個spinner, spinner是類似下拉式選單的元件.

Inflation其實需要三種物件互相配合, View, Data, Adapter, View負責顯示, Data記錄內容, Adapter就是負責把Data轉成View可以使用的介接器. 這個例子使用Android內建的ArrayAdapter介接, 配合R.layout.simple_spinner_item為Spinner的每一個row的模版. 下一次我會介紹如何使用自訂layout和自建Adaptor.

File I/O

Android有很多種資料儲存方式, 例如SQLite和Preference, 這裡先介紹File I/O.

寫入檔案

FileOutputStream fs = myActivity.openFileOutput(filename, Content.MODE_PRIVATE);

OutputStreamWriter fwrite = new OutputStreamWriter(fs);

fwrite.write(“hello world”);

fs.close();

利用openFileOutput新增一個名為filename的檔案, 並且把Access Control設定為PRIVATE, 內建還有好幾種Access權限, 按需求而選. 寫入字串後關閉.

讀取檔案, 基本上跟寫入相反就可以

InputStream fs = openFileInput(filename);

InputStreamReader fread = new InputStreamReader(fs);

BufferedReader freadBuffered = new BufferedReader(fread);

StringBuffer bffr = new StringBuffer();

String temp = null;

while((temp = freadBuffered.readLine()) != null)

{ bffr.append(temp+”\n”); }

fs.close();

Part 4會介紹如何把Part1~3的功能合起來使用, 例如使用Listener管理AsyncTask的結果, Inflate自訂Layout等

Release Mode程式, 利用Ildasm除錯

如果.NET程式遇到圖一這種情況, 可以利用事件檢視器和Ildasm工具找出問題的原因.

首先, 打開 控制台>…>事件檢視器, 路徑會因為作業系統而改變, Win7是控制台>系統及安全性>檢視事件記錄檔

找出錯誤記錄, 圖二, 記錄描述解讀如下

P1 出錯的程式名稱, P4 出錯的組件, P7 組件裡出錯的方法, P9 錯誤名稱

根據圖二, 出錯的是system.windows.forms, 錯誤為argumentnullexception

接下來就要找出是那一個方法出錯, 先打開Ildasm反組譯工具, 可能找看看下列位置

% WindowsRoot%\Microsoft.NET\Framework\version\

%Program Files%\Microsoft SDKs\Windows\version\bin

我是在%Program Files%\Microsoft SDKs\Windows\v7.0A\bin 找到的

執行Ildasm, File>Open, 把P4的組件打開, 打開後點選View>MetaInfo>Show!

會打開一個文字檔, 裡面是該組件的meta資料, 利用Find功能找出有問題的方法

搜尋 0600+P7, 根據圖二, 這裡要搜尋的是06001521, 找到一個名稱Method #467 (06001521) NotifyEnter的方法

也就是說, 程式裡導致錯誤的原因跟system.windows.forms NotifyEnter方法相關

P.S. Report to Microsoft一點用都沒有, 還是靠自己吧

Android 開發Part2

介面設計

Package Explorer裡, 在Android專案的res資料夾, right click->New->Android XML file. 選一種Layout (最基本的是LinearLayout), 點Next

點Next之後, 會出現一個設定Qualifers的介面, 這裡要小心, 如果你不知道甚麼是Qualifers, 就先不要設定, 不然啟動Activity時會有ResourcesNotFound Exception. 那是因為你有加Qualifers限制, 而AndroidManifest.xml的設定沒匹配, 就會找不到Resources, 也就是你的layout.xml會找不到.

Android介面物件的大小, 單位是dp, 例如一個ImageView的Height = 100dp. 錯了單位compiler不會過. Padding和Margin的單位是dip, 例如marginBottom=”10dip”.

要對齊元件, 要使用Container(或Layout)的Gravity屬性

如果發現Graphical顯示的結果, 長寬怪怪的, 或者orientation不對,  請在畫面設計工具上方設定target device, orientation和主題

啟動Main Activity

跟整個程式有關的設定, 都在AndroidManifest.xml.

先設定本程式會用到的資源uses-permission, 例如android.permission.INTERNET就是需要上網

AndroidManifest.xml裡應該有註明本程式所有的Activity, 一個程式裡的每一個頁面都是獨立的Activity. 要指定一個Activity作為初始, 在<activity/>裡加入<intent-filter/>, filter裡有放兩個elements, 分別是<action android:name=”android.intent.action.MAIN” />和<category android:name=”android.intent.categroy.LAUNCHER”/>

資源管理

layout的xml, drawable如圖片檔, values的xml, 都是放在res裡的資源.

加入圖片檔, 使用一般的複製貼上就可以, 但android對不同resolution的畫面可以使用不同的圖, 分別存放在後綴hdpi, ldpi, mdpi, xhdpi.

顏色定義, 命名好像規定要用Color當後綴, value可以是#RRGGBB或#AARRGGBB

利用資源id除錯. 寫以下try, catch

try

{  //do things with resources }

catch(Exception e)

{

Log.e(“ERROR”, e.toString());

}

Log會告訴你出錯的resource ID, 進去gen>package.yours>R.java 裡面有寫

內部Intent

利用內部Intent在程式內切換頁面

主動的Activity

Intent turnPageIntent = new Intent(view.getContext(), NextActivity.class);

startActivityForResult(turnPageIntent, 0);

這樣就會叫起下一個Activity

被呼叫的Activity

Intent returnIntent = new Intent();

setResult(RESULT_OK, returnIntent);

finish();

這樣就會回到前一個Activity

一定要用到的Event

在Activity裡

@Override

Public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.yourLayout);

}

按鍵事件

Button theButton = (Button)findViewById(R.id.buttonId);

theButton.setOnClickListener(//your code);

Android 開發PART1 環境架設

首先來下載三個東西, 全部免費

1. Java SE SDK – 如果你不知道你有沒有裝, 那就是沒有裝了, 去下載最新版本吧

2. Android SDK – 包含API, 模擬器等

3. Eclipse for Java Developer – 知名整合開發工具

然後安裝 JavaSE -> Android SDK , 要記一下Android SDK的安裝目標路徑, 等一下會用到

(安裝Android SDK時如果它表示找不到JavaSE, 點Back, 再點Next)

Eclipse是不用安裝的, 直接解壓啟動就可以. 第一次啟動後可以看看教學甚麼的, 最後選進入workbrench

然後要安裝ADT插件, 在Eclipse的選單->Help->Install New Software…->點Add

Name隨便取, Location用這個https://dl-ssl.google.com/android/eclipse/

然後可能要等一下, 出現可安裝的項目, 就裝下去吧

安裝好就在Eclipse選單->window->Preferences, 左邊會有一項Android

點下去後在右邊SDK Location填入Android SDK的路徑, 剛才說要記一下的那個

然後再到window->Android SDK and AVD Manager, 新增你用來測試的模擬器

我指定的版本是android 2.2, 你可以根據你的目標device去設定

最後一步就是建立專案, Eclipse選單->File->New->Project…->Android->Android Project, Next

輸入Project Name, 選一個Build Target, 最好跟你剛才的AVD版本一樣

Application Name 就是程式的名字, 好像deploy後會顯示在Android上, 例如Hungry Shark Part 2

Package Name就是Java的打包名字, 例如com.fgol.sharkpart2

通常會選擇Create Activity, Android把一支應用程式叫做Activity, Create Activity的意思就是建立一個啟動應用程式

像C的Main, 或者.Net的啟動專案的Application.Run. 例如SharkRun, 他會幫你建立一個class SharkRun.java

Min SDK Version可以不用管, Next, 然後下一個是Test專案的建立, 我暫時沒用, 直接Finish

建立好之後可以試著compile, 如果他說你缺library或者找不到aapt.exe, 存儲專案, 關閉eclipse

重新開啟eclipse後, 到Install New Software…那邊, 重新安裝/更新一次ADT, 應該就OK了

感動的畫面, 花了我一個小時去解那些低能的整合問題

感動的成功畫面

PS 應該還有一步說定你的測試用模擬器(AVD), 不過我忘記在那了, 因為太簡單就沒記住了

Use Memcached in .NET application with Linq

First of all, get the memcached, I user win32 version. http://jehiah.cz/projects/memcached-win32/ version 1.2.1
Secondly, get a client for memcached, I use this one http://memcachedproviders.codeplex.com/ version 1.2
Get the document for the client too, I start without it and I think you should read it in advance.
I will share some of my experience too.

The official documentation is a bit poor, I am going to give an introduction on the main points.

1. Memcached is a universal distributed cache.

i.e. it can run multiple instances and appears to the programmer as one cache server which is programming language independent. A really large hashtable.

2. It stores no object, you’ll have to serialze every object that you want to cache.

3. It won’t throw any exception no matter what happens.

I will focus on how to use the memcached server, not how to setup since you should be run it without a problem.

Setup the client in your application. Unarchive and add reference to all three .dll in your project.

Then configure your web.config or app.config like this, inside <configSections> tag, add the following:

<section name="cacheProvider" type="MemcachedProviders.Cache.CacheProviderSection, MemcachedProviders"
 allowDefinition="MachineToApplication" restartOnExternalChanges="true"/>
 <sectionGroup name="enyim.com">
 <section name="memcached"
 type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching"/>
 </sectionGroup>
 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>

This tells the application that there will be a section with tag <cacheprovider> and <log4net>. Also will have a section with tag <enyim.com> who has a child tag <memcached> inside. You can just copy and paste the above, it should work.

Then add the detail settings in <configuration> tag.

<cacheProvider defaultProvider="MemcachedCacheProvider">
 <providers>
 <add name="MemcachedCacheProvider" type="MemcachedProviders.Cache.MemcachedCacheProvider, MemcachedProviders"
 keySuffix="_MySuffix_" defaultExpireTime="2000"/>
 </providers>
 </cacheProvider>

 <enyim.com>
 <memcached>
 <servers>
 <add address="192.168.1.2" port="11211" />
 </servers>
 <socketPool minPoolSize="10" maxPoolSize="100" connectionTimeout="00:00:10" deadTimeout="00:02:00" />
 </memcached>
 </enyim.com>

 <log4net>
 <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
 <layout type="log4net.Layout.PatternLayout">
 <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}]- %message%newline" />
 </layout>
 </appender>
 <root>
 <priority value="WARN"/>
 <appender-ref ref="ConsoleAppender">
 <filter type="log4net.Filter.LevelRangeFilter">
 <levelMin value="WARN"/>
 <levelMax value="FATAL"/>
 </filter>
 </appender-ref>
 </root>
 </log4net>

You can change the default expire time. Server address is the host which you run memcached and 11211 is the default port.

Caching string and serialized object should be something you already know. For simple class, just add [Serializable] to the class.

Let’s see how it works with LINQ.

To serialize LINQ object, open .dbml design view, select serialize mode to unidirectional.

Then you will need to use DataContractSerializer to actually serialize your object. For more information.

There are a few serializer, XML, binary, JSON, etc. I am using DataContractJsonSerializer since it should be faster than XML. The following code snippet serialize and cache the object array to memcached.

//create a serialize provider with specify object type
DataContractJsonSerializer serializeProvider = new DataContractJsonSerializer(typeof(myClass[]));
//a memory stream as buffer since the provider do not directly generate string
 using (MemoryStream ms = new MemoryStream())
 {
 serializeProvider.WriteObject(ms, myClassArrayObject);
 byte[] l_buffer = new byte[ms.Length];
 ms.Position = 0;
//If the string is out of integer range, skip it, you can do whatever you need to handle this
 if (ms.Length < int.MaxValue)
 {
 ms.Read(l_buffer, 0, (int)ms.Length);
 string l_serialized = Encoding.Unicode.GetString(l_buffer);
//remove existing cache with the same key
 DistCache.Remove("keyString");
 DistCache.Add("keyString", l_serialized);
 }
 else
 {
 throw new Exception("object data large.");
 }
 }

Then you can check whether you succussfully cache it with telnet.

Telnet to your server and type the command ‘stats’, it will then shows the status of your memcached.

curr_items is the number of items inside memcached.

There is some common practices in choosing the hash key but that is another topic.

I am using Application:Method:Parameters:Salt and then hash it with SHA256, convert it to base64.

This should be enough for testing purpose, you can come up with your own hash decision.

Then we will get the cache and deserialize it to our object.

string l_serialized = (string)DistCache.Get("keyString");
 DataContractJsonSerializer serializeProvider = new DataContractJsonSerializer(typeof(myClass[]));
 using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(l_serialized)))
 {
 myClass[] members = (myClass[])serializeProvider.ReadObject(ms);
 }

You can do this with your object, object array, object collection, etc.

But remember you are in a different data context if you get you data from cache.

This will need some workaround to update or delete data with Linq.

I will cover this part later in another article.

中文版