2012/11/27

[Firefox OS] HTML 內嵌 SVG 動畫實例

前言:這些實作都已經包含在最新的 Firefox OS 裡面,有興趣的可以 check 最新的 code 出來玩。

最近接到了一個需要變更設計的 issue,我看到這個設計愣了一下,主要的原因是因為要做出這樣的設計用 HTML + CSS 還真的需要想一下如何實作。

主要更改的地方是 Firefox OS 的兩個元件:Lockscreen 跟 Dialer。

這兩個元件主要的設計概念都是希望有一條像是橡皮筋(或跳繩)的線上下的跳動,並且在往上跳動的時候露出下面的兩個按鈕,提示使用者可以把這條橡皮筋往上滑,接下來按下按鈕解鎖。我們這邊只討論如何實作,不討論視覺設計 :P



如上圖所示,上面的那條弧線是需要動態的上下彈動,如果用 HTML + CSS 的話有幾種方法可以嘗試做到跟缺點:
  1. 上一個 canvas,然後把這條線在 canvas 上面不斷的重繪(缺點:這樣要不停的計算跟重繪)
  2. 用超級多張 png 不停的置換圖檔(缺點:要生出超級多圖)
  3. 先畫一張弧線,接著用 CSS 的 Transform 更改他的 scale()(缺點,接近中間的時候整個圖形就會被壓得很扁)
考慮過 HTML + CSS 的解法之後,以上的解法似乎都不太好。這個時候我就開始考慮用 SVG 來做這件事情。但是用 SVG 來做這件事情其實是蠻冒險的,因為在這期間問了幾個同事他們都沒有測試過 SVG 在 Firefox OS 真正的手機上的詳細效能。所以收到這個設計的時候我先寫信問了個對於整個 platform 比較熟的同事,然後因為 deadline 非常的趕,但是我又不能不確定效能狀況就下手,所以就先決定做個獨立可以同時在電腦跟手機都可以驗證的小型 app。

這個 app 要驗證的事情有兩件:
  1. 當用滑鼠(手指)按住拖曳,這個時候改變 SVG 的屬性讓他改變弧度的效能衝擊有多大
  2. 使用 SVG animation (SMIL) 效能到底如何
然而做這個 DEMO 幾乎也可以搞清楚要怎麼用 SVG 實作這個解鎖畫面了。在弧線的部分,採用 SVG 的 Path 搭配上 c (curveto) 參數可以達成弧線,動畫的部分則是用 SMIL 的 animate tag 完成。下面這個網頁就是驗證效能用的網頁(我只用過 Firefox 打開過,其他瀏覽器不知道有沒有支援):

http://yurenju.github.com/lockscreen-demo/wrapper.html

這邊只是用來驗證的網頁,所以會有一些小 bug。主要的功能就是往上拉的時候用 javascript 去改變 SVG d (data) 裡面的 c (curveto) 的參數,放開滑鼠的時候用 SMIL animate 把 curve 滑回原位。而下面有個連結 install lockscreen demo 用途是如果你用 Firefox OS 的手機點了這個連結就可以把這個 demo app 安裝到手機裡面。

很棒的是當我把這個 app 安裝到手機裡面,發現這樣實作的效能在手機上是完全可以接受的!既然可以接受那就大膽的把這樣的實作方式引入 Firefox OS。這邊有針對 lockscreen 的 commit


在 SVG 方面,首先利用 path tag 來劃出最原始的弧線。在 attribute d (data) 裡面用了兩個參數:M (moveto) 跟 C (curveto),moveto 用來指定 path 的起點,curveto 用來指定用來控制曲線的兩根桿子的弧度。SVG 1.1 Path 裡面有張圖讓 curve 的控制點比較好理解一點:



C 後面接的兩組 (x, y) 分別是兩個控制點的坐標,我們需要的大概像是最左上角那張圖的效果。attribute 'd' 裡面的最後一個參數則是曲線的終點。整個 attribute 長這樣:

M0,80 C100,150 220,150 320,80

第一個是曲線起始坐標,最後一個是曲線結束坐標,中間兩個則是控制點的坐標。

接下來說明 path 裡面的五個 animate tag。1 跟 3 是針對曲線的彎度變化,而 2 跟 4 則是針對透明度的變化。1-4 都是用於拖曳橡皮筋之後放開的動畫,而 (5) 則是當你不去碰橡皮筋時,他的彈跳提示動畫。請想像這一整組動畫:一條繩子彈上去後隨著重力掉下來,掉到地上之後會再彈跳幾下後靜止。下面講解比較複雜的第五組動畫:

