基于人人网的Android开发流程介绍

 人人网(www.renren.com)前身为校内网,是中国最大、最受用户欢迎的网络SNS平台。人人网目前针对不同领域的开发者,提供了相应的教程和文档,目前主要分为“站内应用开发”、“第三方网站接入”、“移动客户端接入”和“桌面客户端接入”四大模块。以下结合自己实际做过的一个项目(人人好友电话簿)介绍基于Android开发的移动客户端接入基本流程。

手机等移动设备的客户端应用(如手机游戏、实用工具等)接入人人网,可以使用人人网帐号登录移动客户端,并利用人人网开放平台提供的社交图谱(Social Graph)和传播渠道,增进用户与好友的交互,提升使用体验,并获得广泛传播。

 

Android手机客户端接入人人网,可以有两种实现方式:


第一种是直接使用人人网开放平台提供的各种接口,如用作验证和授权的OAuth 2.0,提供数据的底层Rest API,以及嵌入各种Widget。关于OAuth 2.0详细协议介绍,请参考https://datatracker.ietf.org/doc/draft-ietf-oauth-v2/,后文会介绍在人人中的相关用法。


第二种是使用人人网开放平台官方封装的开源Android SDK。 人人网最新版的Android SDK实际上是将OAuth 2.0、Rest API等平台提供的底层接口封装起来而已。

 

在我的项目中我采用了第一种方式,即直接使用人人网开放平台提供的各种接口,这种方式比较简单,自己可以随心所欲的发挥。第二种方式,我看过人人提供的SDK,比较晦涩难懂。如有兴趣,可以自己去研究。

 

整体流程如下图:



第一步:注册一个人人账户,如果有了,跳过此步。

第二步:用自己的账号密码登陆到人人网,然后跳转到http://dev.renren.com/app,点击创建客户端应用按钮,并按相应的流程填写完相应信息,你就可以提交应用审核。完成后你将得到:API Key和Secret Key,如下图:


 

API Key就是人人OAuth2.0中的“client_id”,Secret Key就是“client_secret”。

基于人人网的Android开发流程介绍_第1张图片


第三、四步:


人人网开放平台提供了上述的OAuth2.0验证与授权流程以支持不同类型的应用,包括:网站、站内应用、手机客户端和桌面客户端。

由于OAuth 2.0协议定义的桌面客户端的授权流程,用户体验方面过于复杂,所以桌面客户端应用可以在应用中嵌入浏览器控件(很多框架都支持嵌入浏览器,例如:.NET、AIR、Cocoa等)使用客户端流程。

由于大部分桌面客户端软件是没有后端没有Web服务器支持,没办法提供一个'redirect_uri',所以人人网为没有Web服务器的客户端应用提供了一个通用的URL:http://graph.renren.com/oauth/login_success.html。

流程如下:

在应用中嵌入一个浏览器控件,并使用客户端流程定向控件到人人OAuth 2.0 Authorize Endpoint(https://graph.renren.com/oauth/authorize):

https://graph.renren.com/oauth/authorize?client_id=YOUR_API_KEY&redirect_uri=http://graph.renren.com/oauth/login_success.html

经过用户验证、应用授权,人人OAuth2.0将把浏览器控件定向导'redirect_uri'(http://graph.renren.com/oauth/login_success.html),并在URI Fragment中追加Access Token:http://graph.renren.com/oauth/login_success.html#access_token=...当应用发现浏览器的控件的URL跳转到这个URL上时,从URL中解析出Access Token。

 

在Android中加载html用WebView控件,由于Android主要是触屏,因此需要加display=touch参数,完整的URL为

https://graph.renren.com/oauth/authorize?client_id=05d3794614f244c39e300c65f5f68a9e&response_type=token&display=touch&redirect_uri=http://graph.renren.com/oauth/login_success.html

 

登陆授权流程如下:


基于人人网的Android开发流程介绍_第2张图片



主要代码和注释如下:

RenrenLoginActivity.java

[java]  view plain copy
  1. <span style="font-size:16px;">import android.app.Activity;  
  2. ……  
  3. /** 
  4.  * @author hexiaoling 
  5.  */  
  6. public class RenrenLoginActivity extends Activity {  
  7.     public final static String TAG = "RenrenLoginActivity";  
  8.     private WebView renrenLoginWebView; // WebView 控件,用于显示从人人网请求得到html授权页面  
  9.       
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.renren_login_web_view);  
  14.         renrenLoginWebView = (WebView) findViewById(R.id.renren_login_web_view); // 得到 WebView 控件  
  15.           
  16.         //对WebView进行设置(对JS的支持,对缩放的支持,对缓存模式的支持)  
  17.         WebSettings webSettings = renrenLoginWebView.getSettings();  
  18.         webSettings.setJavaScriptEnabled(true);   
  19.         webSettings.setBuiltInZoomControls(true);  
  20.         webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);  
  21.           
  22.         // 根据client_id取得到人人服务器人人对你的应用授权,如果成功则返回人人网登陆页面的html文件,并在WebView控件上显示  
  23.         // 此时用户需要输入自己人人账号的用户名、密码并点击登陆  
  24.         renrenLoginWebView.loadUrl("https://graph.renren.com/oauth/authorize?"+  
  25.                 "client_id=05d3794614f244c39e300c65f5f68a9e&response_type=token"+  
  26.                 "&display=touch&redirect_uri=http://graph.renren.com/oauth/login_success.html");  
  27.           
  28.         renrenLoginWebView.setWebViewClient(new WebViewClient() {  
  29.   
  30.             //击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边  
  31.             @Override  
  32.             public boolean shouldOverrideUrlLoading(WebView view, String url) {  
  33.                 view.loadUrl(url);  
  34.                 return true;  
  35.             }  
  36.   
  37.             @Override  
  38.             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {  
  39.                 handler.proceed();//让webview处理https请求  
  40.             }  
  41.               
  42.             @Override  
  43.             public void onPageFinished(WebView view, String url) {  
  44.                 String url0 = renrenLoginWebView.getUrl();  
  45.                 String access_token = "";  
  46.                 String expires_in = "";  
  47.                 Log.i(TAG, "URL = " + url0);  
  48.                 if(url0 != null) {  
  49.                     if(url0.contains("access_token=")) { // 从URL中解析得到 access_token  
  50.                         access_token = url0.substring(url0.indexOf("access_token=") + 13, url0.length() - 19);  
  51.                         try {  
  52.                             access_token = URLDecoder.decode(access_token, "utf-8"); // 制定为utf-8编码  
  53.                         } catch (UnsupportedEncodingException e) {  
  54.                             e.printStackTrace();  
  55.                         }  
  56.                         Log.i(TAG, "access_token = " + access_token);  
  57.                     }  
  58.                     if(url0.contains("expires_in=")) { // 从URL中解析得到 expires_in  
  59.                         expires_in = url0.substring(url0.indexOf("expires_in=") + 11, url0.length());  
  60.                         Log.i(TAG, "expires_in = " + expires_in);  
  61.                     }  
  62.                     RenrenUtil.access_token = access_token; // 将解析得到的 access_token 保存起来  
  63.                     RenrenUtil.expires_in = expires_in; // 将解析得到的 expires_in 保存起来  
  64.                       
  65.                     //输入用户名、密码登陆成功,进行页面跳转  
  66.                     if(RenrenUtil.access_token.length() != 0) {  
  67.                         Intent intent = new Intent(RenrenLoginActivity.this, RenrenFriendsActivity.class);  
  68.                         startActivity(intent);  
  69.                     }  
  70.                 }  
  71.                 super.onPageFinished(view, url);  
  72.             }             
  73.         });  
  74.     }  
  75. }  
  76. </span>  


下图表示加载人人授权应用的html登陆成果界面:


基于人人网的Android开发流程介绍_第3张图片


第五步:这里以访问我的人人好友列表为例,包括取得好友的名称和照片。主要流程如下:



基于人人网的Android开发流程介绍_第4张图片



①人人API利用不同功能的请求函数和相关参数,详细请参考http://wiki.dev.renren.com/wiki/API。此处以获得好友列表为例,其对应函数为friends.getFriends(得到用户的信息,支持批量获取),人人对friends.getFriends的描述和参数列表如下图:


基于人人网的Android开发流程介绍_第5张图片


下图表示将请求参数和Secret得到MD5值为签名,并组装请求参数核心代码:


[java]  view plain copy
  1. <span style="font-size:16px;">String method = " friends.getFriends "// 人人API中定义的方法,用于得到当前登录用户的好友列表。  
  2.             int count = 30// 得到用户数30个  
  3.               
  4.             List<String> param = new ArrayList<String>();  
  5.             param.add("method=" + method);  
  6.             param.add("v=1.0"); // 版本固定参数  
  7.             param.add("access_token=" + RenrenUtil.access_token); //RenrenUtil.access_token 是在LoginActivity中已经保存的数据  
  8.             param.add("format=JSON"); // 返回JSON数据  
  9.             param.add("count=" + count); //得到用户数  
  10.               
  11.             String signature = getSignature(param, "55f436828286417ab9db7ea17ee9cbde"); //第二个参数为 Secret Key  
  12.               
  13.             List<BasicNameValuePair> paramList = new ArrayList<BasicNameValuePair>();         
  14.             paramList.add(new BasicNameValuePair("sig", signature)); // 签名  
  15.             paramList.add(new BasicNameValuePair("method", method));  
  16.             paramList.add(new BasicNameValuePair("v""1.0"));  
  17.             paramList.add(new BasicNameValuePair("access_token", RenrenUtil.access_token));  
  18.             paramList.add(new BasicNameValuePair("format""JSON"));  
  19.             paramList.add(new BasicNameValuePair("count""" + count));  
  20. </span>  


下图表示将请求参数和SecretKey加密得到它们的MD5值的算法如下:


[java]  view plain copy
  1. <span style="font-size:16px;">/** 
  2.      * 得到MD5签名 
  3.      * @param paramList 
  4.      * @param secret 
  5.      * @return 
  6.      */  
  7.     public String getSignature(List<String> paramList,String secret) {  
  8.          Collections.sort(paramList);  
  9.          StringBuffer buffer = new StringBuffer();  
  10.          for (String param : paramList) {  
  11.              buffer.append(param);  //将参数键值对,以字典序升序排列后,拼接在一起  
  12.          }  
  13.          buffer.append(secret);  //符串末尾追加上应用的Secret Key  
  14.          try {//下面是将拼好的字符串转成MD5值,然后返回  
  15.             java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");  
  16.             StringBuffer result = new StringBuffer();  
  17.             try {  
  18.                 for (byte b : md.digest(buffer.toString().getBytes("utf-8"))) {  
  19.                     result.append(Integer.toHexString((b & 0xf0) >>> 4));  
  20.                     result.append(Integer.toHexString(b & 0x0f));  
  21.                 }  
  22.             } catch (UnsupportedEncodingException e) {  
  23.                 for (byte b : md.digest(buffer.toString().getBytes())) {  
  24.                     result.append(Integer.toHexString((b & 0xf0) >>> 4));  
  25.                     result.append(Integer.toHexString(b & 0x0f));  
  26.                 }  
  27.             }  
  28.             return result.toString();  
  29.         } catch (java.security.NoSuchAlgorithmException ex) { }  
  30.         return null;  
  31.    }  
  32. </span>  


②使用HTTP协议,Android已经对其做了很好的封装,使用起来很方便。使用将第①步得到的参数列表想http://api.renren.com/restserver.do发送Http请求,同时得到HttpResponse响应,判断响应代码是否为200,为200表示成功。然后解析JSON数据为相关对象,核心代码如下:


[java]  view plain copy
  1. <span style="font-size:16px;">String returnValue = "0";  
  2.             try {  
  3.                 HttpPost httpPost = new HttpPost("http://api.renren.com/restserver.do");                  
  4.                 httpPost.setEntity(new UrlEncodedFormEntity(paramList, HTTP.UTF_8));// 添加请求参数到请求对象  
  5.                   
  6.                 HttpResponse httpResponse = httpClient.execute(httpPost);  
  7.                 if(httpResponse.getStatusLine().getStatusCode() == 200) { //为200表示执行成功  
  8.                     strResult = EntityUtils.toString(httpResponse.getEntity()); //得到返回数据(为JSON数据)  
  9.                     if(! strResult.contains("error_code")) {  
  10.                         renrenList = Renren.parseRenrenFromJson(strResult); //解析JSON数据为相应对象  
  11.                         returnValue = "1"//定义返回标志  
  12.                     }  
  13.                 }  
  14.             } catch (ClientProtocolException e) {  
  15.                 strResult = e.getMessage().toString();  
  16.                 e.printStackTrace();  
  17.             } catch (IOException e) {  
  18.                 strResult = e.getMessage().toString();  
  19.                 e.printStackTrace();  
  20.             } catch (Exception e) {  
  21.                 strResult = e.getMessage().toString();  
  22.                 e.printStackTrace();  
  23.             }  
  24.             return returnValue;  
  25. </span>  

网络请求会比较耗时,因此建议使用多线程来处理。在Android中可以使用继承AsyncTask<String, Integer,String>类来实现多线程处理。

 

③解析JSON数据为人人对象方法:

[java]  view plain copy
  1. <span style="font-size:16px;">/** 
  2.      * 解析JSON数据为人人对象 
  3.      * @param renrenJsonData 
  4.      * @return 
  5.      */  
  6.     public List<Renren> parseRenrenFromJson(String renrenJsonData) {  
  7.         List<Renren> renrenList = new ArrayList<Renren>();  
  8.         try {  
  9.             JSONArray jsonArray = new JSONArray(renrenJsonData);  
  10.             int length = jsonArray.length();  
  11.             for (int i = 0; i < length; ++i) {  
  12.                 JSONObject jsonObject = jsonArray.getJSONObject(i);  
  13.                 Renren renren = new Renren();  
  14.                 renren.setId(jsonObject.getString("id"));  
  15.                 renren.setName(jsonObject.getString("name"));  
  16.                 renren.setHeadurl(jsonObject.getString("headurl"));  
  17.                 renrenList.add(renren);  
  18.             }  
  19.             return renrenList;  
  20.         } catch (JSONException e) { }  
  21.         return null;  
  22.     }  
  23. </span>  


下图为JSON数据的数据结构:


基于人人网的Android开发流程介绍_第6张图片


人人类Renren.java


[java]  view plain copy
  1. <span style="font-size:16px;">public class Renren {  
  2.     protected String id;  
  3.     protected String name;  
  4.     protected String headurl;  
  5. 省略get、set方法。  
  6. }  
  7. </span>  

④在List中显示人人好友列表(图片、名称),并用Adapater来加载数据。由于请求friends.getFriends返回的只有图片的URL,因此图片还需要单独去请求,为了保障用户体验,图片采用异步加载。

[java]  view plain copy
  1. <span style="font-size:16px;">public class FriendsAdapater extends BaseAdapter {  
  2.         private AsyncImageLoader asyncImageLoader = new AsyncImageLoader();  // 异步获取图片  
  3.           
  4.         @Override  
  5.         public int getCount() {  
  6.             return renrenList == null ? 0 : renrenList.size();  
  7.         }  
  8.   
  9.         @Override  
  10.         public Object getItem(int position) {  
  11.             return  renrenList == null ? null :renrenList.get(position);  
  12.         }  
  13.   
  14.         @Override  
  15.         public long getItemId(int position) {  
  16.             return position;  
  17.         }  
  18.   
  19.         @Override  
  20.         public View getView(int position, View convertView, ViewGroup parent) {  
  21.               
  22.             convertView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.friend, null);  
  23.             userName= (TextView) convertView.findViewById(R.id.username);  
  24.             userImage = (ImageView) convertView.findViewById(R.id.userimage);  
  25.               
  26.             Renren renren = renrenList.get(position);  
  27.             if (renren != null) {  
  28.                 convertView.setTag(renren.getId());  
  29.                 userImage.setTag(renren.getHeadurl());  
  30.                 userName.setText(renren.getName());  
  31.                   
  32.                 //异步加载图片并显示  
  33.                 Drawable cachedImage = asyncImageLoader.loadDrawable(renren, new ImageCallback() {  
  34.                             @Override  
  35.                             public void imageLoaded(Drawable imageDrawable, String imageUrl) {  
  36.                                 ImageView imageView = (ImageView) friendsList.findViewWithTag(imageUrl);  
  37.                                 if(imageView != null) {  
  38.                                     imageView.setImageDrawable(imageDrawable);  
  39.                                 }  
  40.                             }  
  41.                         });  
  42.                   
  43.                 if (cachedImage != null) {  
  44.                     userImage.setImageDrawable(cachedImage);  
  45.                 } else {//如果没有图片,就以一个载入的图片代替显示  
  46.                     userImage.setImageResource(R.drawable.icon);  
  47.                 }  
  48.             }  
  49.             return convertView;  
  50.         }  
  51.     }  
  52. </span>  



