這篇要示範的是, 如何寫一個ContentProvider讓其他的應用程式透過這個Provider去存取網路上的資料

這不是正統用法, 不過的確可以這樣用, 那為啥要這樣呢? 只是好玩, 還沒想到特別的應用

這邊用的範例是Twitter search (search keyword): MLB

首先要建立一個放Provider的Package, 不過這個Provider不是拿來存取DB的, 傳統的Android ContentProvider通常是用來存取資料庫, 但那並不代表那就是它的全部, “Content"Provider不就是拿來提供"Content"的嘛?管他Content哪來

建立一個Provider, 姑且叫它做TwitterSearchProvider, Authorities定為twitter.my.search好了, 裡面, 先只實作一個"query" 

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco} span.s1 {color: #9a1867} span.Apple-tab-span {white-space:pre}

public Cursor query(Uri uri, String[] projection, String selection,

String[] selectionArgs, String sortOrder) {

return new TwitterSearchCursor();

}

這Query啥事都不用作, 回傳個TwitterSearchCursor就好了

從這看出玄機了嘛? 我們就是要利用Cursor, 在Cursor裡面實作連接網路取得資料的動作

後面我懶得寫解釋了, MBP快沒電了, 以下就是TwitterSearchCursor的Source:

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco} p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; min-height: 15.0px} p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; color: #9a1867} p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; color: #777777} p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; color: #429073} p.p6 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; color: #382ffa} p.p7 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; color: #0023c7} span.s1 {color: #9a1867} span.s2 {color: #000000} span.s3 {color: #0023c7} span.s4 {color: #382ffa} span.s5 {color: #8dafc9} span.Apple-tab-span {white-space:pre}