先看到 values。用分號切分開來的話總共有五組數據:
  1. 起始的曲線數據 (Y=150)
  2. 第一次彈跳到最高的數據,兩個控制點的 Y 坐標都變少了讓整個圓弧的開口朝下 (Y=40)
  3. 回到最地上 (Y=150)
  4. 再次彈起來,但是幅度較低 (Y=100)
  5. 回到原點 (Y=150)
至於 keySplines 則是指定彈跳的 timing function,如果你用過 CSS animation,就跟 ease-in/out 那種差不多的東西,只是要直接指定數據,下面這張圖是 keySplines 設定 0.5 0 0.5 1 會產生的 timing function:


keySplines 裡面有四組數據,分別就是 1-2, 2-3, 3-4, 4-5 這四段動畫的 timing function。每一組數據裡面都是兩個控制點的坐標。

SVG 的部分大概就是這樣!這部分有很多需要細微調整的,有興趣的就留言一起討論吧。接下來是 Javascript 部分。這邊我就講一些 SVG + Javascript 要注意的小技巧
  • fill=freeze 功能為讓動畫結束之後停留在最後一格,不過這樣的話如果你想要用 mousemove 去逐漸改變曲線的外形時,你會發現這個屬性會讓整個 path 卡死。如果拿掉 fill=freeze 的話,因為我執行完動畫之後還要把曲線固定在最後一格,所以拿掉的話就導致動畫有閃爍的現象。 解法就是平常不用,等到要播放補間動畫的時候再把 freeze 加上去。
  • 用 beginElement(), endElement() 來播放、停止動畫
  • addEventListener endEvent 來處理動畫結束後的後續處理。
這邊的細節真的非常的多,如果你也想 HTML + SVG + Javascript 來實作的話,建議是要讀一下 SVG 跟 SMIL 的 spec,然後撿想要用的東西放在裡面,什麼不明白的事情就直接寫到 SVG 裡面看一下效果如何就是了。那個時候這個 commit 要上真是超級緊張的,因為這是我開始 contribute Firefox OS 以來最大的修改。結果上的時候還是有些小細節沒注意到,感謝同事的幫忙在 bug 還沒關之前就注意到這個低級錯誤然後讓我可以及時的推入 repository 了。

之後更複雜的是 Dialer 的部分。



如上圖所見左右兩邊各有一個會隨著弧線移動的兩個 spotlight,而線段上的顏色還多了紅色跟綠色線段。線段不同顏色方面,看遍了 SVG 的資料後比較方便的方法還是用多重的漸層並且把兩個漸層的 offset 設定成一樣,這樣就可以讓曲線有不同的顏色。至於隨著 curve 的 spotlight 則是透過 clipPath 作修剪遮罩,讓漸層只在部分的地方露出來即可。做完這次的 commit 我的 SVG 功力真的大增啊... Orz



這是在 Dialer 曲線用的漸層。2 跟 3 的 offset 都是一樣的,但是顏色卻用不一樣,這樣的技巧可以讓線段不會產生漸層。

這段是如何產生 spotlight 的方法。首先 path 不一樣的地方是 d 除了原本的 M 跟 C 以外,又加了 H V Z 分別用來畫出橫線、直線跟關閉 path 用。fill style 則套用上面的 #gradient-red 的漸層紅色,最後用 clipPath 的方式作剪裁遮罩。對綠色的部分也用相同的方法,最後通通拿去做動畫,就完成啦!

所以這是最後的結果:



Dialer 最後實作的結果在這邊,很可惜速度上並不是很好,目前看起來撥電話進來之後整隻手機的效率會下降許多,目前我們也正在改進這個問題。



這個 lockscreen 的 code 都在 github 上面,有興趣的可以抓下來玩玩 :-)

2012/10/29

Using Google APIs Client for Android

這是在十月 27, 28 日在高雄軟體園區舉辦的 MOPCON 我講的題目。原本演講的時候最後一段 OAuth 的部分是直接在 Source code 裡面講解。不過回來之後已經把投影片都補上了。

另外如果你也有使用 Google APIs Client 也可以交流一下,我發現好像還蠻少人在用的。

總之下面是投影片,請笑納 :D


2012/10/17

jscallgraph - Javascript Call Graph 靜態分析

最近用 xmind 追 code 追得很辛苦,跟幾位朋友討論過後,不知不覺就開始寫起了靜態分析用的程式。昨天先用 python 驗證了一下概念,深夜開完會議之後就決定用 node.js 來寫個程式會比起用 python 更為合適。目前已經放上 npm,要安裝只要你已經裝好了 node.js 跟 npm,輸入以下指令就可以安裝了。

$ sudo npm -g install jscallgraph
$ sudo apt-get install graphviz

