好久沒玩些好玩的了….先來點小菜….(POC = Prove of concept, 只是剛好有些想法, 所以來證明一下可不可行)

基本的想法是想用類似下面的javascript來寫出Android Activity(當然, 要延伸成其他的應該也沒問題)

https://gist.github.com/950551.js?file=gistfile1

這一個script有幾個簡單的組成 : 

  1. getcontentview : 用來指定這個activity所要用的main layout ID
  2. oncreate : 在Activity oncreate時被呼叫
  3. onresume : 在Activity onresume時被呼叫
  4. onpause : 在Activity onpause時被呼叫
  5. 基本延伸函數 : 
    • log: 等同於Log.d
    • findview: 等同於findViewById
    • R.layout(“layoutname”): 等同於R.layout.layoutname (同樣的R.id(“”)也是…)

因為要在原本Android Java的平台上跑Javascript, 所以需要一個Javascript engine做為一個平台, 這邊選用的是Mozilla Rhino

Rhino可以幾乎無痛的在Android上使用, 當然SL4A有更多的script engine的選擇, 但SL4A的方向不同, 我是想弄一個也可以寫一般的Android program的script

初始化Javascript engine

第一個版本, 只是拿來實驗的, 所以script先放在asset裡面, 這一段會初始化基本的script engine, 然後從asset目錄裡面載入"init.js"

首先要先初始化一個Context instance, 所有的script執行都是要透過這個Context的, 不用後可以透過.exit()來釋放資源

jsContext.setOptimizationLevel(-1);

setOptimizationLevel(-1)這段很重要, 沒設成-1的話是無法在Android上跑的, 原因是Rhino會把javascript編譯成java bytecode以增加執行效率, 但這bytecode是標準的java bytecode而非Android dalvik的bytecode, 設成-1的話, Rhino會改用Intepreter來跑, 而非先compile

Host objects 和 Host functions

ScriptableObject.putProperty(jsScope, “R”, RObj);

ScriptableObject.putProperty(jsScope, “log”, new Log());

ScriptableObject.putProperty(jsScope, “findview”, new FindViewById(this));

這一段包裝了一個host object和兩個host functions讓script使用(亦即是前述的5)

我把R包裝成另一個Object供javascript使用(因為我還沒找到如何讓Rhino使用static fields), 這包裝叫Rwrapper.java:

只是把R用reflection包裝一下而已

至於javascript function的部份, 其實也不難, 只要implement Function.call就可以了, 這邊實作了兩個(繼承自BaseFunction), Log.java和FindViewById.java

Log.java:

FindViewById.java:

不管是Object或是Function, 其實都是被當做objects來看待, 所以只要利用ScriptableObject.putProperty放到目前的scope去就可以給script取用了

目前看來, 這想法可行, 而且還蠻容易的, 缺乏的只是一些包裝而已, 甚至直接對view做操作也是可行的, 有空(會有空嗎?) 再來玩深入點.. :P

替照片上的每個人都加上標籤(tag)是一個蠻好用的功能, 這可以方便你找到有某人的照片, 或是一群人在某時的合照, 如果善加利用是相當好用的功能, 目前主流的幾個相片服務像是Flickr, Facebook, Picasa等等, 都有這樣的功能

其中以Picasa的功能最為先進, 它可以自動幫你把你所有照片裡的朋友都挑出來標示, 不像Flickr跟Facebook就只能手動

如果每張照片都只能手動標示的話, 標示完所有的照片是很累人的, 更何況可能還有很多未標示的舊照片, 就這點來說, Picasa就強勁很多, 至於Facebook和Flickr呢? 所幸有Face.com

Face.com是一個做自動標示的服務, 其實這樣講有點狹隘, 自動標示照片的服務只是它其中一個產品 - PhotoTagger, PhotoTagger的用處就有點像Picasa做的一樣, 自動幫你把照片的人物找出來做標示:

_2011-05-01_8
當然準確度不會到100%那麼高啦, 不過加上手動輔助的話, 已經可以節省不少功夫了

