[Android] 噗浪姬Project - Day3 OAuth


不知不覺來到 Day 3 了,今天介紹的是 OAuth ,但其實我也是第一次在 Android 上使用 OAuth 驗證,之前雖然有過,但那是在 C# 上作的開發。
我預計這節要介紹的有如何透過 OAuth 的 App Key 及 App Secret 取得 Token 及 Token Secret,並透過取得的 Token 資訊及 Authorization Url 讓使用者進行驗證,並透過驗證後的資訊取得 Consumer Key 及 Consumer Secret.

一開始我先試著在電腦上找到相對應的類別庫,這次的專案我用的是 signpost 類別庫,這是個輔助 OAuth 的類別庫。這時會遇到的第一個問題是,該如何引用JAR檔,有開發過 java 程式的應該可以跳過,不過我在這還是稍作介紹:
1. 下載相對應的 JAR 檔,在這專案關於 OAuth 我們會使用到 signpost-core-1.2.1.2.jar 及 signpost-commonshttp4-1.2.1.2.jar 兩個類別庫,第一個是他的核心,第二個則是如果要用 org.apache.http.client.HttpClient 來進行 Post 及 Get 的話就需引用他,這類別庫有撰寫相對應的程式碼。
然後將下載的檔案放到專案的目錄下(自己記好位置即可,我是都丟 lib 裡面)

2. 於專案下右鍵,選擇 Properties
圖一、專案目錄下右鍵點選 Properties
接下來如圖二,可以看到其實我已經將 signpost 加入專案了
圖二、Java Build Path
新增的方式是選點選 Add Library ,然後建立 User Library,按下 Next 後會讓你選擇 Library ,如果完全沒新增過,這時應該會是一片空白,點選旁邊的 User Libraries... 按鈕,按下 New 按鈕就可以新增一個 Library ,再點選新建的 Library 就可以點選 Add JARs 或 Add External JARs 將指定的 JAR 加到該 Library 裡。
圖三、Preference(User Libraries)
至此,已經將 Library 新建好,然後回到 User Library 目錄勾選後就可以將該 Library 加入到專案裡面。

3. 引用完當然要來試試 OAuth 寫的程式了,我就去網路上找相關的程式碼,並跟著copy撰寫(透過前人的知識來學習XD),耶,遇到第一個問題是  DefaultOAuthConsumer 只允許使用 HttpURLConnection 的問題,這個找了相當多的文件,還是沒找到解決方案,最後在那想著 JAR 的說明,有提到 signpost-commonshttp4-1.2.1.2.jar 的說明是「Adds support for signing Apache Commons HttpCore v4.x requests.」,突然在想這是否就是關鍵,把類別庫展開後發現 oauth.signpost.commonshttp.CommonsHttpOAuthConsumer 這個類別,利用他去 Google 發現相對應的語法,卡了一晚的問題就這麼簡單就解決了,有時真心酸。
圖四、Library 展開
接下來很開心的撰寫了... 想說實驗性質,就試著把他寫在 MainActivity 的 onCreate 上,咦?發生了 NetworkOnMainThreadException 這個例外!
Google 完後發現是因為新版後 Google 對網路的限制,他不允許主線程(UI)直接去存取網路,所以丟出了這個 Exception,解決方法有以下幾種:
1. 透過 StrictMode.setThreadPolicy 設定:於 onCreate 最前面加入以下語法
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD)
{
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
        .detectDiskReads()
        .detectDiskWrites()
        .detectNetwork()   // or .detectAll() for all detectable problems
        .penaltyLog()
        .build());
}

2. 把 AndroidMainifest 中的 android:targetSdkVersion 設成 9 就不會有這個例外了.
3. 透過 AsyncTask 或自行建立一個 Thread 來執行

在此我挑選的是透過第三個方法建立一個新的 Thread 來解決此問題,透過這方法可以使耗時的網路操作不會妨礙主執行緒的操作,或產生 ANR 的錯誤。

以下是這次產出的程式碼,透過 Apache Commons HttpCore 、 signpost 及 Thread 來取得使用者的資料。

package wct.apps.plurk;

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;

import oauth.signpost.OAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import wct.apps.plurk.Database.DBHelper;
import wct.apps.plurk.OAuth.Plurk;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;

public class MainActivity extends Activity {
 
 private DBHelper _db = null;
 private Plurk _plurk = null;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  TestLink t = new TestLink("t");
  t.start();
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }
 
 @Override
 protected void onDestroy() {
  super.onDestroy();
 }
 
 private boolean AuthUser() {
  return false;
 }
 
 private class TestLink implements Runnable
 {
  private Thread t;
  private String threadName;
  
  TestLink(String name)
  {
   threadName = name;
   Log.i("TestLink.ThreadName", name);
  }
  
  public void run()
  {
   Log.i("TestLink.run", threadName);
   OAuthConsumer consumer = new CommonsHttpOAuthConsumer("app key", "app secret");
   consumer.setTokenWithSecret("token key", "token secret");
         try
         {
          
          HttpClient client = new DefaultHttpClient();
       HttpGet request = new HttpGet("http://www.plurk.com/APP/Profile/getOwnProfile");
          
       consumer.sign(request);
       
       String content = client.execute(request, new BasicResponseHandler());
    Log.d("TAG", content);
    
         }
         catch(Exception e)
         {
          e.printStackTrace();
         }
  }
  
  public void start ()
  {
   Log.i("TestLink.start", threadName);
   if (t == null)
   {
    t = new Thread (this, threadName);
    t.start ();
   }
  }
 }
}



後記:
這一篇搞了好久終於生出來了,因為中間發生了一些問題無法執行,雖然查完資料後最後一一解決了,但還是不小心多卡了兩天了,對不起(跪~
然後在開發過程中本來我都用 Android 模擬器來開發,最後發現這樣不行,太慢且耗時了,就直接讓自己的手機上了XD,整個的執行過程中速度真的差很多!
下一篇應該是如何設計一個驗證取得 Customer Token 的畫面吧... !?

Reference:
淺談OAuth in Android -- 以Plurk為例子
OAuth Core 1.0 Revision A
解決NetworkOnMainThreadException的問題



留言

這個網誌中的熱門文章

DB 資料庫呈現復原中

Outlook 刪除大量重覆信件

[VB.Net] If vs IIf ,兩者的差異