第二行是安裝 graphviz,主要的用途是拿來產生分析完的圖形用的。使用上很簡單,舉例來說你想要對 Firefox OS 的 system app 裡面的 window_manager.js 作靜態分析,把檔案抓下來,用以下指令產生 dot file

$ jcg window_manager.js WindowManager > window_manager.dot

第一個參數是要分析的檔案,第二個參數是要分析的物件,這邊可以參考一下 window_manager.js 的檔案,裡面宣告了 WindowManager 物件,並且把相關 method 都包裝在裡面。

接下來用 graphviz 的 dot 指令產生 png 圖檔

$ dot -Tpng -o window_manager.png window_manager.dot


這樣就完成了!



Source code 放在 github, 請大家自由取用!另外這程式不是什麼 javascript 都可以分析,有興趣的請送 patch! :D

2012/10/15

[Firefox OS] 呼叫 MozActivity 的內部訊息流程

上週快結束的時候我一直在追蹤一個 Firefox OS 的 Bug #800169,後來發現不是 Gaia (Firefox OS 的 App 層) 之後,我就一路往下看到了 Gecko (Firefox OS 的 Runtime 層),當我覺得快要找出癥結的時候,這個 Bug 在 Nightly build 被別人解決了! XDD

不過趁著這個機會也把架構熟悉過了一遍,跟大家分享一下。

先解釋一下這個 bug,Firefox OS 的瀏覽器在把一個網頁加入到 home screen 的時候,加入 Home screen 的確認視窗會跳出來兩次。

首先我們就從 Browser 的 browser.js 開始,按下『加入至 home screen』後會使用下面的 API 呼叫加入 home screen 的 dialog 。

new MozActivity({
  name: 'save-bookmark',
  data: {
    type: 'url',
    url: this.currentTab.url,
    name: this.currentTab.title,
    icon: place.iconUri
  }
});

MozActivity 是怎麼呼叫 Dialog 的呢?經過追蹤,當你呼叫了 MozActivity 的時候,真正執行的是 B2G/gecko/dom/activities/src/Activity.cpp。當你找出這個地方後可以用 gdb 來確認是不是這裡,詳細的用法可以參考 Debugging B2G using gdb。Activity:Initialize 的最後面的程式碼是這樣:

nsresult rv;
mProxy = do_CreateInstance("@mozilla.org/dom/activities/proxy;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);

mProxy->StartActivity(this, options, window);
return NS_OK;

事實上這個時候 B2G 去調用了同一個目錄底下的 ActivityProxy.js,這個時候用 Child Process Message Manager 的  sendAsyncMessage 丟了用來開啟 Activity 的訊息出去。

cpmm.sendAsyncMessage("Activity:Start", { id: this.id, options: aOptions });
cpmm.addMessageListener("Activity:FireSuccess", this);
cpmm.addMessageListener("Activity:FireError", this);

ActivityService.jsm 的 receiveMessage 會接收到這個訊息,並且交由 this.startActivity 來處理之。而 startActivity 決定完成要用哪個 app 開啟這個 Activity 後,再用 system-message-internal 的 sendMessage 丟出一個名為 activity 的訊息。

let sysmm = Cc["@mozilla.org/system-message-internal;1"]
              .getService(Ci.nsISystemMessagesInternal);
if (!sysmm) {
  // System message is not present, what should we do?
  return;
}

debug("Sending system message...");
let result = aResults.options[aChoice];
sysmm.sendMessage("activity", {
    "id": aMsg.id,
    "payload": aMsg.options,
    "target": result.description
  },
  Services.io.newURI(result.description.href, null, null),
  Services.io.newURI(result.manifest, null, null));

最後到了 SystemMessageInternal.js 裡面的 sendMessage 最後會調用 _processPage 來開啟正確的 App。

let page = { uri: aPage.uri,
             manifest: aPage.manifest,
             type: aPage.type,
             target: aMessage.target };
debug("Asking to open  " + JSON.stringify(page));
Services.obs.notifyObservers(this, "system-messages-open-app", JSON.stringify(page));

而追蹤的 bug 的問題點就在這裡,這邊有兩個 match 的 page,所以他連續開啟了兩次 add to home screen 的 dialog。當我追蹤到這邊的時候,其實基本上已經找到問題的根源了。不過在跟別人討論的過程中突然發現有另外一個 bug 的 patch 已經解決這個問題,而且在 nightly 的 build 也不會有這個問題,所以我就沒繼續追蹤下去了。

藉由這次機會也從 Gaia 到 Gecko 看了一大圈,也算是很有收穫  :D

2012/09/28

HTML5 mobile app 練習

這幾天在練習 HTML5 mobile app,決定找一個 Android 底下的 app 來重刻成 HTML5 版本,我挑的是 News & Weather 的天氣部分。



這是一個可以根據你現在地點給你當地天氣的 app,按下右邊靠近上面的驚歎號圖示可以看今天一整天的溼度以及溫度變化,在圖形上面用手指滑動可以看指定時間的溼度。

我找了一下如果要每個小時的溼度與溫度的 Weather API 大多都要錢的,所以最後我接了 World Weather Online 的 API, 但是圖表就換成接下來五天的天氣氣溫變化,當作練習就是了。

成果如下,他只能跑在 Firefox for Android 上面,我沒有針對 Chrome/Safari 等 webkit 系列調整。

  


作完之後有一些感想...
  1. 如果不考慮相容性,現在的 CSS3 真的很強大,以前很詭異的排版方法現在都變得好排多了,我連右上角驚歎號圖示按下去顯示另外一頁都是用 CSS3 完成的,不需要 javascript。
  2. CORS 我還是沒搞定,最後用比較醜的 JSONP 解決。
  3. SVG 雖然好用,不過看起來效能在 mobile 上面還無法接受,目前應該還是用 Canvas  比較好。
  4. CSS/SVG 的漸層效果在 Firefox For Android 上面看起來很差,不知道爲什麼。
然後我很懶惰的沒有做 SVG path 的圓角,看起來好像沒有像是 rect 的 rx,ry 可以直接設定...

Source code 我放在 github 上面,有興趣的參考參考。

2012/09/06

cinnamon - 用 Javascript 高度客製化的 GNOME3


看了 小 Q 的 Facebook 之後,我也裝了 cinnamon 來用用。我之前一直推想 cinnamon 應該是把 GNOME3 裡面的 gnome-shell 的高度客製化,這次裝起來果然是這樣。一個很簡單的確定方式就是按 alt + F2 輸入 lg,就會從下面拉起一個 javascript console, debugger 跟 inspector 合一的開發工具 LookingGlass。


如果去查看 cinnamon 的套件檔案列表,也包含相當多的 Javascript。

因為很多人不知道,所以我再強調一次,整個 gnome-shell 都是 Javascript 寫的,所以 cinnamon 也都是整個用 Javascript 寫的。

比起 Ubuntu 整個砍掉重練用 compiz 重寫一個 unity 桌面環境,我覺得 cinnamon 用 GNOME3 既有的成果開發桌面環境是比較好的選擇,畢竟 Javascript 一來是比較多人會用的程式語言,另外在需要擴充功能的時候,Javascript 也可以很快的就寫好擴充。

更何況 cinnamon 就算是高度客製化他還是 GNOME3,很多資源都是可以共享的,而 unity 就等於完全新開一個獨立的分歧,恐怕濃厚的 Ubuntu 色彩會讓其他 distribution 不太願意使用。(不過 Fedora 18 似乎要把 unity 加入 repository 裡面 )

2012/08/01

ReactiveUI 概念篇

ReactiveUI 解決了我兩個問題,一個是把 App 中可以測試的部分擴大,二是解決 UI/Thread 糾結的問題。

你怎麼測試你的 app 的呢?底層我們當然可以透過 unit test 的方式進行,但是通常來說 UI 上面的東西都是比較難以測試的。通常我們會透過自動化 UI 的測試如 Sikuli 測試。但是這樣自動化 UI 常常會跟畫面綁定造成常會因為畫面變更而需要重新製作測試的 script。至於 MVC 裡面 Controller 的部分常因為跟 Event 綁定造成 Controller 其實是很難以測試的。ReactiveUI 是一種 MVVM (Model View ViewModel) 的 framework,其中由 ViewModel 取代 Controller 達到連 ViewModel 這個部份都可以測試,原因在後面讓我娓娓道來。

另外,我們這邊討論的 MVVM 只限於 ReactiveUI,我並沒有用過其他的 MVVM Framework。

MVC v.s. MVVM


MVC (Model-View-Controll) 是大家現在非常常用的 pattern,基本上就是把資料 (Model)、UI (View)、邏輯 (Controller) 分開來。在 Android 或一般的 WPF 裡面,我們會把事件處理也放在 Controller 裡面,Controller 可以作為聯繫 View 與 Model 的橋梁處理各式各樣的事情。所以 Controller 處理了邏輯跟事件。那 Controller 有辦法獨立進行測試嗎?這只有在你把邏輯與事件處理拆開後才有辦法單獨針對邏輯測試。

MVVM 是有別於 MVC 的另外一種設計方式,架構如下:



你可以能會說,這架構只是把 Controller 換成 ViewModel 而已,感覺不出來什麼差別阿?但其實不是的,ViewModel 反映了 View 裡面的資料。ReactiveUI 裡面的 ViewModel 包含了兩個重要的東西:屬性與命令

我用下面這個 Mockup 舉例,這是一個可以選擇要把內容發布到不同 Social Network 的小程式,左邊的 100x100 是自己的大頭照 Avatar。右上角的輸入框是要發布的內容,下面的下拉選單是要發布到哪裡的選項。


在 View 裡面的每個需要資料的元件,指定要跟 ViewModel 裡面的哪個屬性綁定,如同上面的例子,View 裡面要指定:
  1. 圖片的 Source 位置 => AvatarSource
  2. 輸入框的文字 => ContentText
  3. 下拉式選單的選項 => SocialNetworks ["Facebook", "Twitter", "Google+"]
  4. 下拉式選單選擇的項目 => SelectedSocialNetwork
ViewModel 與 View 的互動是雙向的,比如說 (1) 我在輸入框裡面打了 "I saw The Dark Knight Rises today!",這個時候 ViewModel 裡面的 ContentText 就會更新成新的文字敘述。(2) 當我點擊下拉選單,把要發表的 Social Network 從 Facebook 改成 Twitter 後,ViewModel 裡面的 SelectedSocialNetwork 也會被修改成新的數值。從另一個方向來看,(3) 如果我在程式裡面修改了 AvatarSource 的網址,這個時候 View 裡面的圖片就會自動更新。所以兩個方向的更新都是可以的。



至於命令 (Command) 則是獨立於 View Event 的部分。在上面這個例子裡面,按下 PostButton  之後就會執行 PostCommand。由於在 Command 裡面需要的東西,如要發表到哪個 Social Network,要發表的內容都已經綁定到 ViewModel 裡面的屬性了,所以不需要跟 View 互動,只要直接取用屬性及可,我們可以用簡單的虛擬碼表示:

PostCommand.Subscribe( _ => SendToSocialNetwork (SelectedSocialNetwork, Content))

由於屬性與命令都已經 Mapping 到了 ViewModel 了。這下子 ViewModel 的部分就可以被拿出來獨立測試。如同下面的虛擬碼:

Test {
    var ViewModel = new ViewModel()
    ViewModel.SocialNetworks.AddRange("Facebook", "Twitter", "Google+")
    ViewModel.ContentText = "Test Content"
    ViewModel.PostCommand.Execute()
}

如此一來 ViewModel 就可以測試了,而且架構非常乾淨!

而 ReactiveUI 裡面包含了 ReactiveCommand 與 ReactiveAsyncCommand,當你要處理非同步事件只要用 ReactiveAsyncCommand 即可。

這樣的架構是不是很乾淨呢?下次再來講 ReactiveUI 實際上要怎麼使用。

等不及的人,這邊是 ReactiveUI 的官網

2012/07/19

[H4] 人生自動化之 ifttt 與 on{x}

以下是 Hacking Thursday 聚會討論到的東西,沒看過太多人介紹,所以自己介紹一下。




IFTTT 其實是一個超簡單的東西,他連結了很多網路上各式各樣的服務,接下來就可以用 if this then that 的方式來做一些方便的事情,下面提供我使用的例子:

  1. 如果我在 flickr 上面對一張照片打了愛心,然後就把這張照片傳送到 Dropbox
  2. 如果 ecoupons.com 網站上有 ThinkPad 的特價,然後就寄信提醒我
  3. 如果有人在 Facebook 的照片上 tag 我,然後就把這張照片儲存到 Dropbox
相信這樣大家就很清楚 ifttt 的功能了。



那 on{x} 呢?這個服務跟 IFTTT 一樣,但是他更結合了手機的位置當作觸發條件。一樣用幾個例子讓大家了解這東西怎麼用。另外這是一個服務,而且你需要在你的 Android 安裝相對應的 app。
  1. 如果我離開家裡,就幫我關掉手機的 wifi
  2. 如果我離開公司,就寄送手機簡訊給我老婆上面寫『我要回家啦...』

瞭解了吧,另外 on{x} 更好的是可以用 javascript 寫程式,可謂是 IFTTT 的超級強化版。

2012/07/06

在 Windows 透過 ssh 啟動 Linux 上 API Server 的測試方式

如果你採用 Django, Node.js 或 Rails 這些網頁開發 framework 大多都不會真的在 Windows 上架設服務,通常會在 Mac 或 Linux 架設測試伺服器。開發 Windows 的時候比較好的解決方法就是在 VirtualBox (或其他軟體) 裝 Linux 連接到上面測試。

這樣做在我的狀況下會有些問題,因為我的測試項目會更改到一些儲存在資料庫的東西會影響到下次測試的結果。所以我希望每次測試都可以用新的資料庫環境測試。今天下午花了些時間設定好,分享一下。

基本上就是透過 putty 的指令介面 plink 來執行遠端 Linux 的指令,達到開啟關閉以及清除資料庫的功能。遠端的 script 大概長這樣:

#!/bin/bash

if [ "$1" = "start" ]; then
  echo "starting api service"
  #start web service > /dev/null 2>&1 &
  pidfile=$!
  echo $pidfile > ~/api.pid
else
  echo "stopping api service"
  kill `cat ~/api.pid`
  mongo api_db --eval "db.dropDatabase()"
fi


在 Windows 這邊可以到 putty 官網下載 plink.exe,先把它丟到家目錄去。遠端的指令要這樣執行:

%HOMEDRIVE%%HOMEPATH%\plink.exe -pw PASSWORD USERNAME@192.168.1.148 /PATH/TO/SCRIPT start

你可能注意到我沒有用 public key 的方式做,因為不知道為什麼在 linux 產生的 private key 好像沒辦法直接拿來用。後來懶得研究就直接用密碼了。

最後我們是要在 NUnit 跑的時候執行這個指令,所以你可以修改測試專案下面的 Program.cs 來達到執行指令的功能,下面我就貼出整個檔案了,基本上就是在前後加上執行的指令,ExecuteCommand 是從網路上貼來的。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;

namespace YourNamespace
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            string script = "\\plink.exe -pw PASSWORD USERNAME@192.168.1.148 /PATH/TO/SCRIPT ";
            string home = Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
            ExecuteCommandAsync(home + script + "start");
            Console.WriteLine("starting service, sleep 15 seconds");
            Thread.Sleep(15000);

            string[] my_args = { Assembly.GetExecutingAssembly().Location };

            int returnCode = NUnit.ConsoleRunner.Runner.Main(my_args);

            Console.WriteLine("stopping service...");
            ExecuteCommandSync(home + script + "stop");
            Console.WriteLine("service stopped");
            if (returnCode != 0)
                Console.Beep();
        }

        static public void ExecuteCommandSync(object command)
        {
            try
            {
                // create the ProcessStartInfo using "cmd" as the program to be run,
                // and "/c " as the parameters.
                // Incidentally, /c tells cmd that we want it to execute the command that follows,
                // and then exit.
                System.Diagnostics.ProcessStartInfo procStartInfo =
                    new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);

                procStartInfo.RedirectStandardOutput = true;
                procStartInfo.UseShellExecute = false;
                // Do not create the black window.
                procStartInfo.CreateNoWindow = true;
                // Now we create a process, assign its ProcessStartInfo and start it
                System.Diagnostics.Process proc = new System.Diagnostics.Process();
                proc.StartInfo = procStartInfo;
                proc.Start();
                // Get the output into a string
                string result = proc.StandardOutput.ReadToEnd();
                // Display the command output.
                Console.WriteLine(result);
            }
            catch (Exception objException)
            {
                // Log the exception
            }
        }

        static public void ExecuteCommandAsync(string command)
        {
            try
            {
                //Asynchronously start the Thread to process the Execute command request.
                Thread objThread = new Thread(new ParameterizedThreadStart(ExecuteCommandSync));
                //Make the thread as background thread.
                objThread.IsBackground = true;
                //Set the Priority of the thread.
                objThread.Priority = ThreadPriority.AboveNormal;
                //Start the thread.
                objThread.Start(command);
            }
            catch (ThreadStartException objException)
            {
                // Log the exception
            }
            catch (ThreadAbortException objException)
            {
                // Log the exception
            }
            catch (Exception objException)
            {
                // Log the exception
            }
        }
    }
}