此外還有PhotoFinder和CelebrityFindr….不過這些並不是重點, 它真正比較強大的是, 它乾脆把它的Face detection和Face recogintion 的API開放出來, 讓你可以利用它們的雲端運算做出自己的應用

它的API是以REST的形態提供的, 支援的後端服務有Facebook, Flickr, Twitter, 基本的API也大致相當完整, train, detect, recongnize, tag等等, 如果是做Facebook相關的應用, 甚至可以省略過train的部份, 就可以達到辨識的效果

當然, 剛剛的應用像是PhotoTagger都是以拍完後的照片為目標而做的, 如果拍照完後馬上就可以找出有哪些人可以tag的話, 那就更棒了

為了實驗這個我寫了一個簡單的Android app - Face.me , 只要拿著Android手機對著你朋友拍一張(按螢幕畫面), 它就會利用Face.com去找出你這朋友的名字 (可以從連結下載APK安裝, 因為Camera部分不熟, 寫的不太好, 可能會有bug, 就不open source了 :P) 

這App利用了一個open source的Face.com的Java lib叫face4j, 基本上這lib移植到Android上並不困難, 它用到一些apache commons, http的library, 只要把這些含入就可以

要開發這個, 首先你必須要有Facebook API key和Face.com的API key, Face.com的API key可以到這裡註冊個新的, 另外還得把Facebook的API key和secret註冊到Face.com上(看你信不信任它囉)

Facebook API的部份, 我是用Facebook Android SDK去開發的, 其實也沒用到幾個API, 大致上只有login和取得自己的UID而已

另外由於face4j在reconigize這個method只支援檔案跟url兩種方式, 我另外修改了一個支援input stream的:

https://gist.github.com/950492.js?file=gistfile1

剩下的部份就簡單了, 在Camera.takePicture的第三個參數的PictureCallback.onPictureTaken裡加上:

QtADB 這應該是對Android developer很實用的懶人工具吧, 把原本需要下的adb command和fastboot常用的部份包裝成GUI

以下是它的使用說明:

看起來比adt內建的來的好一些, 實際上使用的時候, 不知道為啥抓不到我手機的目錄, 不知道是不是跟我adb版本有關, 不過在ADT下又是正常的

基本的功能大致上都有了, 還堪用, 不過如果能夠增加db browser, 或是start activity, service, broadcast intent等等功能, 或許會更不錯

Mac的使用者需要先裝Qt, 裝這裡下載的, 不要用port安裝的qt4-mac(因為目錄不同會讓QtADB抓不到At4 library)

雖然我說它"沒那麼有誠意", 不過我也想不出有啥方法做的比它更好

我想, 這東西的起因在於SDK 3.0 (Honeycomb SDK)為了Duo Panel的設計引入了Fragment, 當然還有Loader和Drag and drop等其他的新東西, 新東西本身並沒啥問題, 但問題在於, 如果要把原本Android的軟體porting到Honeycomb, 勢必要把很多的Activities改寫成Fragments, 但, 這引發了一個問題, Fragement並沒有向前相容, 軟體勢必要為Honeycomb跟pre-honeycomb分兩套寫法來維護, 這的確很不經濟, 因此有Google導入Fragment造成Android API的Fragmentation

所以可能因為如此, Google便有了“Android Compatibility package”這個解決方法:

Fragement for All

Google Releases Compatibility Package to Address Fragmentation Issues

這解法就是把這些class包裝成一個static java library (jar), 讓你可以在你的程式內含入, 所以在1.6以上的版本都可以享受Fragment的好處

但問題在於, 如果用過Honeycomb的Fragment一定會發現, 它跟Activity緊緊的綁在一起, 他們要怎解決替換這個Framework裡很重要的class, 答案是…沒有替換, 用另一個東西來取代 - FragmentActivity

