android項目源碼解析04:新浪微博客戶端源碼解析

       本文主要介紹如何構建新浪微博客戶端。以網上流傳weiboSina源碼爲例介紹,其下載地址爲:http://download.csdn.net/detail/ryzhanglu/3453875

1、項目概況

       該項目文件列表如下:


        其AndroidManifest.xml文件內容爲:

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.       package="cn.com.hh.view"  
  4.       android:versionCode="1"  
  5.       android:versionName="1.0">  
  6.   
  7.   
  8.     <application android:icon="@drawable/icon" android:label="@string/app_name"  
  9.         android:theme="@android:style/Theme.NoTitleBar.Fullscreen">  
  10. <!--   主Activity    -->  
  11.         <activity android:name="cn.com.hh.view.MyBlogActivity"  
  12.                   android:label="@string/app_name">  
  13.             <intent-filter>  
  14.                 <action android:name="android.intent.action.MAIN" />  
  15.                 <category android:name="android.intent.category.LAUNCHER" />  
  16.             </intent-filter>  
  17.         </activity>  
  18. <!--  註冊 授權登錄Activity    -->  
  19.         <activity android:name="cn.com.hh.view.AuthorizeActivity" android:launchMode="singleTask">  
  20.             <intent-filter>  
  21.                 <action android:name="android.intent.action.VIEW" />  
  22.                 <category android:name="android.intent.category.DEFAULT" />  
  23.                 <category android:name="android.intent.category.BROWSABLE" />  
  24.                 <data android:scheme="myapp" android:host="AuthorizeActivity" />  
  25.             </intent-filter>  
  26.         </activity>  
  27. <!--  註冊登錄Activity     -->  
  28.         <activity android:name="cn.com.hh.view.LoginActivity" >             
  29. <!--         <intent-filter>-->  
  30. <!--             <action android:name="android.intent.action.VIEW" />-->  
  31. <!--             <category android:name="android.intent.category.DEFAULT" />-->  
  32. <!--             <category android:name="android.intent.category.BROWSABLE" />-->  
  33. <!--             <data android:scheme="myapp" android:host="AuthorizeActivity" />-->  
  34. <!--         </intent-filter>-->  
  35.         </activity>  
  36. <!--  註冊登錄Activity     -->  
  37.         <activity android:name="cn.com.hh.view.HomeActivity" >              
  38.         </activity>  
  39. <!--  註冊登錄Activity     -->  
  40.         <activity android:name="cn.com.hh.view.ViewActivity" >              
  41.         </activity>  
  42.     </application>  
  43.     <uses-permission android:name="android.permission.INTERNET" />  
  44. </manifest>  

