透過Facebook Android SDK去sign in Facebook其實很單純, 只需要這樣幾行code:

if (!facebook.isSessionValid()) {

    facebook.authorize(this, new String[] { “publish_stream” }, new AuthDialogListener());

}

其中AuthDialogListener是繼承自Facebook.DialogListener, 因為Authentication dialog是由Facebook SDK處理的, 應用程式只要負責處理onComplete, onError等等callback

但單純這樣的code會在手機內有安裝Facebook for Android時發生問題

因為後來Facebook導入所謂Single sign on的機制, 讓Application跟Facebook for Android是可以用同一使用者的

Single sign on的原理很簡單, 只是透過Facebook for Android去做proxy login的動作而已, Application只消去Facebook註冊一個自己的package signature就可以了

但由於原本舊的Login視窗是包在SDK內一起被build進applciation package, 但在SSO機制內, Dialog是由Facebook for Android出的, 因此如果沒有加下面的code, AuthDialogListener的callback都不會被call到

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

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

facebook.authorizeCallback(requestCode, resultCode, data);

}

這是由於Facebook SDK是用startActivityForResult去call Facebook for Android, 因此application必須加上這行自己接它的callback再轉送給原本的listener

這篇要示範的是, 如何寫一個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應該可以組合出其他不同的作法跟應用…