异步加载图片类AsyncImageLoader.java


[java]  view plain copy
  1. <span style="font-size:16px;">/** 
  2.  * 异步加载图片类 
  3.  * @author hexiaoling 
  4.  */  
  5. public class AsyncImageLoader {  
  6.   
  7.      private HashMap<String, SoftReference<Drawable>> imageCache; //缓存图片  
  8.        
  9.      public AsyncImageLoader() {  
  10.          imageCache = new HashMap<String, SoftReference<Drawable>>();  
  11.      }  
  12.     
  13.      public Drawable loadDrawable(final Renren renren, final ImageCallback imageCallback) {  
  14.          final String imageId = renren.getId();  
  15.          final String imageURL = renren.getHeadurl();  
  16.            
  17.          //判断缓存中是否已经存在图片,如果存在则直接返回  
  18.          if (imageCache.containsKey(imageId)) {  
  19.              SoftReference<Drawable> softReference = imageCache.get(imageId);  
  20.              Drawable drawable = softReference.get();  
  21.              if (drawable != null) {  
  22.                  return drawable;  
  23.              }  
  24.          }  
  25.            
  26.          final Handler handler = new Handler() {  
  27.              public void handleMessage(Message message) {  
  28.                  imageCallback.imageLoaded((Drawable) message.obj, imageURL);  
  29.              }  
  30.          };  
  31.            
  32.          //开辟一个新线程去下载图片,并用Handler去更新UI  
  33.          new Thread() {  
  34.              @Override  
  35.              public void run() {  
  36.                  Drawable drawable = loadImageFromUrl(imageURL);  
  37.                  imageCache.put(imageURL, new SoftReference<Drawable>(drawable));  
  38.                  Message message = handler.obtainMessage(0, drawable);  
  39.                  handler.sendMessage(message);  
  40.              }  
  41.          }.start();  
  42.          return null;  
  43.      }  
  44.     
  45.      //从URL下载图片  
  46.     public static Drawable loadImageFromUrl(String url) {  
  47.         URL m;  
  48.         InputStream i = null;  
  49.         try {  
  50.             m = new URL(url);  
  51.             i = (InputStream) m.getContent();  
  52.         } catch (MalformedURLException e1) {  
  53.             e1.printStackTrace();  
  54.         } catch (IOException e) {  
  55.             e.printStackTrace();  
  56.         }  
  57.         Drawable d = Drawable.createFromStream(i, "src");  
  58.         return d;  
  59.     }  
  60.       
  61.     //回调接口  
  62.      public interface ImageCallback {  
  63.          //回调函数  
  64.          public void imageLoaded(Drawable imageDrawable, String imageUrl);  
  65.      }  
  66. }  
  67. </span>  


下图为运行结果:(因涉及好友真实信息,不将部分图片和文字加码,此结果仅表示按照此步骤运行正确)



基于人人网的Android开发流程介绍_第7张图片


另外需要注意的是,访问人人API需要访问网络,因此需要在添加网络访问授权

<uses-permissionandroid:name="android.permission.INTERNET" />

 

此文仅简单介绍了基于人人网API的Android开发的基本步骤,如需了解更多信息,请研究人人官网http://wiki.dev.renren.com/wiki/API。欢迎就上述问题进一步交流。

 

参考文献:http://wiki.dev.renren.com/wiki/API

你可能感兴趣的:(android,json,url,Access,token,开放平台)