2012/07/02

Jenkins, NUnit and VS 2010 express!

好吧你沒看錯主題,我要講的真的是 Visual C# 2012 express。

當要開始開發程式的時候,最重要的就是基礎架構先弄好之後開始,在 Windows application development 也一樣,但是跟其他平台開發有個不太小的不同,Windows 的開發軟體都是要錢的,所以你想要的功能基本上獨立開發者是無法負擔的。你還在想念 Android, Java, Linux, Mac app 開發一些基礎的東西嗎?像是 Code Coverage, Source Control (TFS), Lint (Static Code Analysis)在 Visual Studio 都是要錢的。

當然我們這種從 Android/Linux development 轉過來的人,當然是希望用 Open Source 的 solution。首先就從最基本的 Continues Integration 跟 unit test 講起。

請先安裝好 Visual C# 2010 express,Jenkins, Java runtime。

CI 當然就是選 jenkins 了,至於要搭配的東西就比較麻煩。在這邊我們採用 msbuild 建構專案,使用 NUnit 作 unit test。因為我們要在 Windows 上 build,所以 Jenkins 也要安裝在 Windows 上面。基本上就是從 jenkins 上面抓下來,找個風水好地放著,執行 java -jar jenkins.war 就行了。

接下來安裝 msbuild, nunit 這兩個 plugins。接下來就到 jenkins → Manage Jenkins → MSBuild 裡面填寫 MSBuild 正確的位置。


