2014年12月15日 星期一
Material Design I : Floating Action Button (FAB)
自從Google再Lollipop(5.0)推出後,終於記得順便幫Android穿上漂亮的衣服,那就是 Material Design(MD)最重要的使命,不論是設計的元素、語言跟配色都有了屬於自己的Guildline。
對Android Developer而言,則是遵守官方規矩,就可以自己做出"有點"美感APP,官方BLOG還貼心的準備Check list給開發者(這表示幾乎都有附上source code)。而我們公司的產品也順著這班MD的風潮導入了許多元素,讓原本很iOS的APP找回了自己應有的面貌:> 這篇文章介紹其中一個導入的widget : Floating Action Button(FAB)
仿間有一大堆自製的牌子, 以下條列使用心得
https://github.com/makovkastar/FloatingActionButton
評論 : 最多star,多做了show/hide的animation,但用在5.0以下的Device會怪怪的(可能是我的project設定有問題)(這是我發的issue ticket),所以最後沒採用這套。
https://github.com/futuresimple/android-floating-action-button
評論: 它是fork上面那個的專案,不過它多做了sub-menu的功能(有點像Path的小紅點, 這也是MD其中一種元素),但沒有包裝show/hide的animation,最後我採用這套,因為他沒有上面的issue(神奇),最後我把上面animation的code搬到這個專案之中,搞定。
2014年10月11日 星期六
Paper.js : The Swiss Army Knife of Vector Graphics Scripting
Frontend的發展總是迅速,一堆超酷的Framework、Library總覺得永遠學不完;今天要介紹的Paper.JS大概又是讓我讚嘆已久的Library;還記得兩年前的HackDay,那時候的作品為了要呈現夢幻的感覺,用了Box2Djs及Canvas湊出了泡泡漂移的感覺,那時候寫起來特別吃力,因為Box2D的官方文件很簡陋,而且對於Html的Support也很粗淺,沒辦法它只是負責運算物理特效,所以Html的Event全部都要自己來!
今天看到了Paper.js的範例CandyCrash,有種相見恨晚的感覺,Framework的完整性超高,除了沒有物理運算以外,但呈現的效果已經比當初好的太多,而且可以有更多的延展性。
Brackets : a IDE for JavaScript, HTML and CSS
對開發者而言有幾點很棒的好處讓我也想使用它:
1. Auto Refresh : 裡面叫做Live Preview,也就是以後不用狂按Save-Reload,還可以安裝Static Preview的套件,就可以迅速架設一個Local的網站供其他人測試
2. 完整的文件提示 : 隨時按Cmd+K就可以看Html及Css屬性的文件
3. Color Picker
4. Image Preview
...
看來最近如果想玩Frontend的東西都會用它了吧!
Canvas coordinate system
最近加新功能的時,碰到一個在Canvas上座標系統認知上有所不同的地方;一般我們會用 Canvas.drawBitmap(Bitmap, 10, 10, Paint)把一張圖畫在(0,0)的位置,當然這個(0,0)不預期是整個device的(0,0),而是根據View最後被擺放的位置,例如某View被Layout在(100, 100),而Canvas.drawBitmap(Bitmap, 10, 10, Paint)則會畫在螢幕(110,110)的地方。
到目前為止都沒有錯誤,但如果translation的方式是用Matrix,而你的code可能會變成
Matrix matrix = new Matrix();
matrix.postTranslate(10, 10);
canvas.setMatrix(matrix);
Canvas.drawBitmap(Bitmap, matrix, Paint);
此時畫出來的結果就還是會在(10, 10)。原本猜想是忘記apply原本canvas的matrix所致,所以改成
Matrix matrix = new Matrix(canvas.getMatrix());
matrix.postTranslate(10, 10);
canvas.setMatrix(matrix);
Canvas.drawBitmap(Bitmap, matrix, Paint);
結果還是(10,10)!結果不小心喵到有Method叫Canvas.concat()。後來改成:
Matrix matrix = new Matrix(canvas.getMatrix());
matrix.postTranslate(10, 10);
canvas.concat(matrix);
Canvas.drawBitmap(Bitmap, matrix, Paint);
Matrix matrix = new Matrix();
matrix.postTranslate(10, 10);
canvas.setMatrix(matrix);
Canvas.drawBitmap(Bitmap, matrix, Paint);
此時畫出來的結果就還是會在(10, 10)。原本猜想是忘記apply原本canvas的matrix所致,所以改成
Matrix matrix = new Matrix(canvas.getMatrix());
matrix.postTranslate(10, 10);
canvas.setMatrix(matrix);
Canvas.drawBitmap(Bitmap, matrix, Paint);
結果還是(10,10)!結果不小心喵到有Method叫Canvas.concat()。後來改成:
Matrix matrix = new Matrix(canvas.getMatrix());
matrix.postTranslate(10, 10);
canvas.concat(matrix);
Canvas.drawBitmap(Bitmap, matrix, Paint);
最後就成功的畫在(110,110)的位置。百思不得其解的是:
Matrix matrix = new Matrix(canvas.getMatrix()) 應該會等於 canvas.concat(matrix)
因為都會Apply原本Canvas裡面的Matrix參數,於是乎就去看了一下底層的source code
matrix == null ? 0 : matrix.native_instance);
}
看來還要追到NDK那層,不過看起來兩種設定Matrix的方式有些出入,就等未來有空再往底層看了。
2014年9月27日 星期六
StreamTokenizer
最近在實作藍牙時,碰到處理接收資料的問題,原本是用InputStream.read(byte[]),但會碰到字串切割的問題,假設原本想傳送110給App,就有可能會收到1跟10的兩個數字;
因無法判斷何時需要斷點,直覺想到可以自定義分割符號(例如:冒號,100:110:120),如果要自己實作buffer的話又嫌太麻煩。
最後找到Java有StringTokenizer以及StreamTokenizer就可以自動幫我分割好字串(生活變得更加美麗),有興趣的話可以參考範例
參考鏈結:
StringTokenizer http://developer.android.com/reference/java/util/StringTokenizer.html
StreamTokenizer http://developer.android.com/reference/java/io/StreamTokenizer.html
因無法判斷何時需要斷點,直覺想到可以自定義分割符號(例如:冒號,100:110:120),如果要自己實作buffer的話又嫌太麻煩。
最後找到Java有StringTokenizer以及StreamTokenizer就可以自動幫我分割好字串(生活變得更加美麗),有興趣的話可以參考範例
參考鏈結:
StringTokenizer http://developer.android.com/reference/java/util/StringTokenizer.html
StreamTokenizer http://developer.android.com/reference/java/io/StreamTokenizer.html
2014年7月16日 星期三
How to handle binary file by Parse's Cloud Code
最近在開發Side Project的時候,因為不預期有Server Team的support,所以就來玩玩Parse的Cloud Code,這個東西大概在一兩年前就有碰過一次,那個時候是上傳一些感測器的資料,如果超過某個Threshold就要寄送notification到device。
這次的則是用在交易管理,最棒的是Stripe已經是Cloud Code的其中一個module,所以整合的過程不算太複雜;比較麻煩的事,我們需要上傳一包binary到Parse的Storage,如果由Client直接Create Data Object的話,就可以用ParseFile的物件直接搞定,該死的事繞過Cloud Code,它的Input預期是個JSON,這時候就有幾種解法:
1. 把Binary用Base64 Encode後放進JSON,但超過30MB的JSON會不會有其他悲劇發生?!
2. 直接Client端用ParseFile存檔,JSON裡面放上傳成功的URL。因為Binary沒有跟任何一筆Data Object關連,所以從後台Clean Cache的時候(可能)就會清掉。
3. 同上的Idea,但把資料放到S3。一樣很難清掉沒有意義的資料,因為存檔的動作都是由Client進行。
4. 用Pointer!Create 另外一個Table(Class),Client先Create好一筆Data後,把Object Id傳給Server做關聯。
最後我們採用第四種做法,兼具consistance以及簡化的流程。但看了Parse的Free Plan,20GB的儲存空間,以我們的使用情境,大概1000筆訂單後就要開始付錢了:<
2014年7月7日 星期一
Retrofit : A type-safe REST client for Android and Java
Square 雖然是做支付的公司,但也open了許多好用的library,像是OkHttp、Picasso跟Otto等等;目前公司產品內有使用的是Otto,它解決了Activity跟Fragment,或者Fragment跟Fragment之間的雙向溝通(關於Otto改天再寫別篇文章來描述使用情境),而今天要介紹的Retrofit能簡化Client跟Server間的Code。
舉例說明,如果你要對Restful Server拿資料,首先你要開HttpClient,然後把Api的參數(POST/GET/PUT/DELETE以及Parameters)塞進去;因為Android不允許Http Connection跑在UI Thread,所以你必須在包一個AsyncTask;接著要Parsing Response Body,如果是JSON可以用內建的JOSN Library去一層一層get出來,聰明一點就會用Gson Library。對了你別忘記了Http還有很多複雜的情境,像是Response Code : 301/302的Redirect、Cache的功能、Retry的機制等等等等。
完整的程式片段可能會像這樣(別忘了,它還沒辦法handle 301/302、Cache跟Retry的機制):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
protected onCreate(Bundle savedStateInstance) { | |
NetworkTask task = new NetworkTask(this); | |
NetworkTask.execute(); | |
} | |
public void successful(List<Repo> repos) { | |
// do something | |
} | |
public void fail(Exception e) { | |
// do something | |
} | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class NetworkTask extends AsyncTask<Void, Void, String> { | |
private Callback callback; | |
public interface Callback { | |
public void successful(List<Repo> repos); | |
public void fail(Exception e); | |
} | |
public NetworkTask(Callback callback) { | |
this.callback = callback; | |
} | |
protected Void doInBackground (Void.. params) { | |
// we hard code here, because it should be re-design more interface if we want to support different type of parameter. | |
String url = "http://example.com/users/PRADA/repos?limit=10"; | |
HttpGet request = new HttpGet(url); | |
AndroidHttpClient client = AndroidHttpClient.newInstance("Android"); | |
try { | |
HttpResponse response = client.execute(request); | |
HttpEntity entity = response.getEntity(); | |
return EntityUtils.toString(entity); | |
} catch (Exception e) { | |
return null; | |
} finally { | |
client.close(); | |
} | |
} | |
protected void onPostExecute (String result) { | |
if (callback != null) { | |
if (result == null) { | |
callback.fail(new Exception()); | |
} else { | |
callback.successful(parseJsonString(result)); | |
} | |
} | |
} | |
private List<Repo> parseJsonString(String json) { | |
// TODO | |
} | |
} |
Retrofit能讓這一切簡單一些,讓您不再畏懼/厭煩對接所有Restful API,關於使用細節就直接參考官方文件。把同樣的程式用Retrofit再寫一次的結果如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
protected onCreate(Bundle savedStateInstance) { | |
RestAdapter restAdapter = new RestAdapter.Builder() | |
.setEndpoint("https://example.com") | |
.build(); | |
SampleService service = restAdapter.create(SampleService.class); | |
List<Repo> repos = service.listRepos("PRADA", 10); | |
} | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface SampleService | |
@GET("/users/{user}/repos") | |
List<Repo> listRepos(@Path("user") String user, @Query("limit") int limit); | |
} |
如果有任何問題,歡迎各位一起討論。
2014年6月1日 星期日
Dart : The New Platform for Scalable Web App Engineering
Google的風格就是,當某種語言/工具無法滿足需求時,就會自己創造一個全新的,Dart也是其中一個產物。
Dart的目的是要取代Javascript,因為它實在是太多問題了(Firefox : 哪會),但目前除了Chrome以外,沒人願意把DartVM綁在自己的Browser裡,不過目前Dart Code還是可以轉成Javascript(轉換後不預期你做任何修改),執行在其他的瀏覽器上面(據說Performance也比原本的Javascript Native來的好),如此創新的作法還真的讓人躍躍欲試,但是,想到許多原本用的很開心的Javascript library都要等別人包成Dart的Plugin才能使用。
基於最後的結論,我還是緩緩先。
Reference:
Wiki : http://zh.wikipedia.org/wiki/Dart
https://www.dartlang.org/
Dart的目的是要取代Javascript,因為它實在是太多問題了(Firefox : 哪會),但目前除了Chrome以外,沒人願意把DartVM綁在自己的Browser裡,不過目前Dart Code還是可以轉成Javascript(轉換後不預期你做任何修改),執行在其他的瀏覽器上面(據說Performance也比原本的Javascript Native來的好),如此創新的作法還真的讓人躍躍欲試,但是,想到許多原本用的很開心的Javascript library都要等別人包成Dart的Plugin才能使用。
基於最後的結論,我還是緩緩先。
Reference:
Wiki : http://zh.wikipedia.org/wiki/Dart
標籤:
app,
fae,
javascript,
js,
web
2014年5月6日 星期二
Pull-to-Refresh on Android
Pull-to-Refresh(PTR)的功能在iOS是原本就support的功能,而在Android上面既然要靠第三方Library(現在則否)來實現,由此可見Andorid生態系的UI原件多麼貧乏;自然而然就會有開發者去寫一個類似iOS的功能:
Android-PullToRefresh : 我們產品原本也用這個,但是碰到難以延伸的問題之後就改用別的(後面會提到),難以延伸的問題來自原件直接繼承,如果你要用在GridView,那你就必須用PullToRefreshGridView,這就會碰到如果你的GridView是客製化的(可能你會用GridViewHeader,或StickyGridHeaders,這個問題來自另外一個需求,Android的GridView沒有辦法加Header跟Footer),那這個Library就會讓你傷透腦筋,有了這個設計瓶頸,使我們不得不嘗試使用ActionBar-PullToRefresh。
ActionBar-PullToRefresh : Android為了更像Android,連PTR的行為也改得跟iOS不同,也就是Refresh的時候在ActionBar上會有一條跑馬燈在奔馳著,這套Library就是採用這種實作,自然而然程式的架構也不會跟UI元件直接耦合在一起。不過使用它必須要注意的是API的版本跟ActionBar的實作方式,它推出了三種Library:
1. actionbar-pulltorefresh:library : api 14+
2. extra-actionbarcompat-pulltorefresh : api7+. 使用support-library實作ActionBar
3. extra-actionbarshelock-pulltorefresh : api7+. 使用ActionBarshelock
這套Library採用Delegate的方式與元件互動,有興趣的話可以直接看他們的example code
mPullToRefreshLayout = (PullToRefreshLayout) view.findViewById(R.id.ptr_layout);
ActionBarPullToRefresh.from(getActivity())
.allChildrenArePullable()
.listener(this)
.setup(mPullToRefreshLayout);
但可惜的是該套Library已經不在維護,因為Google官方的support-library(v19.02)已經默默推出了SwipeRefreshLayout;再者它跑在Amazon Kindle機器上也會有Bug,感覺是padding算錯了,所以沒有辦法跟ActionBar貼在一起,所以最後我們會採用Google官方的版本。
SwipeRefreshLayout(API 7+):他跟ActionBar-PTR不同的地方在於Animation,上者是從左到右的跑馬燈,而它從中間往外擴的跑馬燈,而且在拖拉到一半的時候不會有提示訊息擋在ActionBar上,API使用的方式也蠻像ActionBar-PRT,但目前是第一版所以有些問題需要自己修改。
1. 無法ListView/GridView無法ScrollUp : 這個問題會發生在你包Layout的時候,如果第一個元件不是List/GridView的話就會發生。
2. 拖拉時,該頁面"一定"會彈跳 : 有些Design不預期有這種行為,但現在的API架構也無法讓你取消它。
寫到最後,才發現如果把細節寫下去可能會長到太過誇張,所以這篇文章先定位成選用不同PTR Solution的Outline,而實作及設計細節就留給其他文章當作"救援投手"了。
2014年4月29日 星期二
The future of JVM : JRuby
昨晚公司邀請了JRuby的Contributor : Charles Oliver Nutter來談談JRuby,也談談未來JVM會的長相。JRuby的目的是讓原本只能活在JVM的Java Code,忽然可以跟世界外的語言有所互動,Ruby是其中之一,當然還有Python(Jython),為了要辦到這件事情有許多問題需要解決。
他提到第一點,Bootstrap Time,他用了JVM可以微調的Flag,加上Pre-load JVM的技術(Drip/NailGun/還有一個忘了),讓執行時間跟跑原本的Ruby環境差不多(個人懷疑Runtim memory可能大上好幾倍)。
第二點是Native Code,原本的世界裡,如果Java要呼叫Native的Code只能透過JNI(例如拿process ID),他本人是非常討厭JNI,除了寫起來吃力不討好之外,也提高的平台相依性(跟JAVA的初衷Write one, Run anywhere有所抵觸),他提到新的玩意叫做Java Native Runtime(JNR)也能辦到相同的事情,除了Interface比較簡潔外,也有龐大的community再幫你解決不同平台上所需要解決的Porting問題,其目的為把所有POSIX上支援的function都有一份Java對應的實作(不過目前還沒完全支援)。
最後他提到JAVA 7之後support的invokedynamic指令,從bytecode等級支援新的語法來提升動態語言的效能。他也提到說每個Engineer都會想要創造自己的程式語言,但礙于並非對Compiler有所專精,而且可能要寫非常多底層的Code來Optimize performance,所以有一個新的架構可以滿足/解決我們的問題,它是Graal,概念有點像是C跟LLVM,而JAVA跟Graal,實際運作是所有的Java Code不會直接Compile成Bytecode,而是Graal Representation,然後再透過Graal的IR做最佳化之後直接Run再Native Code上面;有了Graal後再搭配Truffle,它能夠讓你自己撰寫IR去Optimize你的Code(當然也可以寫JAVA),如此一來,你就可以打造一個完全屬於你的語言了(但還是跑在JVM上面)
備註:本篇文章是根據昨晚聽完簡報後所撰寫,可能技術細節的描述有所錯誤,麻煩請多多指教。
2014年3月31日 星期一
4am.tw + parallax scrolling
今天看完盧廣仲演唱會後異常熱血,喝了點小酒開始把之前hackthon的project重新從github checkout下來;這次想幫4am.tw用parallax scrolling的library(skollr)寫了另外一個版本. 資料全部來自於原本的網站(不知是否會有授權問題?);而source code(在這)。BTW因為它架在Heroku所以如果太久沒人用會呈現睡眠的狀態。
目前沒有做到很完美~歡迎大家指教。
2014年3月4日 星期二
Bolts it!
根據前一篇Deferred&Promise的討論後,我們最後選擇了Parse發佈的Bolts。這篇大概介紹一下Bolts的實際應用方式,某些部分比較像是再把官方文件在述說一次,最後再做個總結。
Q. 什麼情況下要用Bolts?
A. 所有會卡UI Thread的事情都要,包含網路存取,檔案讀寫,複雜的運算等等
Sample Code:
Task.callInBackground(new Callable<Result>() {
@Override
public Result call() {
// DO SOMETHING
return result;
}
});
Q. 那跟AsyncTask沒什麼差別呀?
A. 沒錯,但是Bolts可以應付更複雜的情境
情境:從網路抓一份list(約一百筆資料),並將list中的個人照片抓取下來,最後放到local資料夾。
難題:當然可以把所有事情放到一個AsyncTask裡面做完,所以整個Task時間拉很長,比較好的做法是不同的工作需求可以用不同的Executor來執行,而不同的Executor可以用不同的Policy來調整(包括ThreadPool的Size,Timeout的時間等等);而且每個Sub-Task也要擁有不同的fallback或Error Handling。
Sample Code:
Task.callInBackground(new Callable<List<User>>() {
@Override public List<User> call() {
return queryListFromNetwork();
}
}, NETWORK_EXECUTOR)
.onSuccess(Continuation<List<User>, List<Image>>() {
@Override public List<List<Image>> then(Task<List<User>> task) {
List<Image> list = new ArrayList<Image>();
for (User user : task.getResult()) {
list.add(loadImage(user));
}
return list;
}
}, NETWORK_EXECUTOR)
.onSuccess(Continuation<List<Image>, Integer>() {
@Override public Integer then(Task<List< Image >> task) {
int count = 0;
for (Image image : task.getResult()) {
saveToStorage(image);
count ++;
}
return count;
}
}, STORAGE_EXECUTOR)
.continueWith(Continuation<Integer, Void>() {
@Override public void then(Task<Integer> task) {
if (task.isFault()) {
task.getError();
}
if (task.isComplete()) {
// show success message
}
}
}, UI_EXECUTOR);
Q. 那是否支援等待多個Task?
A. 當然,可以使用Task.whenAll()
Sample Code
List<Task> tasks = new ArrayList<Task>();
for (int i = 0 ; i< 100 ; i ++) {
tasks.add(Task.callInBackground(new Callable<Void>() {
@Override public void call() {
downloadFile();
return null;
}
}));
}
Task.whenAll(tasks).continueWith(Continuation<Void, Void>() {
@Override public void then(Task<Void> task) {
if (task.isFault()) {
task.getError();
}
if (task.isComplete()) {
// show success message
}
}
}, UI_EXECUTOR);
Q 看起來那麼完美,那目前是否有些缺點?
A 當然,畢竟是v1.0.0,目前看到比較缺乏的功能:1. 無法cancel Task。2. 當使用whenAll的時候無法取得所有Sub-Task的Result。
如果還有什麼使用上或者解說上不是那麼清楚的地方請與我聯繫討論。
2014年2月17日 星期一
Kindle Fire's soft-key bar
最近APP要重新上架到Amazon的App Store,在Review時被測出一個Issue;就是在Full-Screen的Activity當置底(BOTTOM|CENTER)的DialogFragment彈出時,會被Kindle Fire的Soft-key Bar給擋住。
原因在於Kindle Fire的Soft-key不是固定占用系統的高度,所以在Full Screen時,如果彈出的Dialog可以支援Back Key來取消的時候,它就會雞婆的把Soft-key用Overlay的方式跳出,就巧妙地擋住部分的Dialog。
原本的解法是判斷手機是否為Kindle Fire,然後決定要不要加上一個神奇的Margin-Bottom,但後來跟同事Pair-Program後,發現到DialogFragment加道Window時,並沒有繼承現在Activity的屬性(也就是全螢幕),所以才會做出如此蠢事。
最後我們捨棄的Magic Margin,而是強制將DialogFragment設成全螢幕的模式,如此該死的Soft-Key Bar就不會莫名其妙的彈跳出來。
Sample Code :
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
2014年2月9日 星期日
您的Service能夠活多久?
昨晚整理以前信件時,發現某個資料夾裡放滿所有註冊網站服務的認證信,大約都是在2007到2009年所註冊,悲慘的是超過一半的網站都已經GG,以下條列他們以前的服務名稱以及網域。
- http://www.iloveeasy.com/
- CrazyVote
- http://www.pala88.com/
- http://www.bday2all.com/
- 搜股網
- http://www.showgood.tw/
- http://my98.98play.com.tw/
- http://www.badongo.com/
- 地圖日記
- HEMiDEMi
- http://www.ppolis.com
- 意慾蔓延
- joost
- http://www.postreach.com
- http://tw.blogdeco.jp/
- http://www.mybloglog.com/buzz/community/mybloglog/
- http://dob.tnc.edu.tw/
- http://slide.com/
Deferred & Promise
最近同事在解High Resolution issue的時候用到了Deferred/Promise的Design Pattern
問題是這樣子,要同時讀取及輸出N個圖片,但同時可以用M個Worker進行以節省時間,而圖片輸出的順序必須依照z-order排序,這表示圖片B輸出時必須等圖片A輸出完成。
這個問題的關鍵在於Task之間必須有Dependency的觀念,如果純用JAVA硬幹是可以用Thread Pool再加上Callable/Future實現,但針對這種問題已經有Deferred/Promise的設計概念可以使用。
簡單介紹一下D&P的概念(如上圖所示),如果Task之間有Dependency,甚至是帶入參數必須等前一個Task完成才能決定時,就非常適合使用它。
在jQuery 1.5之後已經有直接內建DeferredObject
http://api.jquery.com/category/deferred-object/
在Go的語言也是
http://golang.org/doc/effective_go.html#defer
Python也有人包library
http://twistedmatrix.com/documents/13.0.0/core/howto/defer.html
Java雖然不是內建但也有人包成library : jDeferred,當然也有Android的support
https://github.com/jdeferred/jdeferred
最近Parse也release一款類似的library : Bolts (iOS/Android supported)
https://github.com/BoltsFramework/
參考鏈結:
What are the differences between deferred promise and future in JS
http://stackoverflow.com/questions/6801283/what-are-the-differences-between-deferred-promise-and-future-in-javascript
訂閱:
文章 (Atom)