也就是說, 要在pre-Honeycomb裡用Fragment所用的方式跟Honeycomb並不相同, 它的package不是android.app而是另一個android.v4.support.app, 目前也似乎只有把Fragment, Loader和新的CursorAdapter含進來:

也就是說, 跟Honeycomb比起來算是另外一套, 雖然也可以直接在Honeycomb上用, 但等於就是捨棄原生的用Compatibility package

使用的方法很簡單, 只要把jar檔加的classpath裡就好(在Sdk/extra目錄內):

還有一些要注意的:

  1. 要用Fragment的Activity要變成繼承自FramentActivity (那原本需要繼承其他的ListActivity之類的就很麻煩)
  2. 要用getSupportFragmentManager取代原先的getFragmentManager
  3. 有些framework裡的resources只在Honeycomb存在
  4. 附的ApiDemos是Honeycomb的, 不能直接用這個package, 要改

其實也還要一點工, 這也就是我說不是那麼有誠意的原因

以下是我用ApiDemos裡的FragmentLayout改出來的結果 (在Ver 2.X上):

關於這主題, Dianne Hackborn已經在這講的很詳細了, 這邊挑了一些要點

Duo panel的設計在iPad上已經是蠻常見的了, 這設計的一個特點是, 在Landscape模式時為了充分利用空間, 把Panel切割成兩部分, 但使用者轉換成Portrait時, 則會轉換成Single panel

不意外的, 在Android上實現Duo panel的方式是可以利用Fragment的

在Hackborn的範例中, 總共有兩個Fragment (TitlesFragment, DetailsFragment) 和兩個Activity (FragmentLayout - 這邊姑且稱之為main activity, DetailsActivity), 這部份的codes可以在ApiDemos裡面找到

在main activity的layout設計上面, 為了達到Portrait是single panel而Landscape是duo panel的設計, 其實是要portrait跟ladscape分開各一種layout, 在landscape是要包含左右兩邊的Fragment, 但portrait就只能包含左邊的

因此, 在portrait mode時main activity由於不會有右邊的panel的ID, 所以必須自行偵測右邊是否存在(或是偵測目前是不是在portrait), 然後決定按下list item的行為, 如果是portrait, 就不能使用DetailsFragment去取代右邊panel, 取而代之的必須呼叫DetailsActivity來顯示, 這作法其實頗為tricky, 而且在這作法下, 懶惰的programmer再也不能overrride onConfigurationChange來偷懶了, 對於習慣不好的programmer應該是蠻容易在Orientation change這邊產生side effects

上面就是利用不同的Orientation方向判斷到底是用DetailsFragment取代右邊Panel還是呼叫DeatilsActivity, 而DetailsActivity其實就只是一個DetailsFragment一個包裝, 也就是當在landscape時, 本來就只有main activity左右兩個Fragment, 在portrait時, 這兩個Fragment被拆開成兩個Activities, 但如果這時候從portrait的DetailActivity轉回landscape要怎回到原本main activity的duo panel型態呢? 作法很簡單(也有點tricky), 在lands cape mode時, DetailsActivity就把自己給finish掉了, 不過這邊要能夠確定history stack的前一個的確是main activity, 不然也會很怪的(一般應該都會是才對)

在Hackborn的範例中, 每次都得重新建立一個新的DetailsFragment, 這樣的缺點是, 每次花在DetailsFragment初始化還有inflat layout的時間會有點浪費, 如果是在複雜的layout跟Fragment, 這樣容易造成許多不必要的垃圾, 當然能夠reuse最好, 如果把她用的replace換成這個replace, 自己給個tag, 那之後就可以用FragmentManager.findFragmentByTag將這個Fragment instance給取出重複利用, 但, Fragment是不可以被重複加入的, 你可以拿instance出來重複使用但如果試圖把這個instance重複給FragmentTranscaction, 那是會發生錯誤的

附記: 如果沒去設定target sdk version是11的話, 那這個應用程式會被當做是小螢幕的而不是一個tablet application, 此時你看到的會是在中間一個縮小版的(學iPad也不要學這麼像嘛… = =“)