第二個是建立 NUnit 專案,你可以在 VC# 裡面 Tools → Extension Manager → Online Gallery → NUnit Test Application 安裝 NUnit 的 project template,怎麼寫 test case 就上網找一下吧。

當你建立專案的時候,他會很佛心的幫你建一個執行檔組態。執行他就可以獲得 NUnit Test report。

最後是 Jenkins job 的設定。在我們還沒有使用任何 Version Control 的時候,我們可以先從 VS project 裡面複製出專案的方式先代替 Clone project。


首先需要先清除 workspace 裡面遺留下來上次 build 過的東西。因為 Windows 刪除檔案跟目錄太囉嗦了,我這邊用 PowerShell 的指令代替。第二行則是用 xcopy 用複製的方式先把專案複製到定位。



第一個動作是用 MSBuild 去 build solution,這個 solution 包含了兩個 project,一個是主要的專案,另外一個是用來測試的專案。第二個動作則是跑由 NUnit project template 幫我們產生的 Test 執行檔,執行後就可以產生 TestResult.xml,第三個步驟就是引用這個檔案,讓 Jenkins 產生正確的報告在 Jenkins 的頁面上。

這樣就基本的把 Jenkins 設定好了。

2012/06/17

[Android] 用 Google APIs Client for Java 撰寫自己的 APIs Client (1)