public class TwitterSearchCursor extends AbstractCursor {

static class Tweet {

String created_at = ”“;

String id_str = ”“;

String text = ”“;

}

ArrayList<Tweet> tweets = new ArrayList<Tweet>();

@Override

public String[] getColumnNames() {

// TODO Auto-generated method stub

return new String[] {”_id", “created_at”,“id_str”,“text”};

}

@Override

public int getCount() {

if(tweets.size() == 0) {

loadData();

}

Log.d(“AAAA”, “cnt=”+tweets.size());

return tweets.size();

}

@Override

public double getDouble(int arg0) {

// TODO Auto-generated method stub

return 0;

}

@Override

public float getFloat(int arg0) {

// TODO Auto-generated method stub

return 0;

}

@Override

public int getInt(int index) {

return index;

}

@Override

public long getLong(int index) {

return index;

}

@Override

public short getShort(int index) {

return (short)index;

}

@Override

public String getString(int index) {

Tweet t = tweets.get(mPos);

switch(index) {

case 1:

return “”+t.created_at;

case 2:

return “”+t.id_str;

case 3:

return “”+t.text;

}

return null;

}

@Override

public boolean isNull(int arg0) {

// TODO Auto-generated method stub

return false;

}

private void loadData() {

HttpClient client = AndroidHttpClient.newInstance(“TwiAndroid”);

        HttpGet get = new HttpGet(“http://search.twitter.com/search.json?q=mlb”);

        try {

HttpResponse resp = client.execute(get);

BufferedReader reader = new BufferedReader(new InputStreamReader(resp.getEntity().getContent()));

String line = null;

StringBuilder r = new StringBuilder();

while((line = reader.readLine())!=null) {

Log.d(“AAAA”, line);

r.append(line);

}

JSONObject obj = new JSONObject(r.toString());

JSONArray ary = obj.getJSONArray(“results”);

for(int i=0;i<ary.length();i++) {

Tweet t = new Tweet();

t.created_at = ary.getJSONObject(i).getString(“created_at”);

t.id_str = ary.getJSONObject(i).getString(“id_str”);

t.text = ary.getJSONObject(i).getString(“text”);

tweets.add(t);

}

        } catch (ClientProtocolException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (JSONException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

其實就是利用getCount()時去存取Twitter search API

為什麼要在這時候呢? 主要是我在remote端用的是Cursor來bind這些資料, 第一個會被呼叫到的是getCount()

由於整個Cursor會被跑在Provider所在的process並非跟caller同一個process, 所以heap也是吃在provider

這種利用Custom cursor應該可以組合出其他不同的作法跟應用…

昨天一直很想來寫這一篇, 沒想到今天凌晨, Google eBooks就發表了, 某方面來看, 概念跟我原本想講的差不多, 先看一下它的介紹

最近常在iPad/iPhone上看電子書(老實說, 手機並不適合看書), 就一直在想, 紙本書存在的必要性?什麼樣的書比較適合電子化?

首先第一個問題, 我聽過很多人說, 電子書很方便, 但還是無法取代閱讀紙本書的樂趣, 的確, 我個人也是會這樣認為, 至少"翻書", 夾上特別圖案的書籤, 這類的事在電子書上就沒辦法做到了, 不過, 玩傳統單眼的攝影師也常說無法接受數位, 但隨著數位攝影的進步, 這些人也漸漸投降了, 我覺得, 同樣的道理也可以套在這上面, 隨著數位內容的進步, 很快的, 人們也可以接受很多書都是電子化的

那什麼樣的書適合電子化呢?應該所有都適合吧, 不管是文字內容電子化, 或是以掃描的方式, 其實都可以任何型態存在, 但最適合電子化的, 我個人是認為是雜誌, 而且我覺得甚至它以後可以不用以紙本存在, 雜誌與一般書籍不同, 它可以提供最新的消息內容, 並且可以是一種娛樂, 其實, 漸漸的它的角色很容易被web給取代, 因為網際網路可以提供更快速更豐富的消息, 如果藉由現在行動平台的優勢, 可以提供內容更新, 更加豐富互動的內容, 那應該很多人也蠻喜歡的, 最大的一個好處是, 不會製造垃圾, 以往雜誌通常就看過就算, 但實體的雜誌常常堆積在那邊造成一堆垃圾, 搬家時更麻煩, 這種東西全面電子化應該也可以減少不少垃圾

對於電子書來說, 最重要的應該不是閱讀器, 很快的, 不, 就是現在, 閱讀器人人可做, 不管是硬體(Kindle)也好, 軟體也好(iBook), 甚至Web化(Google eBook), 使用者對閱讀器的選擇可以有很多種, 閱讀器的優劣差異也會越來越小, 因此這不會成為一個電子書發行的成敗關鍵, 最重要的, 反而是 – 平台

這也就是Google進來了, Amazon就要趕快反擊, 平台就如同是實體書店一樣, 任何出版商都可以鋪到任一種平台上, 就如同到金石堂或誠品上架一樣, 當然, 任何一個人都可以踩進來做平台, Google, Amazon可以, 出版商亦可以, 但出版商自己來, 流通性不高, 小型出版商也負擔不起, 因此到別人的平台上上架變成是一個比較經濟的方式

目前電子書平台的優勢在哪? 個人覺得是Cloud, 電子書平台不能夠單單的提供使用者購買並下載電子書而已, 還要有完善的管理功能, 這就像是有人蓋了一間很大很大的移動圖書館, 任何人都可以在這間圖書館裡面有一整排的書架, 買來的書直接擺在這書架上, 隨時想看的時候, 這間圖書館隨時都找的到(當然要有電子裝置在手), 而且要不管任何的裝置都要能夠取得你自己的書(這點就像是Amazon做的一樣, Google也做了)

除了這樣以外, 未來還能怎樣延伸呢? 其實不只傳統定義下的"書", 任何個人相關的知識內容應該要都可以存到這書架上, 比如說某年某月在某網站看到的一篇文章, 或是哪份電子報看到的一小篇剪輯, 甚至隨手寫下的小記, 應該都要能夠存到這座個人的圖書館去, 這樣的圖書館, 可以成為一個知識的累加基地, 甚至可以透過這樣的一個平台發表並販售自己的創作, 可以找到志同道合的人聯合創作並販賣, 以後出書可以不用找出版社, 也不用看出版社臉色, 只要有讀者, 到處都可以販售

Wikipedia目的是要建立一套百科全書, 但百科全書的內容來自於事實來自於歷史, 來自於前人的創作, 但知識的累積, 除了這些以外還仰賴了接連不斷的創作跟創新, 這些應該就是建立一個好的電子書平台的一個任務吧….

常常在看一些文章, 但share給朋友始終不是很方便, 大部分的軟體都會做透過e-mail share的功能, 但現在其實用Social network會比用e-mail來的頻繁(e-mail應該快恐龍化了吧), 如果先點share到e-mail再把link copy下來貼其實也很不方便, 後來找到Reeder和FLUD這兩個軟體, 前者是收費的, 後者目前還是免費

先來說Reeder這套付費的軟體, 介面相當的簡單直覺, 但這並不表示它的功能不好, 可以同步Google reader而且還蠻好操作的

最重要的是Share功能, 它提供相當多, 按下按鈕後就可以出現一個Panel裡面有很多支援的服務

對我來說有Twitter, Facebook就很足夠了, 可惜沒支援Plurk, 不過國外沒那麼流行Plurk倒是情有可原, 不過這麼多也很容易眼花撩亂, 所幸在設定裡面其實是可以開關每個服務的

Reeder還算不錯用, 價錢還算OK, 只是缺點是雖然支援Google reader但我有些跟的站, 內容似乎會抓不下來, 這是一大敗筆

再來說到FLUD, 它最大的好處是不用錢, 介面也新穎, 可惜的就是不支援Google Reader (不過iPad版居然是支援的, 不知道為啥iPhone版沒有)

這樣的介面自從Flipboard, Pulse流行後, 好像蠻多人採用的, 好處是可以直接看圖略過, 看到圖就沒興趣的就略過不看, 這樣感覺上還是蠻有效率的, 一樣可以share到Twitter, Facebook, 不過不支援Google Reader還有不是很穩定是致命傷, 希望在iPhone上很快就可以支援Google Reader

目前我會比較推薦Reeder (iPhone上), iPad上我就會推薦FLUD了(Pulse我是覺得很雜亂)

晚上快速翻了一變 memcached , 想了一些想法, 趁還沒忘之前先寫下來再去睡好了

memcahced的設計概念說實在的簡單到不行, 說簡單不是說他不好或是沒什麼, 反而這麼一個簡單的設計, 幫助真的會不小

memcached簡單說了就是一個分散式key-value based data cache, 資料是分散放在很多台電腦的RAM裡面, 因為是key-value based, 所以說起來就是一個大的hash table, 並不需要複雜的query, 只需要O(1)就可以取得資料, 而且資料就在RAM中, 可以是相當的快

我們都知道一個事實: “IO costs”, 如果每次都需要從database query資料, 那所花的時間, 包含SQL處理的時間, 以及IO的時間會是相當長的, 這點不管是Web application或是在mobile上(尤其現在SQLite被大量使用)都通用

因此使用RAM來cache query result as long as possible會是tuning這類的performance的其中一步, 這觀念應該是很多人都想的到

不過這樣得pattern可能得考慮的:

  1. 怎樣把一個常用且複雜的query對應到一個key-value pair
  2. cache的存續時間
  3. How to efficiently invalidate or update cache after data set changed

或許在Android的ContentProvider中可以引入memory caching的機制來減少IO的量

有關Style sheet的用法參考前篇

Three20的document跟Android比起來不遑多讓, 實在也少的可憐, 當然啦, 這樣也有另一種樂趣, 就是上網找跟追code囉!

剛嫌我的code在Launcher View上面的字型實在太醜, 很想把它改掉, 第一個想到的當然是Style sheet, 但遍尋TTStyleSheet, TTDefaultStyleSheet的document, 找不到類似的東西

後來在TTLauncherButton.m發現這一行:

[self setStylesWithSelector:@“launcherButton:”];

難不成就是"launcherButton"?

所以就試著在style sheet的class中加入:

- (TTStyle*)launcherButton:(UIControlState)state { 

UIColor *color = RGBCOLOR(30, 30, 30);

return [TTTextStyle styleWithFont:[UIFont systemFontOfSize:12

color: color

minimumFontSize:12 shadowColor:nil 

shadowOffset:CGSizeZero next:nil];

直覺想說是字型, 所以用了TTTextStyle, 不過這樣的code下場是….圖不見了!!

由於剛剛是在TTLauncherButton發現的, TTLauncherButton繼承自TTButton, 所以看了一下TTButton, label的字型是TTTextStyle沒錯, 而圖是TTPartStyle, 所以兩者都要給, 缺一不可, 而以TTButton.m的這行code來看:

TTPartStyle* imageStyle = [style styleForPart:@“image”];

圖的名字是"image"

(TTPartStyle *) styleWithName:style:next:

TTPartStyle的constructor除了name以外還需要style和next, TTStyle是個linked list, 所以沒有下一個style, next就指定成nil就好, 但style這參數卻不可, 試過給nil, 圖還是不見, 所以還是得繼續找怎回事

這時侯只好到Three20Style裡把TTDefaultStyleSheet.m的source挖出來看囉….(一挖出來發現, 我一開始就看這隻就好了嘛… = =“)

看了一下TTDefaultStyleSheet的launcherButton是這樣寫的:

- (TTStyle*)launcherButton:(UIControlState)state {

  return

    [TTPartStyle styleWithName:@"image” style:TTSTYLESTATE(launcherButtonImage:, state) next:

    [TTTextStyle styleWithFont:[UIFont boldSystemFontOfSize:11] color:RGBCOLOR(180, 180, 180)

                 minimumFontSize:11 shadowColor:nil

                 shadowOffset:CGSizeZero next:nil]];

}

Ok, 看來整段只要照抄再改就好, 所以上面那段就改成這樣:

return [TTTextStyle styleWithFont:[UIFont boldSystemFontOfSize:12

color: color

  minimumFontSize:12 shadowColor:nil 

shadowOffset:CGSizeZero next:[TTPartStyle styleWithName:@“image” 

  style:TTSTYLESTATE(launcherButtonImage:, state) next:nil]];

 

 (跟TTDefaultStyleSheet.m裡面那段有點小不同是, 一來是我是直接接在剛剛那段Text style後面, 二來只是驗證一下順序有沒差, 不過當然是沒關係的)