tag:blogger.com,1999:blog-70502247344758214622024-02-08T03:17:43.359+08:00Yuren's Info Area語言本身無所不能,設計師的表達能力是唯一的極限Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.comBlogger556125tag:blogger.com,1999:blog-7050224734475821462.post-86327128406689957452014-05-26T11:41:00.001+08:002014-05-26T11:41:13.272+08:00搬家了改到 <a href="http://blog.yurenju.info/">blog.yurenju.info</a> 去,文章量太多了沒搬過去,新文章請那邊看。Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-15686873919740778572013-11-12T12:10:00.000+08:002013-11-12T12:10:02.711+08:00Mozart - HTML5 音樂指揮家遊戲今年我們在 Node Knockout 2013 上面做了一個音樂指揮家的遊戲,可以用手機瀏覽器控制電腦瀏覽器播放音樂的節奏,並且實驗性的支持不同電腦發出不同聲部的音樂,就像交響樂團一樣!下面是我們的 DEMO 影片:<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="450" src="//www.youtube.com/embed/JvXZ2bpX15M" width="800"></iframe>
<br />
<br />
製作這個遊戲用到了以下技術:<br />
<ul>
<li>Device Motion event: 用來偵測手機的加速度,<a href="http://yurenju.github.io/dmc/" target="_blank">[按這邊]</a>偵測你的手機跟瀏覽器有沒有支援</li>
<li>WebSocket: 用來把手機的加速度資訊從手機傳到 node.js server 再傳到電腦端的網頁</li>
<li><a href="http://mudcu.be/midi-js/" target="_blank">MIDI.js</a>: 播放 midi檔案,據我所知我們應該進行了大量的 patch XD</li>
<li>Audio API: 這邊不是我做的所以不是很清楚,不過就我所知有把 midi 音樂切分成不同聲部並且在不同的電腦播放。</li>
<li>Canvas: 在電腦端繪出加速度曲線,並且偵測到超過門檻值記錄時間來找到節拍</li>
</ul>
晚點我們整理好後會釋出 source code,請拭目以待 :-)<br />
<br />
另外如果覺得我們的點子很有趣,請到 <a href="http://mozart.2013.nodeknockout.com/" target="_blank">[這個網址]</a> 玩遊戲,並且按下左上角的 [VoteKO] 投給我們一票!<br />
<br />
<br />
Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-32161633112857630912013-09-03T11:45:00.000+08:002013-09-03T11:45:16.257+08:00Customize Your Firefox OS禮拜六在風雨交加下到了高雄的 KSDG 講了 "Customize Your Firefox OS",從 Firefox OS 介紹、內建客制化能力以及如何透過修改源碼更深度的客制化。台灣有許多 OEM 廠商透過不同的 OS 來建制自己的解決方案,希望這個分享可以讓大家可以瞭解到如何從內建的客制化機制以及修改源碼來建制自己的 Firefox OS。<br />
<br />
<br />
<script async="" class="speakerdeck-embed" data-id="31c46180f4870130e8773a0acc06b0ce" data-ratio="1.33507170795306" src="//speakerdeck.com/assets/embed.js"></script>Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-82766203562122117802013-07-15T11:33:00.000+08:002013-07-15T11:33:33.038+08:00Sinon.JS - unit test 斬斷相依性的利器這兩天跟上周抽出一些時間正在弄 Firefox OS 中 system app 的 unit test,然後這次好好讀了 <a href="http://sinonjs.org/" target="_blank">sinon.js</a> 之後開始試著用他來處理一些相依性問題感覺還真不賴。<br />
<br />
是這樣的,unit test 主要的目的是檢測特定的 unit 的工作是否正常運作,在這樣的狀況下我們僅測試該 unit 的邏輯與功能,至於跟它相依的部分通常會在另外一個 unit test 或是 integration test 的時候再測試。<br />
<br />
但是問題來了,我想測試的 function 就是有用到其他外部 Object,你總不可能要我整個 Javascript 都沒用到 document.getElementById 吧(這還是有可能啦...)?<br />
<br />
Sinon.JS 的其中一個功能就是可以隔絕 unit 對於其他 Object 的相依性,讓開發者可以單獨測試單一 unit 邏輯的好幫手。剛好我最近正在寫 system app 其中 ScreenManager 的 unit test,讓我們來看看 ScreenManager.init() 這個函式的相依性關係:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho7uGA6BMd9aagGzH4gCi5AuBEekFF344_A_jeHij6QfBYSl0TLjIACGvqLra5oCSzRryRmkQbjHNMP_CpQ0FVNnFUVUyntDYDu_wdECy_4QwoTBxaXDRVfwEtjDV5_CY3CSrDiIgs56Gl/s1600/ScreenManager.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho7uGA6BMd9aagGzH4gCi5AuBEekFF344_A_jeHij6QfBYSl0TLjIACGvqLra5oCSzRryRmkQbjHNMP_CpQ0FVNnFUVUyntDYDu_wdECy_4QwoTBxaXDRVfwEtjDV5_CY3CSrDiIgs56Gl/s1600/ScreenManager.png" /></a></div>
<br />
<br />
ScreenManager.init() 除了 call 外部的五個 Object 以外,還調用了自己的三個 function。而這些相依性都可以透過 Sinon 的功能隔絕他們。<br />
<br />
在這之前先介紹一下 Sinon.JS 的三大物件:<br />
<br />
<b>Spy</b>: 可以把一個 object/function wrap 起來,可以用來監看該 object/function 被呼叫的狀況。舉個例來說,假設我們要測試某個條件下 init() 就會呼叫到 turnScreenOn(), 我們可以用 spy 把 turnScreenOn wrap 起來:<br />
<br />
<pre class="brush: js">var spyTurnScreenOn = sinon.spy(ScreenManager, 'turnScreenOn');
ScreenManager.init();
assert.isTrue(spyTurnScreenOn.called);
</pre>
<br />
上面這段的意思是如果執行了 init() 之後,ScreenManager.turnScreenOn 會被執行到就代表正確。如果不僅執行到,而且傳入的參數一定要是特定值(比如說 true)也可以這樣用:<br />
<br />
<pre class="brush: js">spyTurnScreen.calledWith(true);</pre>
<br />
除了上面提到這兩個 API 以外,還有幾個都還蠻實用的如 calledCount, calledTwice 等等,詳情請見 <a href="http://sinonjs.org/docs/#spies" target="_blank">Spy API</a>。<br />
<br />
<b>Stub</b>: 有 spy 的所有功能。但是造出 stub 之後,原本的 function 就不會再被呼叫了。我通常都會用 stub 把所有的相依性全部切掉。一樣是 turnScreenOn() 在 init() 會被呼叫的例子,如果使用 stub 代替 spy,不一樣的地方是 spy 還是會去呼叫原有 function,而 stub 則不會呼叫原有 function。<br />
<br />
<b>Mock</b>: 有 Spy 跟 Stub 所有功能,但是還可以透過在跑之前設定期望值,最後再檢查 Mock 後的物件是否有照期待的執行。舉個 Sinon.JS 官網上面的例子,下面這樣的寫法可以讓你確認 jquery.ajax 最少被呼叫到了兩次,最多呼叫五次:<br />
<br />
<pre class="brush: js">sinon.mock(jQuery).expects("ajax").atLeast(2).atMost(5);
...
jQuery.ajax.verify();</pre>
<br />
跟 Spy, Stub 不一樣的是 Mock 可以在測試還沒開始前就先預先指定期望值,而不是像 Stub 一樣需要等到呼叫後才能檢測條件是否成立。不過我目前測試的部分都可以用 stub 做到,Mock 暫時還沒用到,等到真的有用到之後再跟大家分享。<br />
<br />
接下來我們先來看一下相對簡單的例子要怎麼測試:ScreenManager:toggleScreen():<br />
<br />
<pre class="brush: js">toggleScreen: function scm_toggleScreen() {
if (this.screenEnabled) {
// Currently there is no one used toggleScreen, so just set reason as
// toggle. If it is used by someone in the future, we can rename it.
this._screenOffBy = 'toggle';
this.turnScreenOff();
} else {
this.turnScreenOn();
}
</pre>
<br />
這個 function相當的簡單,只要 screenEnabled 是 true,_screenOffBy 就會是 toggle,並且 turnScreenOff 會呼叫到。所以只要利用 sinon.stub 分別作出 turnScreenOff 跟 turnScreenOn 的 stub,並且確認會不會正確的呼叫到即可。<br />
<br />
<pre class="brush: js">suite('toggleScreen()', function() {
var stubTurnOff, stubTurnOn;
setup(function() {
stubTurnOff = sinon.stub(ScreenManager, 'turnScreenOff');
stubTurnOn = sinon.stub(ScreenManager, 'turnScreenOn');
});
teardown(function() {
stubTurnOff.restore();
stubTurnOn.restore();
});
test('if screenEnabled is true', function() {
ScreenManager.screenEnabled = true;
ScreenManager.toggleScreen();
assert.equal(ScreenManager._screenOffBy, 'toggle');
assert.isTrue(stubTurnOff.called);
assert.isFalse(stubTurnOn.called);
});
test('if screenEnabled is false', function() {
ScreenManager.screenEnabled = false;
ScreenManager.toggleScreen();
assert.isTrue(stubTurnOn.called);
assert.isFalse(stubTurnOff.called);
});
});</pre>
<br />
所以我在 5-6 行的地方用 sinon.stub() 分別對兩個 function 做了 stub,這樣一來當 toggleScreen() 呼叫這兩個 function 的時候,就會呼叫到假的 function 了。比如說 15-19 行的測試,當 screenEnabled 是 true 時,turnOff 要被呼叫到,而 turnOn 則不會被呼叫到。sinon.stub() 在這種種狀況就可以協助我們把相依性切除,只專注在測試目標 function 的邏輯。<br />
<br />
但是我們的測試環境是將 Firefox OS 在瀏覽器上面運行,這時候有些 Object 很可能是不存在的。比如說 mozTelephony,所以就不能用上面 sinon.stub(Object, propertyName) 的方式造假物件,還是要利用 sinon.stub() 造一個新的物件。正是因為這樣我設計了 switchProperty/restoreProperty 的 helper function 用來替換掉可能不存在的物件。<br />
<br />
再來看看一些比較進階的例子:如果你有一個 function 只希望在某種狀況下,才使用 stub 指定的回傳值,而其他狀況則是調用原本的 function 該怎麼寫呢?init() 的 setup() 正好有這樣的狀況:<br />
<br />
<pre class="brush: js">var stubById = sinon.stub(document, 'getElementById').withArgs('screen')
.returns(document.createElement('div'));
</pre>
<br />
這段的意思是我們為 document.getElementById 做了一個假物件,他會直接回傳一個 div DOM 元件,但是只有在傳入參數為 screen 的時候才會發生,所以說:<br />
<br />
<pre class="brush: js">document.getElementById('screen');</pre>
<br />
這個時候就會直接回傳一個假的 div,而不會真的到 DOM 裡面查詢一個 #screen 的元素,而且如果你查的是任何其他的參數,就還是可以正常運作!這樣可以確保你的 stub 只在你想要的參數被觸發!<br />
<br />
還有一個很常需要對付的狀況:如果你的測項是需要被 callback 呼叫才能測該怎麼辦呢?舉例:你用了一個 addEventListener('click', callback),所以說一定要 click 才能呼叫 callback 該怎麼辦呢?用下面的 function:<br />
<br />
<pre class="brush: js">sinon.stub(window, 'addEventListener').callsArgWith(1, evt)</pre>
<br />
如果一呼叫 addEventListener 就會直接呼叫第一個參數('click' 是 0, callback 是 1),這樣就可以測試更深層的狀況了。<br />
<br />
總而言之,Sinon.JS 準備了一拖拉庫的造假工具給你用,Javascript Unit Test 有了像 Sinon.JS 這樣強大的工具,就可以切斷相依性,測試的更徹底!<br />
<br />
另外這邊有一個 Firefox OS 裡面的範例:<a href="https://github.com/mozilla-b2g/gaia/blob/master/apps/system/js/screen_manager.js" target="_blank">screen_manager.js</a> 跟 <a href="https://github.com/mozilla-b2g/gaia/blob/master/apps/system/test/unit/screen_manager_test.js" target="_blank">screen_manager_test.js</a>,有興趣的可以參考參考。Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-31138589727519235642013-07-04T18:04:00.001+08:002013-07-04T18:04:09.231+08:00Gaia Development Workflow這是 Firefox OS - Gaia 開發時的 workflow,沒時間寫文章,先放張圖。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTchbJYHZapx7pg5Wcu-zL2RaoDqeg9ju5agty86APhw0RmvcBLZUpDh_oG4jrRh2Rpxb2crpfwttCT4flOBvGKD2kBw_zHft1E6ZbWZMnN5gJ4az2p6zEjRS6wmsJMHBaKR7SxA1hEtGs/s1375/Gaia+Development+workflow.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTchbJYHZapx7pg5Wcu-zL2RaoDqeg9ju5agty86APhw0RmvcBLZUpDh_oG4jrRh2Rpxb2crpfwttCT4flOBvGKD2kBw_zHft1E6ZbWZMnN5gJ4az2p6zEjRS6wmsJMHBaKR7SxA1hEtGs/s1600/Gaia+Development+workflow.png" width="800" /></a></div>
<br />Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-68536179029030089012013-06-30T17:00:00.000+08:002013-07-06T00:07:33.516+08:00Running Firefox OS Gaia on Windows<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaXQEN2kXEUXKZBCKvljfiiQ2nYxfThkbOu7WzcQYrle2HRb9Ub7Or8GMFAgo2WAkPaOQ-eKxgxhdgFpyzFwB8hfxLsekL4gwwFOmxDeXSk7Eq4eW7DXz4QPdrhb6Yd5IxUipFQhR93acH/s1600/Sa2Xm4P.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="368" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaXQEN2kXEUXKZBCKvljfiiQ2nYxfThkbOu7WzcQYrle2HRb9Ub7Or8GMFAgo2WAkPaOQ-eKxgxhdgFpyzFwB8hfxLsekL4gwwFOmxDeXSk7Eq4eW7DXz4QPdrhb6Yd5IxUipFQhR93acH/s640/Sa2Xm4P.jpg" width="640" /></a></div>
<br />
你永遠不知道下一個要解的 Bug 是什麼 :-)<br />
<br />
上週送了一個 Pull Request 到 Gaia,Reviewer 非常好心的跟我說我的 patch 在 Windows 上面不會動,這時候才第二次意識到我們還是需要在 Windows 上面測試(上一次是我修 customization 的時候遇到的)。總之我這次很老實地把 Windows 的環境搞定了,在我等待 fetch pull request 的時候就來說說怎麼設定吧。<br />
<br />
首先下載 <a href="http://nightly.mozilla.org/" target="_blank">Firefox Nightly</a>。<br />
<br />
接著你需要 <a href="http://www.mingw.org/" target="_blank">MinGW</a>,寫這篇文章的時候我裝的是 <a href="http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/mingw-get-inst-20120426/mingw-get-inst-20120426.exe/download" target="_blank">mingw-get-inst-20120426.exe</a>,或許你看到這篇文章的時候已經有新版出來了。安裝的時候記得要選下面這兩個:<br />
<ul>
<li>MSYS Basic System</li>
<li>MinGW Developer ToolKit(這個我不確定要不要)</li>
</ul>
接下來安裝 git,Git 直接到<a href="http://git-scm.com/" target="_blank">官方網站下載</a>就好了。安裝的時候選擇 "Run Git from the Windows Command Prompt",這樣可以直接在 MinGW 的環境使用 git。<br />
<br />
然後接下來是裝 Python,一樣到<a href="http://python.org/" target="_blank">官網下載</a>就好了。 但是你需要把 Pyhton 的路徑加入 PATH 這個環境變數裡面,在 My Computer 按右鍵,選 properties -> Advanced -> Environment Variables,找到 Path 把 C:\Python27 加入。<br />
<br />
最後的一個步驟,安裝一些 build gaia 會用到的指令:<br />
<br />
$ mingw-get install msys-wget<br />$ mingw-get install msys-zip<br />$ mingw-get install msys-unzip<br />
<br />
這樣就搞定啦!<br />
<br />
打開 MinGW Shell 找個風水吉地 git clone gaia,進入目錄後用下面指令 build 出 profile 檔:<br />
<br />
$ DEBUG=1 make<br />
<br />
第一次會需要久一點,主要是下載一些 xulrunner 之類的東西。好了之後用下面的指令啓動你的 Nightly。依據你 clone Gaia 的地方會有些不一樣,自己摸索一下吧。<br />
<br />
$ /C/Program\ Files/Nightly/firefox.exe -profile /C/MinGW/msys/1.0/home/IEUser/gaia/profile-debug/<br />
<br />
這樣就可以在 Windows 的 Nightly 上面啓動 Gaia 囉,在裡面收信也沒問題喔 LOL<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhILMs3fJbGWig3F0-c94-RRIRQHhWFoqGxaPOOD2oBryC45ZC9AESKC9ABWAqTP1L2AZXJdFVaHQLY3LMhXFKdCTInxi3ioYmhvxUk21f6LdzNNUnd5JJ7iJvFMwfxIUgEqyAAgemVdlNH/s1440/mail.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhILMs3fJbGWig3F0-c94-RRIRQHhWFoqGxaPOOD2oBryC45ZC9AESKC9ABWAqTP1L2AZXJdFVaHQLY3LMhXFKdCTInxi3ioYmhvxUk21f6LdzNNUnd5JJ7iJvFMwfxIUgEqyAAgemVdlNH/s640/mail.png" width="640" /></a></div>
<br />Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-14709448695558870112013-06-25T01:43:00.000+08:002013-06-25T08:04:49.794+08:00關於辦 COSCUP 的那些美好回憶下面是一個可口可樂的廣告,幾年前我看到這個廣告的時候就很感動,先跟大家分享一下。 <br />
<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="600" src="http://www.youtube.com/embed/Vfjre-bS4A4" width="800"></iframe><br />
<br />
不知道這個 Blog 的觀眾多少人辦過研討會?從 2005 年準備要跟 ICOS 分成兩個研討會開始我就陸陸續續地參加 COSCUP 的工作人員,從聽眾、場務、講者、主持人到最近連續四年擔任記錄組與議程組成員。<br />
<br />
今年我擔任了議程組長。<br />
<br />
我記得在去年慶功宴的時候,我跟 Bob 說,『Hey, 議程組我混熟了,明年我來當議程組長吧』我壓根沒想到過了一季之後,我們決定要在 TICC 台北國際會議中心辦 COSCUP。<br />
<br />
更沒想到的是半年後,我們不只要在 TICC 辦,我們準備籌辦八軌議程。<br />
<br />
親愛的觀眾,八軌議程阿,這可不是鬧著玩的。<br />
<br />
兩年前我擔任紀錄組長,在約八百人規模,四軌議程下我都累得跟狗一樣了。 然而今年 COSCUP 就像是個小型社會,高度壓力,密集溝通,工作追蹤。當然工作人員之間臉紅脖子粗的爭吵也少不了。我再度的累得跟狗一樣,我想要跟金凱瑞在電影『一個頭兩個大』一樣,自己賞自己一拳,然後再給自己一個過肩摔。<br />
<br />
我對著躺在地上的那個 Loser 說:嘿你看吧,你這個魯蛇,你自找的。我就像是躺在大雨裡的泥濘,雨滴落在地上的聲音就像是那些邊拍手邊笑彎了腰的圍觀人群嘲笑我。<br />
<br />
在這種夜裡,我常常會想起這個廣告的片段。<br />
<br />
<b>『大部份的人會告訴你,你選擇了一個最糟糕的時刻。 ... 我們正經歷一場危機,這不是什麼好事』</b><br />
<br />
白天的工作已經很忙了,晚上回家繼續默默地回著根本不可能看完的信件。<br />
<br />
<b>『可是這會讓你更堅強』</b><br />
<br />
讓我想到 Bob 在某年的慶功宴舉杯的畫面 <br />
<br />
<a href="http://www.flickr.com/photos/yurenju/4898932094/" title="Flickr 上 yurenju 的 敬你。"><img alt="敬你。" src="http://farm5.staticflickr.com/4076/4898932094_701b4995b2_b.jpg" width="800" /></a><br />
<br />
這也是很艱難的一年啊,那年我們把報名系統弄炸了(笑<br />
<br />
你說我還記得多少有多辛苦?我心裡只記得當所有結束之後,那些快樂,震撼還有為了結束的那一點惆悵。<br />
<br />
今年我完成了我從來沒想過的目標,以及從未想過的困難。來自那些 1,500 人入場的壓力,來自八軌議程的壓力,還有截稿前兩週只有三十個人投稿的壓力。<br />
<br />
這些背在肩膀上的事情讓人咬牙的覺得痛苦。<br />
<br />
但是這些終究會過去的。<br />
<br />
當拉開布幕後,當我們像螞蟻一樣在會場穿梭後,當大家在舉杯致敬後, 我很確定那些所有的辛苦都會在落幕之後煙消雲散。而我們現在唯一要做的,就是為了拉開序幕的那刻做好準備。 <br />
<br />
今年夏天你打算做什麼呢?<br />
<br />
<span style="font-size: x-large;">我打算辦一個台灣最大的開放源碼研討會 ;-)</span><br />
<br />
<br />
BTW, [工商服務] <a href="http://blog.coscup.org/2013/06/coscup-2013-individual-sponsorship.html" target="_blank">個人贊助活動</a>仍在招募中,歡迎<a href="http://registrano.com/events/personalsponsor" target="_blank">個人贊助</a>喲。<br />
<br />
<br />
<br />Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-4399367150745567102013-01-22T12:16:00.000+08:002013-01-22T13:44:34.316+08:00威能的 Firefox OS unit testing今天在謀智台客發表了篇文章,主要是講 Firefox OS 在 unit testing 有個不錯的機制,就是設定妥當後,當你在任何編輯器或 IDE 按下儲存後,unit testing 就會自動開始測試跟你剛剛儲存的那個 javascript 相關的測項,最後用 Mac 的 notification center 或是 Linux 的 libnotify 告訴你測試結果,像下面這樣:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh00aIUPw-fYq6tvypgWKiBDg7xI3jV3Txz2QPjC2yuP2ih-b5iq2LNUketn3h-G8Wd19D71ltqkuH3elongDk-_kHiE3NzTwF-wJU2Qt_AkIyu02FPQoGH-s9B0xwHL07jsNZ85540gidD/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-22+%E4%B8%8B%E5%8D%881.43.46.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh00aIUPw-fYq6tvypgWKiBDg7xI3jV3Txz2QPjC2yuP2ih-b5iq2LNUketn3h-G8Wd19D71ltqkuH3elongDk-_kHiE3NzTwF-wJU2Qt_AkIyu02FPQoGH-s9B0xwHL07jsNZ85540gidD/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-22+%E4%B8%8B%E5%8D%881.43.46.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<span id="goog_2092831299"></span><span id="goog_2092831300"></span><br />
<br />
可以讓你隨時都知道自己有沒有把任何東西搞爆了。<br />
<br />
有興趣的可以看一下<br />
<br />
<a href="http://tech.mozilla.com.tw/posts/1470" target="_blank">缺它不可!靈活運用 Firefox OS Gaia 的單元測試</a>Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-69702033387442880112013-01-02T10:08:00.000+08:002013-01-02T10:08:22.559+08:00travis + jsdom: Javascript unit test 的最後一塊拼圖在昨天晚上出去跨年前,我把自己之前練習寫的 <a href="http://yurinfore.blogspot.tw/2012/09/html5-mobile-app.html" target="_blank">weather app</a> 重新整理過了一次,主要是把存取 DOM 的部分聚集起來然後寫 unit test 以及開始用 travis 測試。<br />
<br />
travis 是好幾個朋友跟我提過但是我都沒認真看怎麼玩,終於在上次的 <a href="http://www.hackingthursday.org/" target="_blank">Hacking Thursday</a> 聽 <a href="http://kanru.info/blog/" target="_blank">kanru</a> 跟 <a href="http://www.linkedin.com/in/changzhuo" target="_blank">czchen</a> 討論了幾番之後決定還是自己來試試看比較有感覺。<br />
<br />
<a href="http://travis-ci.org/" target="_blank">travis</a> 提供的服務就如 <a href="http://jenkins-ci.org/" target="_blank">Jenkins</a> 一樣,是一個 CI (continuous integration) 的服務 -- 但是不用自己架 Jenkins 對我來講實在太棒了,我是真的很怕麻煩還要自己維護 CI service。Travis 是直接跟 github 整合的 CI service,所有在 github 的專案都可以很簡單地用 travis hook 透過一個 .travis.yml 來指定 build 以及測試的方式。<br />
<br />
<a href="https://github.com/tmpvar/jsdom" target="_blank">jsdom</a> 則是一個在 node.js 上面的 DOM 實作,意思就是說可以利用它在 node.js 裡面操作 window, document 等元件。這個對我來說真是大大的福音。因為 mobile-weather 大多數的邏輯操作都是跟 DOM 相關的,如果剔除這些其實也沒什麼邏輯好測試的。jsdom 讓我可以在 node.js 裡面操作 DOM 等於我就可以驗證一些跟 DOM 相關的邏輯。<br />
<br />
jsdom 真的是 javascript unit test 的最後一塊拼圖,有他之後我們就可以測試跟 DOM相關的部分了 :D<br />
<br />
最後我在 unit test 採用的是 <a href="http://visionmedia.github.com/mocha/" target="_blank">mocha</a>, <a href="http://chaijs.com/" target="_blank">chai</a>,前者是 node.js 上面的 unit test framework,後者是 assertion library。<br />
<br />
首先第一個步驟是寫 package.json。因為我們的 unit test 會跑在 node.js 裡面,所以我們要先寫 package.json 來交代要用到哪些 library,並且我們可以在裡面指定當執行 "npm test" 的時候要如何執行測試。<br />
<br />
<script src="https://gist.github.com/4425286.js"></script><br />
<br />
4 - 8 行的部分是指定 devDependencies 要使用 mocha, chai 跟 jsdom。9 - 11 行做的是執行 npm test 的時候事實上會執行 node_modules/.bin/mocha -u tdd test/weather_test.js。這之後如果換了 unit test framework 也可以很輕易地從這邊換掉測試方式 :-)<br />
<br />
至於 mocha -u tdd 是 mocha 同時有提供 BDD 跟 TDD 的 interface 可以置換,我這邊是採用 TDD。<br />
<br />
接下來就可以開始來寫 unit test 了。<br />
<br />
<script src="https://gist.github.com/4425319.js"></script>
目前的 unit test 測試的項目還不多,先把架構弄好之後再慢慢補上。第三行是把 index.html 轉成字串儲存下來。在 7 - 12 行的地方利用 jsdom 生成 window 跟 document,並且每次都重新生成 Weather object 跟重新初始化,主要的目的是讓每個 test case 不會互相影響。test case 我們挑在 20-25 行的 'updateWeekday' 來看。我們知道 2013/1/1 是禮拜二,而 updateWeekday 裡面就會把 day0.textContent 設定為 'T' (Tuesday),接下來依序設定。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg78VbIp3_132NYeBcwccVWITuUk1r9H9j9Xe9JEoFOA3SOVG0R9tZdWOfX2pjGvgwfe7-rJyp515dLY4pck0DNCCCpdb0Iw2KcQWr3wn07pXvac-amcfPC6wqc1PqxaPkrXEzOetUVIExw/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-01+%E4%B8%8B%E5%8D%881.22.39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg78VbIp3_132NYeBcwccVWITuUk1r9H9j9Xe9JEoFOA3SOVG0R9tZdWOfX2pjGvgwfe7-rJyp515dLY4pck0DNCCCpdb0Iw2KcQWr3wn07pXvac-amcfPC6wqc1PqxaPkrXEzOetUVIExw/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-01+%E4%B8%8B%E5%8D%881.22.39.png" /></a></div>
<br />
所以我們就可以假定 day0.textContent 是 T, day4.textContent 是 S 這樣的方式來測試。寫完 test cast,下達 npm test 就可以進行測試囉。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLoYjMeF4Jy07i8QXrnkSJ1fkHmfsEiXlYcSGT0HFunqFNnPQwRvJOcneXaGugYDidXHoFSwHu2DJ19_-fOL5ZrkD8zR5GrJGrkmHBp4jETNNxA2Ls2Sg_iM80Zt8CnOiCzNCsgnQ9ifSM/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-01+%E4%B8%8B%E5%8D%881.26.07.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLoYjMeF4Jy07i8QXrnkSJ1fkHmfsEiXlYcSGT0HFunqFNnPQwRvJOcneXaGugYDidXHoFSwHu2DJ19_-fOL5ZrkD8zR5GrJGrkmHBp4jETNNxA2Ls2Sg_iM80Zt8CnOiCzNCsgnQ9ifSM/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-01+%E4%B8%8B%E5%8D%881.26.07.png" /></a></div>
這樣 unit test 就告一個段落,最後就是把它移到 travis 上面就大功告成了!先在根目錄開個 .travis.yml,內容是你的 travis 組態:<br />
<br />
<script src="https://gist.github.com/4425381.js"></script>
這邊我們指定採用 node.js,並且只測 0.8 這個版本。其實我們只有在 unit test 的時候會用到 node.js,基本上他還是一個一般的 browser app。所以不需要測試多個 node.js 版本。把這邊全部都 push 上 github 後,用你的 github 帳號登入 travis。然後在 <a href="https://travis-ci.org/profile" target="_blank">account</a> 的頁面找到你的 project 把右邊的 switch 推到 on。<br />
<br />
當下次你 commit & push 的時候,就會看到 travis 開始測試你的 project 囉!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH2OdymGtDRTbl99ZqrEi9WRBxbpTBuepDh-NZtyCs6oBoGJMukt2VnfiW7cyNt1uG8LPK2O_5E-82_zD6KlZje6eQAY3EJ_9RCUXVUN1ZrTGc06baIuVzHgRl-wC4166MOn48nLT5ACoF/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-01+%E4%B8%8B%E5%8D%881.30.23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH2OdymGtDRTbl99ZqrEi9WRBxbpTBuepDh-NZtyCs6oBoGJMukt2VnfiW7cyNt1uG8LPK2O_5E-82_zD6KlZje6eQAY3EJ_9RCUXVUN1ZrTGc06baIuVzHgRl-wC4166MOn48nLT5ACoF/s640/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-01+%E4%B8%8B%E5%8D%881.30.23.png" width="640" /></a></div>
並且有每一次的 build log:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDkTtDESq_ASDUznH9mqVL3IBKF2Hc6qobkxCVCfb-5fO4Wx7or702WtGCBzNGMe6HAGQUIIdQ8ecWyYjI2wrnV3TtGXtVIKrLHN5vqToBlh83quyqeK-jarnzEhs-O_dAOMQJl2ALLYPk/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-01+%E4%B8%8B%E5%8D%881.35.39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDkTtDESq_ASDUznH9mqVL3IBKF2Hc6qobkxCVCfb-5fO4Wx7or702WtGCBzNGMe6HAGQUIIdQ8ecWyYjI2wrnV3TtGXtVIKrLHN5vqToBlh83quyqeK-jarnzEhs-O_dAOMQJl2ALLYPk/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2013-01-01+%E4%B8%8B%E5%8D%881.35.39.png" /></a></div>
<br />
超棒的吧,不用在自己架 Jenkins 囉!新年快樂!<br />
<br />
目前的計劃是如果我們 fork jsdom 然後把 Firefox OS 有用到的 API 加進去,我們就可以開心的丟掉 Browser 測試 Firefox OS app 了 :DYuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-49779920923112302352012-11-27T10:22:00.000+08:002012-11-27T13:22:14.461+08:00[Firefox OS] HTML 內嵌 SVG 動畫實例前言:這些實作都已經包含在最新的 Firefox OS 裡面,有興趣的可以 check 最新的 code 出來玩。<br />
<br />
最近接到了一個需要變更設計的 issue,我看到這個設計愣了一下,主要的原因是因為要做出這樣的設計用 HTML + CSS 還真的需要想一下如何實作。<br />
<br />
主要更改的地方是 Firefox OS 的兩個元件:Lockscreen 跟 Dialer。<br />
<br />
這兩個元件主要的設計概念都是希望有一條像是橡皮筋(或跳繩)的線上下的跳動,並且在往上跳動的時候露出下面的兩個按鈕,提示使用者可以把這條橡皮筋往上滑,接下來按下按鈕解鎖。我們這邊只討論如何實作,不討論視覺設計 :P <br />
<br />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBps6VflTdVW4UphdcY36V3vahLSayIvfjWnaXsCXClL5BGrxOwsaLlnlcXZLa2ne-ifgDbwYMAPxXSB0EwIIZJNzyUfhUxgzSUNipU5h0_H1NyTubNAyzexPFKUNgxFTNlJCSDxqdUCkY/s1600/device-2012-11-25-103720.png" /> <img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-7qqc1EI50OJ-wxwmUpbt-J9cQSvKcFxDAf-1k3xc7Pz5hgxso3ZavgYx-dc1R5KqOIij4GChSZ4GAVp360Ira4HM0iSZ409-Al_sbxjK_mbslTm-K1h2s_ij3tBE6yQNF8Iu3FADKcHM/s1600/device-2012-11-25-103930-1.png" />
<br />
<br />
如上圖所示,上面的那條弧線是需要動態的上下彈動,如果用 HTML + CSS 的話有幾種方法可以嘗試做到跟缺點:<br />
<ol>
<li>上一個 canvas,然後把這條線在 canvas 上面不斷的重繪(缺點:這樣要不停的計算跟重繪)</li>
<li>用超級多張 png 不停的置換圖檔(缺點:要生出超級多圖)</li>
<li>先畫一張弧線,接著用 CSS 的 Transform 更改他的 scale()(缺點,接近中間的時候整個圖形就會被壓得很扁)</li>
</ol>
考慮過 HTML + CSS 的解法之後,以上的解法似乎都不太好。這個時候我就開始考慮用 SVG 來做這件事情。但是用 SVG 來做這件事情其實是蠻冒險的,因為在這期間問了幾個同事他們都沒有測試過 SVG 在 Firefox OS 真正的手機上的詳細效能。所以收到這個設計的時候我先寫信問了個對於整個 platform 比較熟的同事,然後因為 deadline 非常的趕,但是我又不能不確定效能狀況就下手,所以就先決定做個獨立可以同時在電腦跟手機都可以驗證的小型 app。<br />
<br />
這個 app 要驗證的事情有兩件:<br />
<ol>
<li>當用滑鼠(手指)按住拖曳,這個時候改變 SVG 的屬性讓他改變弧度的效能衝擊有多大</li>
<li>使用 SVG animation (SMIL) 效能到底如何</li>
</ol>
然而做這個 DEMO 幾乎也可以搞清楚要怎麼用 SVG 實作這個解鎖畫面了。在弧線的部分,採用 SVG 的 Path 搭配上 c (curveto) 參數可以達成弧線,動畫的部分則是用 SMIL 的 animate tag 完成。下面這個網頁就是驗證效能用的網頁(我只用過 Firefox 打開過,其他瀏覽器不知道有沒有支援):<br />
<br />
<a href="http://yurenju.github.com/lockscreen-demo/wrapper.html" target="_blank">http://yurenju.github.com/lockscreen-demo/wrapper.html</a><br />
<br />
這邊只是用來驗證的網頁,所以會有一些小 bug。主要的功能就是往上拉的時候用 javascript 去改變 SVG d (data) 裡面的 c (curveto) 的參數,放開滑鼠的時候用 SMIL animate 把 curve 滑回原位。而下面有個連結 install lockscreen demo 用途是如果你用 Firefox OS 的手機點了這個連結就可以把這個 demo app 安裝到手機裡面。<br />
<br />
很棒的是當我把這個 app 安裝到手機裡面,發現這樣實作的效能在手機上是完全可以接受的!既然可以接受那就大膽的把這樣的實作方式引入 Firefox OS。這邊有針對 <a href="https://github.com/yurenju/gaia/commit/ec82ff70d9bbd3dc50424422e8be07d393e13415" target="_blank">lockscreen 的 commit</a>。<br />
<br />
<script src="https://gist.github.com/4148941.js"> </script>
<br />
在 SVG 方面,首先利用 path tag 來劃出最原始的弧線。在 attribute d (data) 裡面用了兩個參數:M (moveto) 跟 C (curveto),moveto 用來指定 path 的起點,curveto 用來指定用來控制曲線的兩根桿子的弧度。<a href="http://www.w3.org/TR/SVG/paths.html" target="_blank">SVG 1.1 Path</a> 裡面有張圖讓 curve 的控制點比較好理解一點:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.w3.org/TR/SVG/images/paths/cubic02.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.w3.org/TR/SVG/images/paths/cubic02.png" /></a></div>
<br />
<br />
C 後面接的兩組 (x, y) 分別是兩個控制點的坐標,我們需要的大概像是最左上角那張圖的效果。attribute 'd' 裡面的最後一個參數則是曲線的終點。整個 attribute 長這樣:<br />
<br />
M<span style="color: #990000;">0,80</span> C<span style="color: #38761d;">100,150 220,150</span> <span style="color: #990000;">320,80</span><br />
<br />
第一個是曲線起始坐標,最後一個是曲線結束坐標,中間兩個則是控制點的坐標。<br />
<br />
接下來說明 path 裡面的五個 animate tag。1 跟 3 是針對曲線的彎度變化,而 2 跟 4 則是針對透明度的變化。1-4 都是用於拖曳橡皮筋之後放開的動畫,而 (5) 則是當你不去碰橡皮筋時,他的彈跳提示動畫。請想像這一整組動畫:一條繩子彈上去後隨著重力掉下來,掉到地上之後會再彈跳幾下後靜止。下面講解比較複雜的第五組動畫:<br />
<br />
<script src="https://gist.github.com/4149114.js"> </script> 先看到 values。用分號切分開來的話總共有五組數據:<br />
<ol>
<li>起始的曲線數據 (Y=150)</li>
<li>第一次彈跳到最高的數據,兩個控制點的 Y 坐標都變少了讓整個圓弧的開口朝下 (Y=40)</li>
<li>回到最地上 (Y=150)</li>
<li>再次彈起來,但是幅度較低 (Y=100)</li>
<li>回到原點 (Y=150)</li>
</ol>
至於 keySplines 則是指定彈跳的 timing function,如果你用過 CSS animation,就跟 ease-in/out 那種差不多的東西,只是要直接指定數據,下面這張圖是 keySplines 設定 0.5 0 0.5 1 會產生的 timing function:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.w3.org/TR/SVG2/images/animate/keySplines02.svg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.w3.org/TR/SVG2/images/animate/keySplines02.svg" /></a></div>
<br />
keySplines 裡面有四組數據,分別就是 1-2, 2-3, 3-4, 4-5 這四段動畫的 timing function。每一組數據裡面都是兩個控制點的坐標。<br />
<br />
SVG 的部分大概就是這樣!這部分有很多需要細微調整的,有興趣的就留言一起討論吧。接下來是 Javascript 部分。這邊我就講一些 SVG + Javascript 要注意的小技巧<br />
<ul>
<li>fill=freeze 功能為讓動畫結束之後停留在最後一格,不過這樣的話如果你想要用 mousemove 去逐漸改變曲線的外形時,你會發現這個屬性會讓整個 path 卡死。如果拿掉 fill=freeze 的話,因為我執行完動畫之後還要把曲線固定在最後一格,所以拿掉的話就導致動畫有閃爍的現象。 解法就是平常不用,等到要播放補間動畫的時候再把 freeze 加上去。</li>
<li>用 beginElement(), endElement() 來播放、停止動畫</li>
<li>addEventListener endEvent 來處理動畫結束後的後續處理。</li>
</ul>
這邊的細節真的非常的多,如果你也想 HTML + SVG + Javascript 來實作的話,建議是要讀一下 SVG 跟 SMIL 的 spec,然後撿想要用的東西放在裡面,什麼不明白的事情就直接寫到 SVG 裡面看一下效果如何就是了。那個時候這個 commit 要上真是超級緊張的,因為這是我開始 contribute Firefox OS 以來最大的修改。結果上的時候還是有些小細節沒注意到,感謝同事的幫忙在 bug 還沒關之前就注意到這個<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=813045" target="_blank">低級錯誤</a>然後讓我可以及時的推入 repository 了。<br />
<br />
之後更複雜的是 Dialer 的部分。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsT4WSNMOgCKa26XILIhu82t89zp47lrnMEFYjnecx-zSlxiniP93EucTISP-vk43s2OAWN6Ct2jGbijA2ON_m7T3FC5eI60GMnJOOiSkxajq7WY8qBoMs1_ssPluENWRF2desqRiz2JAn/s1600/device-2012-11-25-103930-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsT4WSNMOgCKa26XILIhu82t89zp47lrnMEFYjnecx-zSlxiniP93EucTISP-vk43s2OAWN6Ct2jGbijA2ON_m7T3FC5eI60GMnJOOiSkxajq7WY8qBoMs1_ssPluENWRF2desqRiz2JAn/s1600/device-2012-11-25-103930-2.png" /></a></div>
<br />
<br />
如上圖所見左右兩邊各有一個會隨著弧線移動的兩個 spotlight,而線段上的顏色還多了紅色跟綠色線段。線段不同顏色方面,看遍了 SVG 的資料後比較方便的方法還是用多重的漸層並且把<a href="https://github.com/yurenju/gaia/blob/issue-806989-part2/apps/communications/dialer/oncall.html#L71" target="_blank">兩個漸層的 offset</a> 設定成一樣,這樣就可以讓曲線有不同的顏色。至於隨著 curve 的 spotlight 則是透過 clipPath 作修剪遮罩,讓漸層只在部分的地方露出來即可。做完這次的 commit 我的 SVG 功力真的大增啊... Orz<br />
<br />
<br />
<script src="https://gist.github.com/4149305.js"> </script>
<br />
這是在 Dialer 曲線用的漸層。2 跟 3 的 offset 都是一樣的,但是顏色卻用不一樣,這樣的技巧可以讓線段不會產生漸層。 <br />
<br />
<script src="https://gist.github.com/4149344.js?file=gistfile1.xml"></script>
這段是如何產生 spotlight 的方法。首先 path 不一樣的地方是 d 除了原本的 M 跟 C 以外,又加了 H V Z 分別用來畫出橫線、直線跟關閉 path 用。fill style 則套用上面的 #gradient-red 的漸層紅色,最後用 clipPath 的方式作剪裁遮罩。對綠色的部分也用相同的方法,最後通通拿去做動畫,就完成啦! <br />
<br />
所以這是最後的結果:<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="480" src="http://www.youtube.com/embed/pC3IYPPTVz0" width="853"></iframe>
<br />
<br />
Dialer 最後實作的結果在這邊,很可惜速度上並不是很好,目前看起來撥電話進來之後整隻手機的效率會下降許多,目前我們也正在改進這個問題。<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="480" src="http://www.youtube.com/embed/xSXnYaDxl8w" width="853"></iframe>
<br />
<br />
這個 lockscreen 的 code 都在 github 上面,有興趣的可以抓下來玩玩 :-)<br />
<ul>
<li><a href="https://github.com/yurenju/gaia/tree/issue-806989-part1">https://github.com/yurenju/gaia/tree/issue-806989-part1</a></li>
<li><a href="https://github.com/yurenju/gaia/tree/issue-806989-part2">https://github.com/yurenju/gaia/tree/issue-806989-part2</a></li>
</ul>
<br />
<ul>
</ul>
Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com2tag:blogger.com,1999:blog-7050224734475821462.post-32846855804009776042012-10-29T08:01:00.000+08:002012-10-29T08:01:20.162+08:00Using Google APIs Client for Android這是在十月 27, 28 日在高雄軟體園區舉辦的 <a href="http://www.mopcon.org/" target="_blank">MOPCON</a> 我講的題目。原本演講的時候最後一段 OAuth 的部分是直接在 Source code 裡面講解。不過回來之後已經把投影片都補上了。<br />
<br />
另外如果你也有使用 Google APIs Client 也可以交流一下,我發現好像還蠻少人在用的。<br />
<br />
總之下面是投影片,請笑納 :D<br />
<br />
<br />
<script async class="speakerdeck-embed" data-id="508dc480aee4d50002002f75" data-ratio="1.3350717079530638" src="//speakerdeck.com/assets/embed.js"></script>Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com1tag:blogger.com,1999:blog-7050224734475821462.post-33021060685928748382012-10-17T15:04:00.000+08:002012-10-17T15:52:40.114+08:00jscallgraph - Javascript Call Graph 靜態分析最近用 xmind 追 code 追得很辛苦,跟<a href="https://www.facebook.com/photo.php?fbid=10151149547596631&set=a.144732831630.122857.700771630&type=1" target="_blank">幾位朋友</a>討論過後,不知不覺就開始寫起了靜態分析用的程式。昨天先用 python 驗證了一下概念,深夜開完會議之後就決定用 node.js 來寫個程式會比起用 python 更為合適。目前已經放上 npm,要安裝只要你已經裝好了 node.js 跟 npm,輸入以下指令就可以安裝了。<br />
<br />
<pre class="brush: shell">$ sudo npm -g install jscallgraph
$ sudo apt-get install graphviz
</pre>
<br />
第二行是安裝 graphviz,主要的用途是拿來產生分析完的圖形用的。使用上很簡單,舉例來說你想要對 Firefox OS 的 system app 裡面的 <a href="https://github.com/mozilla-b2g/gaia/blob/master/apps/system/js/window_manager.js" target="_blank">window_manager.js</a> 作靜態分析,把檔案抓下來,用以下指令產生 dot file<br />
<br />
<pre class="brush: shell">$ jcg window_manager.js WindowManager > window_manager.dot
</pre>
<br />
第一個參數是要分析的檔案,第二個參數是要分析的物件,這邊可以參考一下 window_manager.js 的檔案,裡面宣告了 WindowManager 物件,並且把相關 method 都包裝在裡面。<br />
<br />
接下來用 graphviz 的 dot 指令產生 png 圖檔<br />
<br />
<pre class="brush: shell">$ dot -Tpng -o window_manager.png window_manager.dot</pre>
<br />
<br />
這樣就完成了!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcpPO5vFMrU3Cp7vTZvAwtup0iOrMBdrZUSXVjBeBKTZY4A4BZgLmTDYBep0jpm4wXW-9nblFRNfWIU3hZHRowZdAjVyHiVwnFFd_Qtz3ggzj5FL3U5fLsQDzqwIs2j8AscWPnr2K1-RGl/s1600/window_manager.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcpPO5vFMrU3Cp7vTZvAwtup0iOrMBdrZUSXVjBeBKTZY4A4BZgLmTDYBep0jpm4wXW-9nblFRNfWIU3hZHRowZdAjVyHiVwnFFd_Qtz3ggzj5FL3U5fLsQDzqwIs2j8AscWPnr2K1-RGl/s640/window_manager.png" width="640" /></a></div>
<br />
<br />
Source code 放在 <a href="https://github.com/yurenju/jscallgraph" target="_blank">github</a>, 請大家自由取用!另外這程式不是什麼 javascript 都可以分析,有興趣的請送 patch! :DYuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com1tag:blogger.com,1999:blog-7050224734475821462.post-665620524898276062012-10-15T11:34:00.000+08:002012-10-15T11:55:08.183+08:00[Firefox OS] 呼叫 MozActivity 的內部訊息流程上週快結束的時候我一直在追蹤一個 Firefox OS 的 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=800169">Bug #800169</a>,後來發現不是 Gaia (Firefox OS 的 App 層) 之後,我就一路往下看到了 Gecko (Firefox OS 的 Runtime 層),當我覺得快要找出癥結的時候,這個 Bug 在 Nightly build 被別人解決了! XDD<br />
<br />
不過趁著這個機會也把架構熟悉過了一遍,跟大家分享一下。<br />
<br />
先解釋一下這個 bug,Firefox OS 的瀏覽器在把一個網頁加入到 home screen 的時候,加入 Home screen 的確認視窗會跳出來兩次。<br />
<br />
首先我們就從 Browser 的 browser.js 開始,按下『加入至 home screen』後會使用下面的 API 呼叫加入 home screen 的 dialog 。<br />
<br />
<pre class="brush: js">new MozActivity({
name: 'save-bookmark',
data: {
type: 'url',
url: this.currentTab.url,
name: this.currentTab.title,
icon: place.iconUri
}
});
</pre>
<br />
MozActivity 是怎麼呼叫 Dialog 的呢?經過追蹤,當你呼叫了 MozActivity 的時候,真正執行的是 B2G/gecko/dom/activities/src/Activity.cpp。當你找出這個地方後可以用 gdb 來確認是不是這裡,詳細的用法可以參考 <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Boot_to_Gecko/Debugging_on_Boot_to_Gecko/Debugging_B2G_using_gdb" target="_blank">Debugging B2G using gdb</a>。Activity:Initialize 的最後面的程式碼是這樣:<br />
<br />
<pre class="brush: c">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;
</pre>
<br />
事實上這個時候 B2G 去調用了同一個目錄底下的 ActivityProxy.js,這個時候用 Child Process Message Manager 的 sendAsyncMessage 丟了用來開啟 Activity 的訊息出去。<br />
<br />
<pre class="brush: js">cpmm.sendAsyncMessage("Activity:Start", { id: this.id, options: aOptions });
cpmm.addMessageListener("Activity:FireSuccess", this);
cpmm.addMessageListener("Activity:FireError", this);
</pre>
<br />
ActivityService.jsm 的 receiveMessage 會接收到這個訊息,並且交由 this.startActivity 來處理之。而 startActivity 決定完成要用哪個 app 開啟這個 Activity 後,再用 system-message-internal 的 sendMessage 丟出一個名為 activity 的訊息。<br />
<br />
<pre class="brush: js">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));
</pre>
<br />
最後到了 SystemMessageInternal.js 裡面的 sendMessage 最後會調用 _processPage 來開啟正確的 App。<br />
<br />
<pre class="brush: js">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));</pre>
<br />
而追蹤的 bug 的問題點就在這裡,這邊有兩個 match 的 page,所以他連續開啟了兩次 add to home screen 的 dialog。當我追蹤到這邊的時候,其實基本上已經找到問題的根源了。不過在跟別人討論的過程中突然發現有另外一個 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=795782" target="_blank">bug 的 patch</a> 已經解決這個問題,而且在 nightly 的 build 也不會有這個問題,所以我就沒繼續追蹤下去了。<br />
<br />
藉由這次機會也從 Gaia 到 Gecko 看了一大圈,也算是很有收穫 :D Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-4252392230328487322012-09-28T10:17:00.001+08:002012-10-18T15:52:44.932+08:00HTML5 mobile app 練習這幾天在練習 HTML5 mobile app,決定找一個 Android 底下的 app 來重刻成 HTML5 版本,我挑的是 News & Weather 的天氣部分。<br />
<br />
<img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg09RrOyV0Or_9nTgHX6g_zyBp1fcqS2eHYeIolvv6GUwGXhbik1Zy1otvh62NQE8lTK5K4vsreUPbHoPVdHhTDbvBDwpt18hd94fC4MVoeGKDVeKHjcvC21xctKcOKhqMjJoo5Il55MOYP/s400/Screenshot_2012-09-28-08-23-19.png" width="225" />
<img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtqjncDyDoEcoX3OitwMUXQ00Rtk0pOj3kK6pUkdyW77fiCumnrhAVPcMiBHaIl3uH7e97cLoj_nu8UYUzk7Z9gcuXaXmVZPniIT03D2rZDwIKdlfUUNGWNWuh7GcHeoZh9r5rrig2pFv2/s400/android-weather.gif" width="225" /><br />
<br />
這是一個可以根據你現在地點給你當地天氣的 app,按下右邊靠近上面的驚歎號圖示可以看今天一整天的溼度以及溫度變化,在圖形上面用手指滑動可以看指定時間的溼度。<br />
<br />
我找了一下如果要每個小時的溼度與溫度的 Weather API 大多都要錢的,所以最後我接了 World Weather Online 的 API, 但是圖表就換成接下來五天的天氣氣溫變化,當作練習就是了。<br />
<br />
成果如下,他只能跑在 Firefox for Android 上面,我沒有針對 Chrome/Safari 等 webkit 系列調整。<br />
<br />
<img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgl4PwkXuILCBOgz9sgyazOkgXsuNCA27PcRhDjsh19Gl_zYuChI5gBMComSYpUalhnprT5HbOkLLSigFBh9Xxbqh2E1p5ePRBAxdZ95aoe095YIICmqdkVUcPe953xEIO7L9YoLLRvUtmt/s400/Screenshot_2012-09-28-08-21-37.png" width="225" />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfeWuERI4n3mDcfwJaN7shRWjUEmUGKFb6S1JLVG94jdsWKwumx6DgTlUj2XzGTvLq4kH1yoGbNIN6jhif_8qMXOPx8nbszlVfwgLptf3VGiYuEog5KmbA7hmKmsVAzZOuLN2skZyp-cRX/s1600/mobile-weather.gif" />
<br />
<br />
<br />
作完之後有一些感想...<br />
<ol>
<li>如果不考慮相容性,現在的 CSS3 真的很強大,以前很詭異的排版方法現在都變得好排多了,我連右上角驚歎號圖示按下去顯示另外一頁都是用 CSS3 完成的,不需要 javascript。</li>
<li>CORS 我還是沒搞定,最後用比較醜的 JSONP 解決。</li>
<li>SVG 雖然好用,不過看起來效能在 mobile 上面還無法接受,目前應該還是用 Canvas 比較好。</li>
<li>CSS/SVG 的漸層效果在 Firefox For Android 上面看起來很差,不知道爲什麼。</li>
</ol>
然後我很懶惰的沒有做 SVG path 的圓角,看起來好像沒有像是 rect 的 rx,ry 可以直接設定...<br />
<br />
Source code 我放在 <a href="https://github.com/yurenju/mobile-weather">github</a> 上面,有興趣的參考參考。Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-32525004728989059882012-09-06T08:53:00.000+08:002012-09-06T08:53:28.405+08:00cinnamon - 用 Javascript 高度客製化的 GNOME3<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiplDzeJLWLH5NMKtVaZw2qBbExFhx58_Ny5Yw4YS3qgCu2IpZtOrJClgMD9TRAqvjHS_Ql4v8EhqMKQno8s5bH44t60IhyphenhyphenmQO6eDo9_I5Lxny-bhDDR4ilJ4VpSPX_kyS3EyArmOrXpjjc/s1600/%E8%9E%A2%E5%B9%95%E6%88%AA%E5%9C%96%E5%AD%98%E7%82%BA+2012-09-06+08:05:02.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiplDzeJLWLH5NMKtVaZw2qBbExFhx58_Ny5Yw4YS3qgCu2IpZtOrJClgMD9TRAqvjHS_Ql4v8EhqMKQno8s5bH44t60IhyphenhyphenmQO6eDo9_I5Lxny-bhDDR4ilJ4VpSPX_kyS3EyArmOrXpjjc/s640/%E8%9E%A2%E5%B9%95%E6%88%AA%E5%9C%96%E5%AD%98%E7%82%BA+2012-09-06+08:05:02.png" width="640" /></a></div>
<br />
看了 小 Q 的 Facebook 之後,我也裝了 cinnamon 來用用。我之前一直推想 cinnamon 應該是把 GNOME3 裡面的 gnome-shell 的高度客製化,這次裝起來果然是這樣。一個很簡單的確定方式就是按 alt + F2 輸入 lg,就會從下面拉起一個 javascript console, debugger 跟 inspector 合一的開發工具 LookingGlass。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirmyvrhZgqEN1-XvWvV7MwjtILdK7C8pDGaqq9MGAvYN-kUbCoD0w2QAapY3a_GpTNjyNFu9rV0k8QHM1ACnU8-f2RnKDVXoL0FTMbb22bRJglv6NPPe4Ca80As4_kAiufjsqK5ZBt4f5C/s1600/%E8%9E%A2%E5%B9%95%E6%88%AA%E5%9C%96%E5%AD%98%E7%82%BA+2012-09-06+08:34:08.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirmyvrhZgqEN1-XvWvV7MwjtILdK7C8pDGaqq9MGAvYN-kUbCoD0w2QAapY3a_GpTNjyNFu9rV0k8QHM1ACnU8-f2RnKDVXoL0FTMbb22bRJglv6NPPe4Ca80As4_kAiufjsqK5ZBt4f5C/s640/%E8%9E%A2%E5%B9%95%E6%88%AA%E5%9C%96%E5%AD%98%E7%82%BA+2012-09-06+08:34:08.png" width="640" /></a></div>
<br />
如果去查看 cinnamon 的套件檔案列表,也包含相當多的 Javascript。<br />
<br />
因為很多人不知道,所以我再強調一次,<b style="color: #cc0000;">整個 gnome-shell 都是 Javascript 寫的,所以 cinnamon 也都是整個用 Javascript 寫的。</b><span style="font-size: small;"> </span><br />
<br />
比起 Ubuntu 整個砍掉重練用 compiz 重寫一個 unity 桌面環境,我覺得 cinnamon 用 GNOME3 既有的成果開發桌面環境是比較好的選擇,畢竟 Javascript 一來是比較多人會用的程式語言,另外在需要擴充功能的時候,Javascript 也可以很快的就寫好擴充。<br />
<br />
更何況 cinnamon 就算是高度客製化他還是 GNOME3,很多資源都是可以共享的,而 unity 就等於完全新開一個獨立的分歧,恐怕濃厚的 Ubuntu 色彩會讓其他 distribution 不太願意使用。(不過 Fedora 18 似乎要把 unity 加入 repository 裡面 )Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-66081655031191494972012-08-01T15:22:00.000+08:002012-08-03T00:27:42.373+08:00ReactiveUI 概念篇ReactiveUI 解決了我兩個問題,一個是把 App 中可以測試的部分擴大,二是解決 UI/Thread 糾結的問題。<br />
<br />
你怎麼測試你的 app 的呢?底層我們當然可以透過 unit test 的方式進行,但是通常來說 UI 上面的東西都是比較難以測試的。通常我們會透過自動化 UI 的測試如 Sikuli 測試。但是這樣自動化 UI 常常會跟畫面綁定造成常會因為畫面變更而需要重新製作測試的 script。至於 MVC 裡面 Controller 的部分常因為跟 Event 綁定造成 Controller 其實是很難以測試的。ReactiveUI 是一種 MVVM (Model View ViewModel) 的 framework,其中由 ViewModel 取代 Controller 達到連 ViewModel 這個部份都可以測試,原因在後面讓我娓娓道來。<br />
<br />
另外,我們這邊討論的 MVVM 只限於 ReactiveUI,我並沒有用過其他的 MVVM Framework。<br />
<br />
<h3>
MVC v.s. MVVM</h3>
<div>
<br /></div>
<div>
MVC (Model-View-Controll) 是大家現在非常常用的 pattern,基本上就是把資料 (Model)、UI (View)、邏輯 (Controller) 分開來。在 Android 或一般的 WPF 裡面,我們會把事件處理也放在 Controller 裡面,Controller 可以作為聯繫 View 與 Model 的橋梁處理各式各樣的事情。所以 Controller 處理了邏輯跟事件。那 Controller 有辦法獨立進行測試嗎?這只有在你把邏輯與事件處理拆開後才有辦法單獨針對邏輯測試。</div>
<div>
<br /></div>
<div>
MVVM 是有別於 MVC 的另外一種設計方式,架構如下:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-6E7bBFIPw1c/UBcWbbSfpFI/AAAAAAAARLE/41qOIwl8ooA/s1600/mvvm-reactiveui.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="343" src="http://4.bp.blogspot.com/-6E7bBFIPw1c/UBcWbbSfpFI/AAAAAAAARLE/41qOIwl8ooA/s400/mvvm-reactiveui.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
你可以能會說,這架構只是把 Controller 換成 ViewModel 而已,感覺不出來什麼差別阿?但其實不是的,ViewModel 反映了 View 裡面的資料。ReactiveUI 裡面的 ViewModel 包含了兩個重要的東西:<b>屬性與命令</b></div>
<div>
<br /></div>
<div>
我用下面這個 Mockup 舉例,這是一個可以選擇要把內容發布到不同 Social Network 的小程式,左邊的 100x100 是自己的大頭照 Avatar。右上角的輸入框是要發布的內容,下面的下拉選單是要發布到哪裡的選項。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf1j1MwO2l86fQDQcAoP907whlgXcfqKbRvPwyZ7G_pt_0M3IFDS9q0wwSQv0CewbMNk0MQ_YAUQk3E2AtsMEG4W60nH_GBQTnkAkwfiphc28liItK1CePZlvqWVmpzEXzY4S1UW0rsqEy/s1600/hello.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf1j1MwO2l86fQDQcAoP907whlgXcfqKbRvPwyZ7G_pt_0M3IFDS9q0wwSQv0CewbMNk0MQ_YAUQk3E2AtsMEG4W60nH_GBQTnkAkwfiphc28liItK1CePZlvqWVmpzEXzY4S1UW0rsqEy/s1600/hello.png" /></a></div>
<div>
<br /></div>
<div>
在 View 裡面的每個需要資料的元件,指定要跟 ViewModel 裡面的哪個<b>屬性</b>綁定,如同上面的例子,View 裡面要指定:</div>
<div>
<ol>
<li>圖片的 Source 位置 => AvatarSource</li>
<li>輸入框的文字 => ContentText</li>
<li>下拉式選單的選項 => SocialNetworks ["Facebook", "Twitter", "Google+"]</li>
<li>下拉式選單選擇的項目 => SelectedSocialNetwork</li>
</ol>
<div>
ViewModel 與 View 的互動是雙向的,比如說 (1) 我在輸入框裡面打了 "I saw The Dark Knight Rises today!",這個時候 ViewModel 裡面的 ContentText 就會更新成新的文字敘述。(2) 當我點擊下拉選單,把要發表的 Social Network 從 Facebook 改成 Twitter 後,ViewModel 裡面的 SelectedSocialNetwork 也會被修改成新的數值。從另一個方向來看,(3) 如果我在程式裡面修改了 AvatarSource 的網址,這個時候 View 裡面的圖片就會自動更新。所以兩個方向的更新都是可以的。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvTrj2klzrV5sfndUbs2daMYPPVrm9mZIlwO4Gk6xyfOvxYddHSvgoY6ZPISBJnSATYQON5umDojBRFJR6x-3y6YoCVJvXLTvksIVmc4bnaTnnJWCYmpErHko7vYRfjEJ-Nz0l8LEB3jz_/s1600/hello2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvTrj2klzrV5sfndUbs2daMYPPVrm9mZIlwO4Gk6xyfOvxYddHSvgoY6ZPISBJnSATYQON5umDojBRFJR6x-3y6YoCVJvXLTvksIVmc4bnaTnnJWCYmpErHko7vYRfjEJ-Nz0l8LEB3jz_/s1600/hello2.png" /></a></div>
<br /></div>
</div>
<div>
<br /></div>
<div>
至於命令 (Command) 則是獨立於 View Event 的部分。在上面這個例子裡面,按下 PostButton 之後就會執行 PostCommand。由於在 Command 裡面需要的東西,如要發表到哪個 Social Network,要發表的內容都已經綁定到 ViewModel 裡面的屬性了,所以不需要跟 View 互動,只要直接取用屬性及可,我們可以用簡單的虛擬碼表示:</div>
<div>
<br /></div>
<div>
PostCommand.Subscribe( _ => SendToSocialNetwork (SelectedSocialNetwork, Content))</div>
<div>
<br /></div>
<div>
由於屬性與命令都已經 Mapping 到了 ViewModel 了。這下子 ViewModel 的部分就可以被拿出來獨立測試。如同下面的虛擬碼:</div>
<div>
<br /></div>
<div>
Test {</div>
<div>
var ViewModel = new ViewModel()</div>
<div>
ViewModel.SocialNetworks.AddRange("Facebook", "Twitter", "Google+")</div>
<div>
ViewModel.ContentText = "Test Content"</div>
<div>
ViewModel.PostCommand.Execute()</div>
<div>
}</div>
<br />
如此一來 ViewModel 就可以測試了,而且架構非常乾淨!<br />
<br />
而 ReactiveUI 裡面包含了 ReactiveCommand 與 ReactiveAsyncCommand,當你要處理非同步事件只要用 ReactiveAsyncCommand 即可。<br />
<br />
這樣的架構是不是很乾淨呢?下次再來講 ReactiveUI 實際上要怎麼使用。<br />
<br />
等不及的人,這邊是 <a href="http://www.reactiveui.net/" target="_blank">ReactiveUI 的官網</a>。Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-17247565110444715182012-07-19T16:16:00.000+08:002012-07-19T16:28:21.705+08:00[H4] 人生自動化之 ifttt 與 on{x}<div class="separator" style="clear: both; text-align: left;">
以下是 <a href="http://www.hackingthursday.org/" target="_blank">Hacking Thursday 聚會</a>討論到的東西,沒看過太多人介紹,所以自己介紹一下。</div>
<div class="separator" style="clear: both; text-align: left;">
<span style="background-color: white;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9ElYP96oZDNv8IShkne5rcijdn0zVKg1b1hqmo1td0pCGC5nbiUWvDjDpH8yclrfkiHDCrgZtEMR7lcr1ys2O4ETqZa29qHBPDsrzxNdADInkdlDXhOd-QLuSkvSnwar-kKivDY7et0IP/s1600/ifttt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9ElYP96oZDNv8IShkne5rcijdn0zVKg1b1hqmo1td0pCGC5nbiUWvDjDpH8yclrfkiHDCrgZtEMR7lcr1ys2O4ETqZa29qHBPDsrzxNdADInkdlDXhOd-QLuSkvSnwar-kKivDY7et0IP/s1600/ifttt.png" /></a></div>
<br />
<br />
IFTTT 其實是一個超簡單的東西,他連結了很多網路上各式各樣的服務,接下來就可以用 if this then that 的方式來做一些方便的事情,下面提供我使用的例子:<br />
<br />
<ol>
<li>如果<span style="color: #cc0000;"><b>我在 flickr 上面對一張照片打了愛心</b></span>,然後就<span style="color: #0b5394;"><b>把這張照片傳送到 Dropbox</b></span>。</li>
<li>如果 <span style="color: #cc0000;"><b>ecoupons.com 網站上有 ThinkPad 的特價</b></span>,然後就<span style="color: #0b5394;"><b>寄信提醒我</b></span>。</li>
<li>如果<span style="color: #cc0000;"><b>有人在 Facebook 的照片上 tag 我</b></span>,然後就<span style="color: #0b5394;"><b>把這張照片儲存到 Dropbox</b></span>。</li>
</ol>
<div>
相信這樣大家就很清楚 ifttt 的功能了。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0t-CSlEVssbIQbRaQZynb4vEPhookUNHM9Zo4yfFtWgwoMWcIi7NI1rKlXJso_V9BxoiDr0WEDqvUWYABPgt-99qtHPHp9BGcrkq_huQYND44XAODbo7_B6QYa9OtrFTX9VHtqBljf2lh/s1600/onx.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0t-CSlEVssbIQbRaQZynb4vEPhookUNHM9Zo4yfFtWgwoMWcIi7NI1rKlXJso_V9BxoiDr0WEDqvUWYABPgt-99qtHPHp9BGcrkq_huQYND44XAODbo7_B6QYa9OtrFTX9VHtqBljf2lh/s1600/onx.PNG" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
那 on{x} 呢?這個服務跟 IFTTT 一樣,但是他更結合了手機的位置當作觸發條件。一樣用幾個例子讓大家了解這東西怎麼用。另外這是一個服務,而且你需要在你的 Android 安裝相對應的 app。</div>
<div>
<ol>
<li>如果<span style="color: #cc0000;"><b>我離開家裡</b></span>,就<span style="color: #0b5394;"><b>幫我關掉手機的 wifi</b></span>。</li>
<li>如果<b><span style="color: #cc0000;">我離開公司</span></b>,就<b><span style="color: #0b5394;">寄送手機簡訊給我老婆上面寫『我要回家啦...』</span></b>。</li>
</ol>
<div>
<br /></div>
<div>
瞭解了吧,另外 on{x} 更好的是可以用 javascript 寫程式,可謂是 IFTTT 的超級強化版。</div>
</div>
<div>
<ul>
<li>IFTTT: <a href="http://www.ifttt.com/">http://www.ifttt.com/</a></li>
<li>on{x}: <a href="https://www.onx.ms/">https://www.onx.ms/</a></li>
</ul>
</div>Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com2tag:blogger.com,1999:blog-7050224734475821462.post-52007374692919403462012-07-06T18:28:00.000+08:002012-07-06T18:28:42.553+08:00在 Windows 透過 ssh 啟動 Linux 上 API Server 的測試方式如果你採用 Django, Node.js 或 Rails 這些網頁開發 framework 大多都不會真的在 Windows 上架設服務,通常會在 Mac 或 Linux 架設測試伺服器。開發 Windows 的時候比較好的解決方法就是在 VirtualBox (或其他軟體) 裝 Linux 連接到上面測試。<br />
<br />
這樣做在我的狀況下會有些問題,因為我的測試項目會更改到一些儲存在資料庫的東西會影響到下次測試的結果。所以我希望每次測試都可以用新的資料庫環境測試。今天下午花了些時間設定好,分享一下。<br />
<br />
基本上就是透過 putty 的指令介面 plink 來執行遠端 Linux 的指令,達到開啟關閉以及清除資料庫的功能。遠端的 script 大概長這樣:<br />
<br />
<pre class="brush: shell">#!/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
</pre>
<br />
在 Windows 這邊可以到 <a href="http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html" target="_blank">putty 官網</a>下載 plink.exe,先把它丟到家目錄去。遠端的指令要這樣執行:<br />
<br />
<pre class="brush: shell">%HOMEDRIVE%%HOMEPATH%\plink.exe -pw PASSWORD USERNAME@192.168.1.148 /PATH/TO/SCRIPT start
</pre>
<br />
你可能注意到我沒有用 public key 的方式做,因為不知道為什麼在 linux 產生的 private key 好像沒辦法直接拿來用。後來懶得研究就直接用密碼了。<br />
<br />
最後我們是要在 NUnit 跑的時候執行這個指令,所以你可以修改測試專案下面的 Program.cs 來達到執行指令的功能,下面我就貼出整個檔案了,基本上就是在前後加上執行的指令,ExecuteCommand 是從網路上貼來的。<br />
<br />
<pre class="brush: csharp">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
}
}
}
}
</pre>
<br />Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com1tag:blogger.com,1999:blog-7050224734475821462.post-21134672688667292312012-07-02T18:06:00.002+08:002012-07-02T18:06:44.337+08:00Jenkins, NUnit and VS 2010 express!好吧你沒看錯主題,我要講的真的是 Visual C# 2012 express。<br />
<br />
當要開始開發程式的時候,最重要的就是基礎架構先弄好之後開始,在 Windows application development 也一樣,但是跟其他平台開發有個不太小的不同,Windows 的開發軟體都是要錢的,所以你想要的功能基本上獨立開發者是無法負擔的。<span style="background-color: white;">你還在想念 Android, Java, Linux, Mac app 開發一些基礎的東西嗎?像是 Code Coverage, Source Control (TFS), Lint (</span><span style="background-color: white;">Static Code Analysis</span><span style="background-color: white;">)在 Visual Studio 都是要錢的。</span><br />
<br />
當然我們這種從 Android/Linux development 轉過來的人,當然是希望用 Open Source 的 solution。首先就從最基本的 Continues Integration 跟 unit test 講起。<br />
<br />
請先安裝好 Visual C# 2010 express,Jenkins, Java runtime。<br />
<br />
CI 當然就是選 jenkins 了,至於要搭配的東西就比較麻煩。在這邊我們採用 msbuild 建構專案,使用 NUnit 作 unit test。因為我們要在 Windows 上 build,所以 Jenkins 也要安裝在 Windows 上面。基本上就是從 jenkins 上面抓下來,找個風水好地放著,執行 java -jar jenkins.war 就行了。<br />
<br />
接下來安裝 msbuild, nunit 這兩個 plugins。接下來就到 jenkins → Manage Jenkins → MSBuild 裡面填寫 MSBuild 正確的位置。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRGy8Fv0ysq5dh-uuFm_38weVsmC9Vb3BepieM0XWXMAGys5AmNFIjeRZ9z49d3ols7s9JEPWvQ8D-JqFA526-aWUiDIuTE8drimPUgq8d0AO2ZjqfUY2dKgIX9QZjz7Kmo1wWuwYfXk6m/s1600/jenkins.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="430" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRGy8Fv0ysq5dh-uuFm_38weVsmC9Vb3BepieM0XWXMAGys5AmNFIjeRZ9z49d3ols7s9JEPWvQ8D-JqFA526-aWUiDIuTE8drimPUgq8d0AO2ZjqfUY2dKgIX9QZjz7Kmo1wWuwYfXk6m/s640/jenkins.png" width="640" /></a></div>
<br />
第二個是建立 NUnit 專案,你可以在 VC# 裡面 Tools → Extension Manager → Online Gallery → NUnit Test Application 安裝 NUnit 的 project template,怎麼寫 test case 就上網找一下吧。<br />
<br />
當你建立專案的時候,他會很佛心的幫你建一個執行檔組態。執行他就可以獲得 NUnit Test report。<br />
<br />
最後是 Jenkins job 的設定。在我們還沒有使用任何 Version Control 的時候,我們可以先從 VS project 裡面複製出專案的方式先代替 Clone project。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFcXP2XbA3Dk8_t7SYnlU72GuKfGZCxCSnrm0jq0qmZnUENqD1zbmBdHgaimD8-L_U5tluvYzkg_FAJbaUJm-mPUa38nkxIy2-3woMF1riXELZ3V0RTHXJF1aCSCmIVDu-SdzI0DJgdTWp/s1600/jenkins2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="430" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFcXP2XbA3Dk8_t7SYnlU72GuKfGZCxCSnrm0jq0qmZnUENqD1zbmBdHgaimD8-L_U5tluvYzkg_FAJbaUJm-mPUa38nkxIy2-3woMF1riXELZ3V0RTHXJF1aCSCmIVDu-SdzI0DJgdTWp/s640/jenkins2.png" width="640" /></a></div>
<br />
首先需要先清除 workspace 裡面遺留下來上次 build 過的東西。因為 Windows 刪除檔案跟目錄太囉嗦了,我這邊用 PowerShell 的指令代替。第二行則是用 xcopy 用複製的方式先把專案複製到定位。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkfuptg6HxtLmB4qt6NNgn6lM6MEyWi-dDpH_b1H9yQPArdr_YID4RCntphNIl4-A2kQcZal4grU-ZrY3hRmsL6m6xmSiDNgMKu1j4NiNmS8OcpCLglXEBhH-34QKHUCjZG3eZ6_JclNoP/s1600/jenkins3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="430" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkfuptg6HxtLmB4qt6NNgn6lM6MEyWi-dDpH_b1H9yQPArdr_YID4RCntphNIl4-A2kQcZal4grU-ZrY3hRmsL6m6xmSiDNgMKu1j4NiNmS8OcpCLglXEBhH-34QKHUCjZG3eZ6_JclNoP/s640/jenkins3.png" width="640" /></a></div>
<br />
<br />
第一個動作是用 MSBuild 去 build solution,這個 solution 包含了兩個 project,一個是主要的專案,另外一個是用來測試的專案。第二個動作則是跑由 NUnit project template 幫我們產生的 Test 執行檔,執行後就可以產生 TestResult.xml,第三個步驟就是引用這個檔案,讓 Jenkins 產生正確的報告在 Jenkins 的頁面上。<br />
<br />
這樣就基本的把 Jenkins 設定好了。Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-49224456296602281082012-06-17T13:23:00.000+08:002012-09-24T08:35:43.082+08:00[Android] 用 Google APIs Client for Java 撰寫自己的 APIs Client (1)你在 Android 上都怎麼實作連接 web service 的 client library 呢?在之前的 Project 中,通常都是用 Apache 的 <a href="http://developer.android.com/reference/org/apache/http/client/HttpClient.html" target="_blank">HttpClient</a>, <a href="http://developer.android.com/reference/org/apache/http/client/methods/HttpGet.html" target="_blank">HttpGet</a>, <a href="http://developer.android.com/reference/org/apache/http/client/methods/HttpPost.html" target="_blank">HttpPost</a> 刻自己的 client library,配合 <a href="http://code.google.com/p/google-gson/" target="_blank">google-gson</a> 將 Json 轉換成 Java Object。但我一直希望可以有一個函式庫或 framework 可以處理這些事情,之前也看過 <a href="http://www.restlet.org/" target="_blank">Restlet</a>,但是似乎不是很好用。最近開始使用 <a href="http://code.google.com/p/google-api-java-client/" target="_blank">Google APIs Client for Java</a> 之後覺得這套 framework 似乎可以把事情處理的很好,就開始使用了。<br />
<br />
這套 Library 對我來說的好處是<br />
<ol>
<li>很方便的切換不同的 HTTP Method,不必每次要換 Method 都需要重寫很多源碼</li>
<li>自己處理瑣碎的事情,像是 gzip 壓縮, 傳入傳出 JSON 的 String-Object 轉換,URL builder 這些都很方便</li>
<li>減少源碼,有了上述的東西,源碼行數當然是大幅減少了。</li>
</ol>
<div>
另外它還有些特性我不太用到像是同時支援 JSON, XML、同時 support GAE, J2SE/J2EE 跟 Android,甚至可以替換 HTTP Client、Json library 或 XML library。</div>
<br />
<br />
話說他的名字可能會讓你覺得這是一套給 Google APIs 專門使用的 client library,其實不然。除了可以透過它存取 Google Services,也可以利用它來建構自己的 client library。如果要初步的了解這套 library 可以先看 <a href="http://youtu.be/9fBcrzA-hWY" target="_blank">Google I/O 2011: Best Practices for Accessing Google APIs on Android</a>,這篇裡面有些使用他的基本知識。<br />
<br />
安裝很簡單,就直接照著 README 的說明,把該丟的 jar 檔放到 libs 裡面就行了。因為它同時支援 google app engine, Java SE, 跟 Android 開發環境,所以不同的狀況底下會有不同的 dependency,你可以參考我丟的 jar:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghCgy7zIPdPZ7aA8pMXI-t3XpqDkXMV44M1aJUVxubXX0nDI1elAnmfxYKz97X3eZ4HsghuhcE5OG0WcU5n-6rzBZenIgDqble_CcmajJdY_-bBFf73nYcm2BrfyT4DD5JF_jj0iryv-Dk/s1600/%E8%9E%A2%E5%B9%95%E6%88%AA%E5%9C%96%E5%AD%98%E7%82%BA+2012-06-17+07:58:45.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghCgy7zIPdPZ7aA8pMXI-t3XpqDkXMV44M1aJUVxubXX0nDI1elAnmfxYKz97X3eZ4HsghuhcE5OG0WcU5n-6rzBZenIgDqble_CcmajJdY_-bBFf73nYcm2BrfyT4DD5JF_jj0iryv-Dk/s1600/%E8%9E%A2%E5%B9%95%E6%88%AA%E5%9C%96%E5%AD%98%E7%82%BA+2012-06-17+07:58:45.png" /></a></div>
<br />
你可能剛開始就被它龐大的相依 library 嚇到了,事實上它可以透過 proguard 在 release app 的時候去除掉那些你從沒用到的地方,在 Yaniv Inbar 的投影片裡面也有提到它在他的例子中可以節省 95% 的空間。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1NEKARVIhXCXTq-SW4Fb7Rcgc_KyQbhsK_SjtgsVhMVemOxoyjsGHzxcWAEdj_Spsiroc4pIw627xhXgwK26KKXWlS-rJIDYc72gunUIaL6g-FO00ltT6neVBTKw5Te86qwWVvvt9NPs4/s1600/%E8%9E%A2%E5%B9%95%E6%88%AA%E5%9C%96%E5%AD%98%E7%82%BA+2012-06-17+08:03:51.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1NEKARVIhXCXTq-SW4Fb7Rcgc_KyQbhsK_SjtgsVhMVemOxoyjsGHzxcWAEdj_Spsiroc4pIw627xhXgwK26KKXWlS-rJIDYc72gunUIaL6g-FO00ltT6neVBTKw5Te86qwWVvvt9NPs4/s1600/%E8%9E%A2%E5%B9%95%E6%88%AA%E5%9C%96%E5%AD%98%E7%82%BA+2012-06-17+08:03:51.png" /></a></div>
<br />
<br />
怎麼樣開始用 Google APIs Client 寫自己的 APi wrapper 呢?下面參考 Google Task 跟 Google+ 的 API 提供了一個簡單的範本<br />
<br />
<br />
<br />
<pre class="brush: java">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);
}
}
</pre>
<br />
DEFAULT_BASE_URL 是 web service 基礎的網址,往後所有的 query 都會基於這個網址。當建立你的 Client 的時候,你可以任意的替換 HttpTransport 與 JsonFactory。這邊我們用的是 NetHttpTransport 跟 JacksonFactory。<br />
<br />
<pre class="brush: java">SampleClient client = new SampleClient(new NetHttpTransport(), new JacksonFactory());
</pre>
<br />
要開始寫頭一個 API 時,首先要先建立 Data Model,這邊我們先用 Twitter 的 user_timeline 測試。用 <a href="http://api.twitter.com/1/statuses/user_timeline/yurenju.json" target="_blank">http://api.twitter.com/1/statuses/user_timeline/yurenju.json</a> 可以獲得我的 twitter 訊息。而每個 tweet 物件的屬性非常多,我們先用 text 與 created_at 這兩個訊息來測試。
在這邊建立 TweetModel 如下:
<br />
<pre class="brush: java">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;
}
}
</pre>
<br />
重點其實是繼承 GenericJson,而 6-10 行主用的功能是協助我們把 text, created_at mapping 到符合 Android 寫作風格的 mText, mCreatedAt,剩下的部份則是 setter/getter。接下來就可以在 SampleClient 裡面加入 API。<br />
<br />
<pre class="brush: java">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;
}
}
}
}
</pre>
<br />
32 行提供了 REST 所用的 Path,其中 screenname 是會將變數替代入 Path 的參數,34-35 行的工作就是將 mScreenName mapping 到 screenname。當你設定了 mScreenName 後,執行 query 時就會帶入到 {screenname}。<br />
<br />
37-41 行的建構子,在這邊可以指定要用的 Http Method 以及要放入 Body 的變數。在這邊我們使用 null 原因是因為 GET Method 不需要填任何東西在 Body,但是使用 POST Method 的時候這邊通常就會把 JSON object 填入。<br />
<br />
43-48 行是真正執行 query 的地方。這個時候它會取得 Response,並且將內容用 TweetModel 陣列的方式剖析,最後回傳 TweetModel[]。<br />
<br />
25-29 行的地方做了 Reuqest 的 initialize,如果你有需要對 header 作手腳的話可以在這邊弄。<br />
<br />
完成了 Client 之後,最後就可以利用以下的方法來調用 getTimeline 的 API:<br />
<br />
<pre class="brush: java">TweetModel[] tweets = client.users().getTimeline("yurenju").execute();
for (TweetModel tweet : tweets) {
Log.i("SAMPLE", tweet.getText());
}</pre>
<br />
接下來如果有意外的話(咦)我會繼續講解 POST, PUT, modify headers 跟 authentication 的部份。Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com2tag:blogger.com,1999:blog-7050224734475821462.post-79175247448855963982012-01-21T17:16:00.000+08:002012-01-21T17:16:57.074+08:00Fedora 16 下換 GNOME3 Theme一時無聊看到 <a href="http://tiheum.deviantart.com/art/Gnome-Shell-Ice-Crean-Sandwich-280076980">ICS GNOME3</a> 的 Theme 覺得很有趣想換一下,沒想到還有點麻煩。主要在於 Fedora 上面的 GNOME3 Theme Selector extension 有相容性問題沒辦法安裝。<br />
<br />
解決的方法是只安裝 GNOME3 User Theme extension,然後再用 command line 修改 theme。<br />
<br />
<ol>
<li>下載 GNOME3 的 Theme,並且放到 .themes 裡面,如果沒這個目錄就創建一個</li>
<li>安裝 user theme extension:<br /># yum install gnome-shell-extension-user-theme.noarch</li>
<li>重開 gnome-shell,按下 alt + F2 輸入 r 按 enter</li>
<li>使用指令指定要使用的 theme:<br />$ gsettings set org.gnome.shell.extensions.user-theme name "Ice Cream Sandwich"</li>
</ol>
<div>
<br /></div>
<div>
這樣就可以切換到所指定的 theme 了。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiTYRsgDQTBjsFY_vy_W8C_kj5cJCnoy-tmYFLHNHiLof3aPlSbh2aTvHh9IbY5fV4OATYb5FnPCnQWlqOM3BdDWTgij4xiTRSQba_e_y9lNbCzG35PbrYQQ3nOhOV8FYWttxmOOsC2OuD/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E6%2588%25AA%25E5%259C%2596%25E5%25AD%2598%25E7%2582%25BA+2012-01-21+17%253A00%253A47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiTYRsgDQTBjsFY_vy_W8C_kj5cJCnoy-tmYFLHNHiLof3aPlSbh2aTvHh9IbY5fV4OATYb5FnPCnQWlqOM3BdDWTgij4xiTRSQba_e_y9lNbCzG35PbrYQQ3nOhOV8FYWttxmOOsC2OuD/s640/%25E8%259E%25A2%25E5%25B9%2595%25E6%2588%25AA%25E5%259C%2596%25E5%25AD%2598%25E7%2582%25BA+2012-01-21+17%253A00%253A47.png" width="640" /></a></div>
<div>
<br /></div>
<div>
這樣跟我的手機剛好搭成一套 :)</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidF-HU9RZ-cu3YX_jgAGmkl0_arfwO1h2MbWZ7SMekLPjRmeCNlwD-YXPtrk3A6aZE-36PVjiKuNtQ1Rj8mEbpRGTt1Gb1USGtIRCvj9inJ8Xo-2QACa3CCt-rWXHY8StMwBb5Vl361vKo/s1600/Screenshot_2012-01-21-17-03-07.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidF-HU9RZ-cu3YX_jgAGmkl0_arfwO1h2MbWZ7SMekLPjRmeCNlwD-YXPtrk3A6aZE-36PVjiKuNtQ1Rj8mEbpRGTt1Gb1USGtIRCvj9inJ8Xo-2QACa3CCt-rWXHY8StMwBb5Vl361vKo/s640/Screenshot_2012-01-21-17-03-07.png" width="360" /></a></div>
<div>
<br /></div>Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-43733215121000958152011-12-30T15:18:00.000+08:002011-12-30T15:19:27.978+08:00客製化 ListView 產生 ScrollRunnable 互搶的問題解法最近用了一個朋友寫的客製化的 ListView,主要的用途是作類似 iOS 上可以作 Pull down refresh 的功能。<br />
<br />
但遇到了一個小問題困擾我很久。<br />
<br />
因為 pull down 的 scrolling back to first item 的工作是由一個 ScrollRunnable 搭配 Scroller 實作的,這東西基本上平常運作都沒有問題,但是如果在 Scroll Fling 到最頂端的時候就會有 scroll 亂跳的問題。<br />
<br />
深究 ListView/AbsListView 之後,發現其實 AbsListView 內有一個 mFlingRunnable 負責控制 Fling 動作發生時的捲動動作,而當使用另外一個 ScrollRunnable 去控制捲動的時候,會跟原本的 mFlingRunnable 的捲動互搶,造成捲動亂跳的問題。<br />
<br />
知道原因了,要解決就很簡單吧?<br />
<br />
錯了。<br />
<br />
AbsListView 並沒有預留讓開發者存取 mFlingRunnable 的介面,拿不到 Runnable 就無法取消它。難道我要把整個 AbsListView, ListView 搬出來嗎 XDD<br />
<br />
還好找到了一個不太好的 workaround。但是鑑於要把整個 AbsListView 移出來的成本太大,就將就著用了。答案是當使用 smoothScrollBy 系列的 method 的時候,AbsListView 將會自己移除 mFlingRunnable 的操作:<br />
<br />
<br />
<pre class="brush: java"> public void smoothScrollBy(int distance, int duration) {
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
} else {
mFlingRunnable.endFling();
}
mFlingRunnable.startScroll(distance, duration);
}
</pre>
所以當你要操作自己的 ScrollRunnable 的時候,先執行 smoothScrollBy(0, 0) 就行了。因為參數都是 0 所以基本上 Fling 的動作就會取消了。<br />
<br />
如果哪位看官有更好的解法請務必跟我說 :D<br />
<br />
特別感謝 <a href="https://www.facebook.com/wuman">David Wu</a> 跟我一起看了這個問題!Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-85957348101579582422011-12-14T19:28:00.000+08:002011-12-14T19:28:49.839+08:00Android 自製元件 - PrismFlipper這幾天公司的 app 要作訂製的 notification bar,花了一點時間做出來,把他整理出來成一個小 view widget,這個 widget 主要是一個客製化的 notification bar,主要是想模擬三角稜柱翻動的樣子。跑起來大概像下面這樣:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe width="640" height="480" src="http://www.youtube.com/embed/Ra37YQMtJ5E?hd=1" frameborder="0" allowfullscreen></iframe></div>
<br />
<br />
使用方法也很簡單,用 flipper.showNext(text, reverse) 就可以使用了,下面附上比較完整的範例<br />
<br />
<br />
<pre class="brush: java">final PrismFlipper flipper = (PrismFlipper)findViewById(R.id.viewFlipper1);
final String[] texts = new String[] { "Refresh", "go to last read position", "last read post" };
flipper.setFrontText(texts[0]);
flipper.setBackground(new ColorDrawable(0xff3465a4));
flipper.setTextColor(Color.WHITE);
Button btn = (Button)findViewById(R.id.button1);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
flipper.showNext(texts[mPosition], false);
mPosition = (mPosition + 1) % texts.length;
}
});
btn = (Button)findViewById(R.id.button2);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
flipper.showNext(texts[mPosition], true);
mPosition = (mPosition + 1) % texts.length;
}
});
</pre>
<br />
完整的範例可以在 <a href="https://github.com/yurenju/PrismFlipper">github</a> 上找到,授權是 BSD license。Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-3520047162092357902011-10-17T17:34:00.000+08:002011-10-17T17:34:34.357+08:00Android 小技巧:找出未用的 Resource記得好像有其他 Android 內建的方法可以哪些 resource 沒有被使用。一時找不到,Google 一下發現另外一套:<a href="http://code.google.com/p/androidresourcetracker/">Android Resource Tracker</a>。<br />
<br />
使用方法也很簡單,在 project 目錄底下執行:<br />
<pre class="brush: text">$ java -jar <PATH_TO_JAR>/AndroidUnusedResources1.4.jar
</pre>
<br />
接著就會列出未被使用的資源:<br />
<br />
<pre class="brush: text">Running in: /Users/yurenju/git/YOUR_PROJECT
242 resources found
44 unused resources were found:
array : upload_photo_options
/Users/yurenju/git/YOUR_PROJECT/res/values/arrays.xml
...
</pre>Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0tag:blogger.com,1999:blog-7050224734475821462.post-84752584184025992252011-10-11T15:05:00.000+08:002011-10-11T15:05:14.842+08:00Eclipse 小技巧 - 同時設定多個 method breakpoint有時候正在除錯剛寫好的 class 時,會需要針對 class 底下的所有 method 設定中斷點。不過一個一個設定實在太麻煩了,Eclipse 提供一個很貼心的功能,可以直接將多個 method 設定中斷點。<br />
<br />
打開 Eclipse 找到列出所有 Method 的 Outline,用 Ctrl/Cmd 多選 Method 後,按右鍵選取 Toggle Method Breakpoint 即可。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi15-X_LhikBPZso1ghNcQ5Fv6MlW0PEyzV_gY2_Xm4we4lsTjDzmBYtFk_77KR6It1vARqUaZBgJNgd5yzZwwJ57PTvO369uHvQq4OMeOvKsWWXoMRHcBpUqcCRyjj2ziYLCbORkX86Q6v/s1600/eclipse.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" id=":current_picnik_image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi15-X_LhikBPZso1ghNcQ5Fv6MlW0PEyzV_gY2_Xm4we4lsTjDzmBYtFk_77KR6It1vARqUaZBgJNgd5yzZwwJ57PTvO369uHvQq4OMeOvKsWWXoMRHcBpUqcCRyjj2ziYLCbORkX86Q6v/s1600/eclipse.png" /></a></div>
<br />Yuren Juhttp://www.blogger.com/profile/09921699211443539030noreply@blogger.com0