你在 Android 上都怎麼實作連接 web service 的 client library 呢?在之前的 Project 中,通常都是用 Apache 的 HttpClient, HttpGet, HttpPost 刻自己的 client library,配合 google-gson 將 Json 轉換成 Java Object。但我一直希望可以有一個函式庫或 framework 可以處理這些事情,之前也看過 Restlet,但是似乎不是很好用。最近開始使用 Google APIs Client for Java 之後覺得這套 framework 似乎可以把事情處理的很好,就開始使用了。

這套 Library 對我來說的好處是
  1. 很方便的切換不同的 HTTP Method,不必每次要換 Method 都需要重寫很多源碼
  2. 自己處理瑣碎的事情,像是 gzip 壓縮, 傳入傳出 JSON 的 String-Object 轉換,URL builder 這些都很方便
  3. 減少源碼,有了上述的東西,源碼行數當然是大幅減少了。
另外它還有些特性我不太用到像是同時支援 JSON, XML、同時 support GAE, J2SE/J2EE 跟 Android,甚至可以替換 HTTP Client、Json library 或 XML library。


話說他的名字可能會讓你覺得這是一套給 Google APIs 專門使用的 client library,其實不然。除了可以透過它存取 Google Services,也可以利用它來建構自己的 client library。如果要初步的了解這套 library 可以先看 Google I/O 2011: Best Practices for Accessing Google APIs on Android,這篇裡面有些使用他的基本知識。

安裝很簡單,就直接照著 README 的說明,把該丟的 jar 檔放到 libs 裡面就行了。因為它同時支援 google app engine, Java SE, 跟 Android 開發環境,所以不同的狀況底下會有不同的 dependency,你可以參考我丟的 jar:


你可能剛開始就被它龐大的相依 library 嚇到了,事實上它可以透過 proguard 在 release app 的時候去除掉那些你從沒用到的地方,在 Yaniv Inbar 的投影片裡面也有提到它在他的例子中可以節省 95% 的空間。



怎麼樣開始用 Google APIs Client 寫自己的 APi wrapper 呢?下面參考 Google Task 跟 Google+ 的 API 提供了一個簡單的範本



package idv.yurenju.google.sample;

import java.io.IOException;

import com.google.api.client.http.HttpMethod;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.json.JsonHttpClient;
import com.google.api.client.http.json.JsonHttpRequest;
import com.google.api.client.json.JsonFactory;
import com.google.common.base.Preconditions;

public class SampleClient extends JsonHttpClient {
 public static final String DEFAULT_BASE_URL = "https://api.twitter.com/";

 public SampleClient(HttpTransport transport, JsonFactory factory) {
  super(transport, factory, DEFAULT_BASE_URL);
 }
}



DEFAULT_BASE_URL 是 web service 基礎的網址,往後所有的 query 都會基於這個網址。當建立你的 Client 的時候,你可以任意的替換 HttpTransport 與 JsonFactory。這邊我們用的是 NetHttpTransport 跟 JacksonFactory。

SampleClient client = new SampleClient(new NetHttpTransport(), new JacksonFactory());

