Thursday, December 22, 2011

淺談OAuth in Android -- 以Plurk為例子

OAuth是目前比較熱門的認證機制之一,他有個最大的好處就在於完全不需要在client端輸入賬號密碼,這部分完全會由web端完成。不過,他機制非常的複雜,有些地方做的讓人覺得很龜毛,所以我們在這篇只要淺談就好,其他的部分我會介紹一個函式庫來把它完成。

這邊有一篇非常不錯的OAuth機制介紹,有興趣的朋友可以看看。太細節部分的工作原理我們就不涉足了,我們專心看看最基本的認證步驟

  1. app開發者跟網站註冊取得API Key跟API Secret
  2. 開始認證的時候,用這組key/secret/callback(*1)跟網站要一個Request Token(*2)
  3. 用這組Request Token, callback組合成一個網址,讓使用者進入該網址認證
  4. 認證成功以後會傳回一個oauth_verifier,通常是一個很好閱讀的字串或者數字
  5. app端把這組verifier,以及request key跟網站取得半永久性的Access Token(*2)
  6. 往後對這個網站任何要求(比方說,plurk來講,取得時間軸)使用這個Token即可,app把它存起來備用,就不需要重新跟網站申請認證了。

這幾個步驟其實相當的繁瑣,尤其是對網站要求每次都要做一個全面性的Parameter Sign的動作,裡面牽涉的東西對一個初學者來講真是煩人到爆炸。當然,就會有人會寫一組library來解決這問題:我們今天介紹的就是oauth-signpost library

基本的取得jar,放進project,設定buildpath這些基礎到爆的東西我們就不討論了。首先,我們要先去Plurk申請一組API Key來作為我們的開發用途。所以我們就有了API Key/Secret了。接下來,回到Plurk API介紹的頁面,他提供了幾個網址供OAuth使用。基本上這些分別是幹嘛的我們就不予深究了,我們只要知道她是要怎麼用。

signpost我們會用到根OAuth有關的一共有兩個部分:OAuthConsumer代表的是我們APP端的所有資料,包含Access Token等等(不過她好像沒幫你存,你要自己存),而OAuthProvider則是代表網站的OAuth認證部分。

static final String PLURK_REQUEST_URL = "http://www.plurk.com/OAuth/request_token";

static final String PLURK_AUTHORIZATION_URL = "http://www.plurk.com/m/authorize";

static final String PLURK_ACCESS_URL = "http://www.plurk.com/OAuth/access_token";

static final String PLURK_CALLBACK_URL = "myplurk:///";

static final String PLURK_CONSUMER_KEY = "你的API Key";

static final String PLURK_CONSUMER_SECRET = "你的API Secret";


我們先把這些東西設定變數,等等會用到。接下來我們設定Provider跟Consumer

mainConsumer = new DefaultOAuthConsumer(PLURK_CONSUMER_KEY, PLURK_CONSUMER_SECRET);

mainProvider = new DefaultOAuthProvider(PLURK_REQUEST_URL, PLURK_ACCESS_URL, PLURK_AUTHORIZATION_URL);

ok,那所有東西差不多就完成了。我們要讓使用者認證的時候,只要開一個url,載入provider提供的url :

String url = mainProvider.retrieveRequestToken(mainConsumer, PLURK_CALLBACK_URL);

利用webview打開這個以後,把webview設定一個WebViewClient,然後override它的link click