2、oauth2認證

       說說關於OAuth授權認證的事情,新浪開放api都必須在這個基礎上才能調用,所以有必要專門來講講,前面的文章中已經提到過關於新浪微博提供了OAuth和Base OAuth兩種認證方式,並且本項目採用OAuth認證方式,至於爲什麼採用這個OAuth認證而不採用Base OAuth認證原因很簡單,自從Twitter只支持OAuth認證方式以來,各大應用都紛紛轉向OAuth認證方式,而新浪微博的開放平臺也將在近日停止Base OAuth的認證方式。
     OAuth的基本概念,OAUTH協議爲用戶資源的授權提供了一個安全的、開放而又簡易的標準。與以往的授權方式不同之處是OAUTH的授權不會使第三方觸及到用戶的帳號信息(如用戶名與密碼),即第三方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權,因此OAUTH是安全的。同樣新浪微博提供OAuth認證也是爲了保證用戶賬號和密碼的安全,在這裏通過OAuth建立普通新浪微博用戶、客戶端程序(我們正在開發的這個android客戶端程序)、新浪微博三者之間的相互信任關係,讓客戶端程序(我們正在開發的這個android客戶端程序)不需要知道用戶的賬號和密碼也能瀏覽、發佈微博,這樣有效的保護了用戶賬號的安全性不需要把賬號密碼透露給客戶端程序又達到了通過客戶端程序寫微博看微博目的。這個是OAuth的作用。
      結合新浪微博的OAuth認證來說說具體的功能實現,首先羅列一下關鍵字組,下面四組關鍵字跟我們接下來OAuth認證有非常大的關係。
      第一組:(App Key和App Secret),這組參數就是本系列文本第一篇提到的建一個新的應用獲取App Key和App Secret。
      第二組:(Request Token和Request Secret)
      第三組:(oauth_verifier)
      第四組:(user_id、Access Token和Access Secret) 
     新浪微博的OAuth認證過程,當用戶第一次使用本客戶端軟件時,客戶端程序用第一組作爲參數向新浪微博發起請求,然後新浪微博經過驗證後返回第二組參數給客戶端軟件同時表示新浪微博信任本客戶端軟件,當客戶端軟件獲取第二組參數時作爲參數引導用戶瀏覽器跳至新浪微博的授權頁面,然後用戶在新浪的這個授權頁面裏輸入自己的微博賬號和密碼進行授權,完成授權後根據客戶端設定的回調地址把第三組參數返回給客戶端軟件並表示用戶也信任本客戶端軟件,接下客戶端軟件把第二組參數和第三組參數作爲參數再次向新浪微博發起請求,然後新浪微博返回第四組參數給客戶端軟件,第四組參數需要好好的保存起來這個就是用來代替用戶的新浪賬號和密碼用的,在後面調用api時都需要。從這個過程來看用戶只是在新浪微博的認證網頁輸入過賬戶和密碼並沒有在客戶端軟件裏輸入過賬戶和密碼,客戶端軟件只保存了第四組數據並沒有保存用戶的賬戶和密碼,這樣有效的避免了賬戶和密碼透露給新浪微博之外的第三方應用程序,保證 了安全性。
      本項目用爲了方便開發採用了oauth-signpost開源項目進行OAuth認證開發,新建OAuth.java類文件對OA進行簡單的封裝,OAuth類主要有RequestAccessToken、GetAccessToken、SignRequest三個方法,第一個方法RequestAccessToken就是上面過程中用來獲取第三組參數用的,GetAccessToken方法是用來獲取第四組參數用,SignRequest方法是用來調用api用。由於採用了oauth-signpost開源項目簡單了很多。具體代碼如下:

[java] view plaincopy
  1. public class OAuth {  
  2.     private CommonsHttpOAuthConsumer httpOauthConsumer;  
  3.     private OAuthProvider httpOauthprovider;  
  4.     public String consumerKey;  
  5.     public String consumerSecret;  
  6.       
  7.     public OAuth()  
  8.     {      
  9.         // 第一組:(App Key和App Secret)  
  10.         // 這組參數就是本系列文本第一篇提到的建一個新的應用獲取App Key和App Secret。  
  11.         this("3315495489","e2731e7grf592c0fd7fea32406f86e1b");  
  12.     }  
  13.     public OAuth(String consumerKey,String consumerSecret)  
  14.     {  
  15.         this.consumerKey=consumerKey;  
  16.         this.consumerSecret=consumerSecret;  
  17.     }  
  18.       
  19.     public Boolean RequestAccessToken(Activity activity,String callBackUrl){  
  20.         Boolean ret=false;  
  21.         try{  
  22.             httpOauthConsumer = new CommonsHttpOAuthConsumer(consumerKey,consumerSecret);  
  23.             httpOauthprovider = new DefaultOAuthProvider("http://api.t.sina.com.cn/oauth/request_token","http://api.t.sina.com.cn/oauth/access_token","http://api.t.sina.com.cn/oauth/authorize");  
  24.             String authUrl = httpOauthprovider.retrieveRequestToken(httpOauthConsumer, callBackUrl);  
  25.             activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)));  
  26.             ret=true;  
  27.         }catch(Exception e){  
  28.         }  
  29.         return ret;  
  30.     }  
  31.       
  32.     public UserInfo GetAccessToken(Intent intent){  
  33.         UserInfo user=null;  
  34.         Uri uri = intent.getData();  
  35.         String verifier = uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER);  
  36.         try {  
  37.             httpOauthprovider.setOAuth10a(true);   
  38.             httpOauthprovider.retrieveAccessToken(httpOauthConsumer,verifier);  
  39.         } catch (OAuthMessageSignerException ex) {  
  40.             ex.printStackTrace();  
  41.         } catch (OAuthNotAuthorizedException ex) {  
  42.             ex.printStackTrace();  
  43.         } catch (OAuthExpectationFailedException ex) {  
  44.             ex.printStackTrace();  
  45.         } catch (OAuthCommunicationException ex) {  
  46.             ex.printStackTrace();  
  47.         }  
  48.         SortedSet<String> user_id= httpOauthprovider.getResponseParameters().get("user_id");  
  49.         String userId=user_id.first();  
  50.         String userKey = httpOauthConsumer.getToken();  
  51.         String userSecret = httpOauthConsumer.getTokenSecret();  
  52.         user=new UserInfo();  
  53.         user.setUserId(userId);  
  54.         user.setToken(userKey);  
  55.         user.setTokenSecret(userSecret);  
  56.         return user;  
  57.     }  
  58.       
  59.     public HttpResponse SignRequest(String token,String tokenSecret,String url,List params)  
  60.     {  
  61.         HttpPost post = new HttpPost(url);  
  62.         //HttpClient httpClient = null;  
  63.         try{  
  64.             post.setEntity(new UrlEncodedFormEntity(params,HTTP.UTF_8));  
  65.         } catch (UnsupportedEncodingException e) {  
  66.              e.printStackTrace();  
  67.         }  
  68.         //關閉Expect:100-Continue握手  
  69.         //100-Continue握手需謹慎使用,因爲遇到不支持HTTP/1.1協議的服務器或者代理時會引起問題  
  70.         post.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);  
  71.         return SignRequest(token,tokenSecret,post);  
  72.     }  
  73.       
  74.     public HttpResponse SignRequest(String token,String tokenSecret,HttpPost post){  
  75.         httpOauthConsumer = new CommonsHttpOAuthConsumer(consumerKey,consumerSecret);  
  76.         httpOauthConsumer.setTokenWithSecret(token,tokenSecret);  
  77.         HttpResponse response = null;  
  78.         try {  
  79.             httpOauthConsumer.sign(post);  
  80.         } catch (OAuthMessageSignerException e) {  
  81.             e.printStackTrace();  
  82.         } catch (OAuthExpectationFailedException e) {  
  83.             e.printStackTrace();  
  84.         } catch (OAuthCommunicationException e) {  
  85.             e.printStackTrace();  
  86.         }  
  87.         //取得HTTP response  
  88.         try {  
  89.             response = new DefaultHttpClient().execute(post);  
  90.         } catch (ClientProtocolException e) {  
  91.             e.printStackTrace();  
  92.         } catch (IOException e) {  
  93.             e.printStackTrace();  
  94.         }  
  95.         return response;  
  96.     }  
  97. }  
2、認證界面

        根據上文的介紹,我們知道,需要獲得用戶的授權纔可以獲得對該用戶新浪微博的操作權限,在項目中對此是這樣處理的。其界面如下:

        用戶進入應用以後,會轉到是否對新浪微博進行授權界面,點擊“開始”後啓動瀏覽器進入新浪微博授權界面,該界面是新浪製作的,與本應用無關。用戶輸入賬號和密碼以後,點擊“授權”,進行授權操作。該界面源碼如下:

[java] view plaincopy
  1. public class AuthorizeActivity extends Activity {  
  2.     private Dialog dialog;  
  3.     private OAuth auth;  
  4.     private static final String CallBackUrl = "myapp://AuthorizeActivity";  
  5.       
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.authorize);  
  10.           
  11.           
  12.         View diaView=View.inflate(this, R.layout.dialog, null);  
  13.         dialog = new Dialog(AuthorizeActivity.this,R.style.dialog);  
  14.         dialog.setContentView(diaView);  
  15.         dialog.show();  
  16.           
  17.         ImageButton stratBtn=(ImageButton)diaView.findViewById(R.id.btn_start);  
  18.         stratBtn.setOnClickListener(new OnClickListener(){  
  19.             public void onClick(View arg0) {  
  20.                 auth=new OAuth("30632531","f539cb169860ed99cf8c1861c5da34f6");  
  21.                 auth.RequestAccessToken(AuthorizeActivity.this, CallBackUrl);  
  22.             }  
  23.               
  24.         });  
  25.   
  26.     }  
  27.       
  28.     @Override  
  29.     protected void onNewIntent(Intent intent) {  
  30.             super.onNewIntent(intent);  
  31.             //在這裏處理獲取返回的oauth_verifier參數  
  32.             UserInfo user= auth.GetAccessToken(intent);  
  33.             if(user!=null){  
  34.                         DataHelper helper=new DataHelper(this);  
  35.                         String uid=user.getUserId();  
  36.                         if(helper.HaveUserInfo(uid))  
  37.                         {  
  38.                             helper.UpdateUserInfo(user);  
  39.                             Log.e("UserInfo""update");  
  40.                         }else  
  41.                         {  
  42.                             helper.SaveUserInfo(user);  
  43.                             Log.e("UserInfo""add");  
  44.                         }  
  45.                    }  
  46.   
  47.     }  
  48.   
  49. }  
        從該源碼中我們可以發現,在AuthorizeActivity界面,用戶點擊“授權”以後,又重新進入AuthorizeActivity界面(這一步與AndroidManifest.xml文件中AuthorizeActivity項的配置有關),此時會調用onNewIntent接口。

       這裏有個地方需要注意,授權成功後重新進入AuthorizeActivity界面,執行onNewIntent函數後並不會自動跳轉到其他界面。這裏需要添加新的跳轉代碼。

3、onNewIntent調用時機

        這一部分介紹一下onNewIntent的調用時機。

在IntentActivity中重寫下列方法:onCreate onStart onRestart  onResume  onPause onStop onDestroy  onNewIntent
3.1、其他應用發Intent,執行下列方法:
I/@@@philn(12410): onCreate
I/@@@philn(12410): onStart
I/@@@philn(12410): onResume
發Intent的方法:
Uri uri = Uri.parse("philn://blog.163.com");
Intent it = new Intent(Intent.ACTION_VIEW, uri);    
startActivity(it);
3.2、接收Intent聲明:

[html] view plaincopy
  1. <activity android:name="cn.com.hh.view.AuthorizeActivity" android:launchMode="singleTask">  
  2.         <intent-filter>  
  3.             <action android:name="android.intent.action.VIEW" />  
  4.             <category android:name="android.intent.category.DEFAULT" />  
  5.             <category android:name="android.intent.category.BROWSABLE" />  
  6.             <data android:scheme="myapp" android:host="AuthorizeActivity" />  
  7.         </intent-filter>  
  8.        </activity>  
3.3、如果IntentActivity處於任務棧的頂端(AndroidManifest.xml配置中的android:launchMode="singleTask" 項),也就是說之前打開過的Activity,現在處於
I/@@@philn(12410): onPause
I/@@@philn(12410): onStop 狀態的話
其他應用再發送Intent的話,執行順序爲:
I/@@@philn(12410): onNewIntent
I/@@@philn(12410): onRestart
I/@@@philn(12410): onStart
I/@@@philn(12410): onResume
        在Android應用程序開發的時候,從一個Activity啓動另一個Activity並傳遞一些數據到新的Activity上非常簡單,但是當您需要讓後臺運行的Activity回到前臺並傳遞一些數據可能就會存在一點點小問題。
        首先,在默認情況下,當您通過Intent啓到一個Activity的時候,就算已經存在一個相同的正在運行的Activity,系統都會創建一個新的Activity實例並顯示出來。爲了不讓Activity實例化多次,我們需要通過在AndroidManifest.xml配置activity的加載方式(launchMode)以實現單任務模式,如下所示:

[java] view plaincopy
  1. <activity android:label="@string/app_name" android:launchmode="singleTask"android:name="Activity1">  
  2. </activity>  
       launchMode爲singleTask的時候,通過Intent啓到一個Activity,如果系統已經存在一個實例,系統就會將請求發送到這個實例上,但這個時候,系統就不會再調用通常情況下我們處理請求數據的onCreate方法,而是調用onNewIntent方法,如下所示:
[java] view plaincopy
  1. protected void onNewIntent(Intent intent) {  
  2.    super.onNewIntent(intent);  
  3.    setIntent(intent);//must store the new intent unless getIntent() will return the old one  
  4.    processExtraData();  
  5.   
  6.  }  
       不要忘記,系統可能會隨時殺掉後臺運行的Activity,如果這一切發生,那麼系統就會調用onCreate方法,而不調用onNewIntent方法,一個好的解決方法就是在onCreate和onNewIntent方法中調用同一個處理數據的方法,如下所示:

[java] view plaincopy
  1. public void onCreate(Bundle savedInstanceState) {  
  2.   super.onCreate(savedInstanceState);  
  3.   setContentView(R.layout.main);  
  4.   processExtraData();  
  5. }  
  6.    
  7. protected void onNewIntent(Intent intent) {  
  8.   super.onNewIntent(intent);  
  9.   setIntent(intent);//must store the new intent unless getIntent() will return the old one  
  10.  processExtraData()  
  11.   
  12. }  
  13.   
  14. private void processExtraData(){  
  15.   Intent intent = getIntent();  
  16.   //use the data received here  
  17. }  

4、獲得微博數據

        獲得用戶認證後,就可以獲得第四組參數,就可以依次獲得用戶數據了。

       在項目中這一步是在HomeActivity界面中完成的,其中獲得微博數據部分函數的源碼如下:

[java] view plaincopy
  1. private void loadList(){  
  2.       if(ConfigHelper.nowUser==null)  
  3.       {  
  4.             
  5.       }  
  6.       else  
  7.       {  
  8.           user=ConfigHelper.nowUser;  
  9.           //顯示當前用戶名稱  
  10.           TextView showName=(TextView)findViewById(R.id.showName);  
  11.           showName.setText(user.getUserName());  
  12.             
  13.           OAuth auth=new OAuth();  
  14.           String url = "http://api.t.sina.com.cn/statuses/friends_timeline.json";  
  15. //          String url = "http://api.t.sina.com.cn/statuses/public_timeline.json";  
  16.           List<BasicNameValuePair> params=new ArrayList<BasicNameValuePair>();  
  17.           params.add(new BasicNameValuePair("source", auth.consumerKey));   
  18.           HttpResponse response = auth.SignRequest(user.getToken(), user.getTokenSecret(), url, params);  
  19.           if (200 == response.getStatusLine().getStatusCode()){  
  20.               try {  
  21.                   InputStream is = response.getEntity().getContent();  
  22.                   Reader reader = new BufferedReader(new InputStreamReader(is), 4000);  
  23.                   StringBuilder buffer = new StringBuilder((int) response.getEntity().getContentLength());  
  24.                   try {  
  25.                       char[] tmp = new char[1024];  
  26.                       int l;  
  27.                       while ((l = reader.read(tmp)) != -1) {  
  28.                           buffer.append(tmp, 0, l);  
  29.                       }  
  30.                   } finally {  
  31.                       reader.close();  
  32.                   }  
  33.                   String string = buffer.toString();  
  34.                   //Log.e("json", "rs:" + string);  
  35.                   ((org.apache.http.HttpResponse) response).getEntity().consumeContent();  
  36.                   JSONArray data=new JSONArray(string);  
  37.                   for(int i=0;i<data.length();i++)  
  38.                   {  
  39.                       JSONObject d=data.getJSONObject(i);  
  40.                       //Log.e("json", "rs:" + d.getString("created_at"));  
  41.                       if(d!=null){  
  42.                           JSONObject u=d.getJSONObject("user");  
  43.                           if(d.has("retweeted_status")){  
  44.                               JSONObject r=d.getJSONObject("retweeted_status");  
  45.                           }  
  46.                             
  47.                           //微博id  
  48.                           String id=d.getString("id");  
  49.                           String userId=u.getString("id");  
  50.                           String userName=u.getString("screen_name");  
  51.                           String userIcon=u.getString("profile_image_url");  
  52. //                          Log.e("userIcon", userIcon);  
  53.                           String time=d.getString("created_at");  
  54.                           String text=d.getString("text");  
  55.                           Boolean haveImg=false;  
  56.                           if(d.has("thumbnail_pic")){  
  57.                               haveImg=true;  
  58.                               //String thumbnail_pic=d.getString("thumbnail_pic");  
  59.                               //Log.e("thumbnail_pic", thumbnail_pic);  
  60.                           }  
  61.                             
  62.                           Date startDate=new Date(time);  
  63.                           Date nowDate = Calendar.getInstance().getTime();  
  64.                           time=new DateUtilsDef().twoDateDistance(startDate,nowDate);  
  65.                           if(wbList==null){  
  66.                               wbList=new ArrayList<WeiBoInfo>();  
  67.                           }  
  68.                           WeiBoInfo w=new WeiBoInfo();  
  69.                           w.setId(id);  
  70.                           w.setUserId(userId);  
  71.                           w.setUserName(userName);  
  72.                           w.setTime(time +" 前");  
  73.                           w.setText(text);  
  74.                             
  75.                           w.setHaveImage(haveImg);  
  76.                           w.setUserIcon(userIcon);  
  77.                           wbList.add(w);  
  78.                       }  
  79.                   }  
  80.                     
  81.               }catch (IllegalStateException e) {  
  82.                   e.printStackTrace();  
  83.               } catch (IOException e) {  
  84.                   e.printStackTrace();  
  85.               } catch (JSONException e) {  
  86.                   e.printStackTrace();  
  87.               }   
  88.           }  
  89.             
  90.           if(wbList!=null)  
  91.           {  
  92.               WeiBoAdapater adapater = new WeiBoAdapater();  
  93.               ListView Msglist=(ListView)findViewById(R.id.Msglist);  
  94.               Msglist.setOnItemClickListener(new OnItemClickListener(){  
  95.                    
  96.                   public void onItemClick(AdapterView<?> arg0, View view,int arg2, long arg3) {  
  97.                       Object obj=view.getTag();  
  98.                       if(obj!=null){  
  99.                           String id=obj.toString();  
  100.                           Intent intent = new Intent(HomeActivity.this,ViewActivity.class);  
  101.                           Bundle b=new Bundle();  
  102.                           b.putString("key", id);  
  103.                           intent.putExtras(b);  
  104.                           startActivity(intent);  
  105.                       }  
  106.                   }  
  107.                     
  108.               });  
  109.               Msglist.setAdapter(adapater);  
  110.           }  
  111.       }  
  112.       loadingLayout.setVisibility(View.GONE);  
  113.   }  
        其中根據新浪提供的API對用戶數據進行操作。這裏只是獲得用戶所發佈的微博信息,其api爲http://api.t.sina.com.cn/statuses/friends_timeline.json。這裏同時需要提供第四組參數,params.add(new BasicNameValuePair("source", auth.consumerKey));。其返回的數據格式是json需要對其進行解析。對於json格式的解析可以參看本人博文《android基礎知識11:json解析及簡單例子》。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章