要開始寫頭一個 API 時,首先要先建立 Data Model,這邊我們先用 Twitter 的 user_timeline 測試。用 http://api.twitter.com/1/statuses/user_timeline/yurenju.json 可以獲得我的 twitter 訊息。而每個 tweet 物件的屬性非常多,我們先用 text 與 created_at 這兩個訊息來測試。 在這邊建立 TweetModel 如下:
package idv.yurenju.google.sample;

import com.google.api.client.json.GenericJson;

public class TweetModel extends GenericJson {
 @com.google.api.client.util.Key("text")
 private String mText;
 
 @com.google.api.client.util.Key("created_at")
 private String mCreatedAt;

 public String getText() {
  return mText;
 }

 public void setText(String mText) {
  this.mText = mText;
 }

 public String getCreatedAt() {
  return mCreatedAt;
 }

 public void setCreatedAt(String mCreatedAt) {
  this.mCreatedAt = mCreatedAt;
 }
}

重點其實是繼承 GenericJson,而 6-10 行主用的功能是協助我們把 text, created_at mapping 到符合 Android 寫作風格的 mText, mCreatedAt,剩下的部份則是 setter/getter。接下來就可以在 SampleClient 裡面加入 API。

package idv.yurenju.google.sample;

import java.io.IOException;

import com.google.api.client.http.HttpMethod;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.json.JsonHttpClient;
import com.google.api.client.http.json.JsonHttpRequest;
import com.google.api.client.json.JsonFactory;
import com.google.common.base.Preconditions;

public class SampleClient extends JsonHttpClient {
 public static final String DEFAULT_BASE_URL = "https://api.twitter.com/";

 public SampleClient(HttpTransport transport, JsonFactory factory) {
  super(transport, factory, DEFAULT_BASE_URL);
 }
 
 public Users users() {
  return new Users();
 }
 
 public class Users {
  public GetTimeline getTimeline(String screenname) throws IOException {
   GetTimeline get = new GetTimeline(screenname);
   initialize(get);
   return get;
  }
  
  public class GetTimeline extends JsonHttpRequest {
   private static final String REST_PATH = "1/statuses/user_timeline/{screenname}.json";
   
   @com.google.api.client.util.Key("screenname")
   private String mScreenName;
   
   private GetTimeline(String screenname) {
    super(SampleClient.this, HttpMethod.GET, REST_PATH, null);
    Preconditions.checkNotNull(screenname);
    mScreenName = screenname;
   }
   
   public TweetModel[] execute() throws IOException {
    HttpResponse response = executeUnparsed();
    TweetModel[] result = response.parseAs(TweetModel[].class);
    return result;
   }
  }
 }
}


32 行提供了 REST 所用的 Path,其中 screenname 是會將變數替代入 Path 的參數,34-35 行的工作就是將 mScreenName mapping 到 screenname。當你設定了 mScreenName 後,執行 query 時就會帶入到 {screenname}。

37-41 行的建構子,在這邊可以指定要用的 Http Method 以及要放入 Body 的變數。在這邊我們使用 null 原因是因為 GET Method 不需要填任何東西在 Body,但是使用 POST Method 的時候這邊通常就會把 JSON object 填入。

43-48 行是真正執行 query 的地方。這個時候它會取得 Response,並且將內容用 TweetModel 陣列的方式剖析,最後回傳 TweetModel[]。

25-29 行的地方做了 Reuqest 的 initialize,如果你有需要對 header 作手腳的話可以在這邊弄。

完成了 Client 之後,最後就可以利用以下的方法來調用 getTimeline 的 API:

TweetModel[] tweets = client.users().getTimeline("yurenju").execute();
for (TweetModel tweet : tweets) {
 Log.i("SAMPLE", tweet.getText());
}

接下來如果有意外的話(咦)我會繼續講解 POST, PUT, modify headers 跟 authentication 的部份。

2012/01/21

Fedora 16 下換 GNOME3 Theme

一時無聊看到 ICS GNOME3 的 Theme 覺得很有趣想換一下,沒想到還有點麻煩。主要在於 Fedora 上面的 GNOME3 Theme Selector extension 有相容性問題沒辦法安裝。

解決的方法是只安裝 GNOME3 User Theme extension,然後再用 command line 修改 theme。

  1. 下載 GNOME3 的 Theme,並且放到 .themes 裡面,如果沒這個目錄就創建一個
  2. 安裝 user theme extension:
    # yum install gnome-shell-extension-user-theme.noarch
  3. 重開 gnome-shell,按下 alt + F2 輸入 r 按 enter
  4. 使用指令指定要使用的 theme:
    $ gsettings set org.gnome.shell.extensions.user-theme name "Ice Cream Sandwich"

這樣就可以切換到所指定的 theme 了。


這樣跟我的手機剛好搭成一套 :)