private class AuthClient extends WebViewClient {

@Override

public void onPageStarted(WebView view, String url, Bitmap favicon) {

super.onPageStarted(view, url, favicon);

}

@Override

public void onPageFinished(WebView view, String url) {

super.onPageFinished(view, url);

}

@Override

public boolean shouldOverrideUrlLoading(WebView view, String urlString) {

Log.d("SubPlurkV2", "url : " + urlString);

if(urlString.contains("subplurkv2")) {

Uri url = Uri.parse(urlString);

String verifier = url.getQueryParameter("oauth_verifier");

try {

SystemManager.getInst().getAuthManager().aquireAccessToken(verifier);

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

finish();

}

return super.shouldOverrideUrlLoading(view, urlString);

}

然後我們可以取得verifier,利用這個取得Access Token

mainProvider.retrieveAccessToken(mainConsumer, verifier);

Log.d("SubPlurkV2", "Token = " + mainConsumer.getToken() + " and secret = " + mainConsumer.getTokenSecret());


把這祖Token記錄起來,以後會用到。最後,我們來試試看最簡單地拿取時間軸吧

mainConsumer.setAccessToken("U5GNYyH6wVGS", "aCtJ9XVgNTeIpigq3qxsLi70Sv0HbA6h");


 

URL url = new URL("http://www.plurk.com/APP/Timeline/getPlurks");

HttpURLConnection request = (HttpURLConnection) url.openConnection();

request.setDoOutput(true);

request.setRequestMethod("POST");

mainConsumer.sign(request);

request.connect();

String context = StreamUtil.InputStreamToString(request.getInputStream());

Log.d("SubPlurkV2", "" + context);


have fun! 參考plunk api list繼續實做其他的api吧!

Monday, December 19, 2011

幾個Android裡面Dependency的設定差異 (1)

 

Android裡面,設定Dependency有幾種方法,不過都各自有一些問題。

官方的做法是把其中一個設定成Lib Project,然後我們就能在Host專案裡面選取這個Lib Project,剩下的ADT會自動幫你搞定。

Select

Select

Check

 

然後,在要使用這個Library的專案,同樣的地方,下面的checkbox應該就會出現可以選擇的lib名稱,選Add加入就好

Screen Shot 2011 12 19 at 11 07 10 AM

 

這個方法是Android內建的方法,他有些問題跟限制。1. 他會被「Close not related project」關掉,這個簡單,大不了在打開 2. 他在clean build會出錯,要重復clean個幾次。3 被設定成lib的project就不能跑了,所以不能相依於另外一個完整地執行模組(手動開開關關lib開關是可以啦,只是麻煩)。

 

Wednesday, December 14, 2011

解決static library(.a)在IB以及某些category的靈異狀況。

Interface Builder是個很神奇的東西,裡面很多神秘的機制。其中最為讓人難以裡解的就是他跟dependency的恩怨情仇。對,android的java有更嚴重的問題(在UI上,android甚至沒辦法access到dependency的resource),不過這個Bug完全是因為Interface的linking機制是等到obj編譯完以後才開始找的關係(所以這個bug基本上只會出現在自定的widget class)。

這個bug大概會以這種形式發作:我們的.a(static靜態函示庫)裡面有某個widget class,而引用端的Interface Builder會引用這個class, 於是在某種情況下就出錯了

2011-12-14 13:58:50.999 TestPlatform[294:707] Unknown class MyClassBtn in Interface Builder file.

2011-12-14 13:58:51.010 TestPlatform[294:707] Unknown class MyClass2Btn in Interface Builder file.


這出錯的原因很簡單。因為Interface Builder是從.obj(compile from .a)裡面去獲得class的名字以及資訊,但是他找不到(廢話)。為什麼找不到?因為.obj裡面真的沒有這個symbol。所以IB只好很哀怨地把他設定回預設的class,比方說UIButton。當然,當你真的invoke起這個button又用了只有MyClassBtn才有的method,他就會理所當然地吐一個unregconized selector的runtime exception給你。

所以問題出在linker,自作主張地把這個.a裡面的symbol沖倒馬桶去沒有編譯進去.obj了。linker在編譯.a編譯成.obj的時候,為了一些效能以及檔案大小上的理由,會主動地去尋找其他的.obj裡面到底有沒有真的用到某個symbol,要是他在其他obj裡面找不到,那自然就會理所當然地把它扔了。

然後IB自然就沒辦法在編譯出來的.a找到symbol,一切合情合理。

解決的方法有兩個,我先聊聊第一個比較簡單的方法。為了防止linker愚蠢的(這樣將不太公平,天曉得這東西IB會用到?)把不該丟掉的symbol給沖到馬桶去,所以我們必須要呼叫他的static method來欺騙linker,這東西是有用的,不准丟掉。link show了一個比較hack的方法,你做一個static method,但是什麼都不做,然後在host呼叫他,linker就會被欺騙而不會把.a裡面的symbol丟掉才編成.obj。

其實下面也提到一個更簡單的方法,事實上你呼叫class靜態方法也可以,比方說[MyClassBtn class]。

後面會提到第二種方法,同樣可以解決這問題。不過再看第二種方法以前,我們先看看另外一個靈異現象。

Category也有類似的問題,不過起因不太一樣。當你去Categorize一個Native class(最常見的,恩,NSString)在編譯的時候會被錯誤的當作沒用到的丟棄了--這起因應該主要是因為linker的objc旗標buggy,我猜的--所以同樣的在使用static library時,library裡面native class的category都會無作用(很妙的是,自動完成使用的資料庫是.h,所以自動完成會自動地幫你找到method,但是runtime會因為找不到這個selector炸掉,真悲)。

解決的方法就是前面提過的第二種方法 -- 強制linker「你什麼symbol都不准丟掉」。在宿主的build setting裡面,找到other linker flags,加上-all_load或者-force_load(老實講前後兩者不太一樣,但是我沒有去追究他們到底有什麼細微的不同)。這同樣也可以解決IB找不到symbol的問題。

所以我們可以替這兩種方法做一個總結。第一種方法是藉由對linker額外的提示(但是code會混亂不堪),明確地告訴linker那些東西要額外地放進來。所以.a->.obj並不會include過多的symbol,不會造成.obj又肥又大的問題。當然缺點就是code要用一些非常莫名的東西來提示linker--當然IBOutlet/IBAction都用了,不在乎再多個幾行莫名其妙的code。我們其實可以藉由#define來稍為美化他一下,至少讓他[MyClass class];這行看起來不會那麼奇怪。另外,他對於category造成的問題其實是無解的。

第二種方法則沒有以上的所有缺點,但是會造成.a->.obj非常大--尤其當你的.a包山包海無所不包的時候(比方說我個人非常愛的Utility classes)--然後大通常伴隨的就是慢。不過,在普通的輕量級class裡面,這算是一個可以接受的效能損失。當然,category造成的問題,這是唯一解的樣子。聽說-force_load效能會比-all_load效能好,不過我沒比過。

static library問題多,還是用source code編譯最好(遠目)

Tuesday, December 13, 2011

iOS底下幾個NS Debug用的旗標

Screen Shot 2011 12 13 at 11 06 16 AM

首先,先Edit Scheme叫出下面這個畫面

Debug Arguments

 

這些Enviroment Varibles分別代表這些意思 :

 

NSZombieEnabled : 除錯BAD_ACCESS_ERROR用的,可以追蹤過度retain的時候的一些多於資訊

 

MallocStackLogging : 可以enable一些unhandled exception的call stack資訊。

 

NSDebugEnabled :設定除錯旗標,可以讓你在程式裡面判斷,比方說

#include <unistd.h>

int main (int argc, char ** argv, char ** envp)
{
#ifdef DEBUGENVIRON
 
if (!getenv("NSDebugEnabled"))
 
{
    setenv
("NSDebugEnabled","1",1);
   
... set the other variables ...
   
// Maybe this will complain about an autorelease pool.
   
char * executablePath = [[[NSBundle mainBundle] executablePath] filesystemRepresentation];
    execve
(executablePath, argv, environ); abort();
 
}
#endif
 
... do what you normally do in main() ...
}

但是老實講除此之外我也不知道他能幹嗎
Ref : 

http://stackoverflow.com/questions/3816136/condtional-environment-varialbes-in-xcode

http://www.yifeiyang.net/iphone-development-skills-of-debugging-articles-3-crash-after-debugging-skills-program/

Monday, December 12, 2011

iOS底下啓動Code Trace Stack的方法

XCode4以後,以往的Code Stack變成了一堆address map,所以當Unhandled exception被Throw, 就會出現象如下圖一般的悲慘畫面

Origin

 

這些Trace顯然對追bug幾乎一點幫助都沒有,inspect crashed thread又只能看到一堆assembly,以前那些親切的Exception Stack跑哪去了呢?

其實Stack trace在exception裡面一直都是存在的,我們要做的只是讓unhandled exception被印出來而已。為了達到這個目的,所以我們必須做個小小的手腳。

首先先介紹一個基本的觀念,在C裡面,所有的unhandled exception都有一個最終處理函數,這個函數是可以經由指標切換的。原始預設的函數就是印出一堆有的沒有的以後,執行exit()。我們要做的就是寫一個函數取代掉它

 

void uncaughtExceptionHandler(NSException *exception) {

NSLog(@"CRASH: %@", exception);

NSLog(@"Stack Trace: %@", [exception callStackSymbols]);

// Internal error reporting

}

 

接下來我們必須要告訴系統,預設的處理unhandled exception handler函數更改為這個函數

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

// Override point for customization after application launch.

NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);

 

return YES;

}

 

當然,我們有很多地方可以放NSSetUncaughtExceptionHandler,不過我們選擇放在application:didFinishLaunchWithOptions:裡面

 

然後我們就可以得到下面的結果 :

XCode4_ST_Fixed

 

看,是不是好除錯多了嗎? 至少可以知道exception是從哪裡丟出來的了