人人网(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”。
第三、四步:
人人网开放平台提供了上述的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
登陆授权流程如下:
主要代码和注释如下:
RenrenLoginActivity.java
- <span style="font-size:16px;">import android.app.Activity;
- ……
-
-
-
- public class RenrenLoginActivity extends Activity {
- public final static String TAG = "RenrenLoginActivity";
- private WebView renrenLoginWebView;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.renren_login_web_view);
- renrenLoginWebView = (WebView) findViewById(R.id.renren_login_web_view);
-
-
- WebSettings webSettings = renrenLoginWebView.getSettings();
- webSettings.setJavaScriptEnabled(true);
- webSettings.setBuiltInZoomControls(true);
- webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
-
-
-
- renrenLoginWebView.loadUrl("https://graph.renren.com/oauth/authorize?"+
- "client_id=05d3794614f244c39e300c65f5f68a9e&response_type=token"+
- "&display=touch&redirect_uri=http://graph.renren.com/oauth/login_success.html");
-
- renrenLoginWebView.setWebViewClient(new WebViewClient() {
-
-
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- view.loadUrl(url);
- return true;
- }
-
- @Override
- public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
- handler.proceed();
- }
-
- @Override
- public void onPageFinished(WebView view, String url) {
- String url0 = renrenLoginWebView.getUrl();
- String access_token = "";
- String expires_in = "";
- Log.i(TAG, "URL = " + url0);
- if(url0 != null) {
- if(url0.contains("access_token=")) {
- access_token = url0.substring(url0.indexOf("access_token=") + 13, url0.length() - 19);
- try {
- access_token = URLDecoder.decode(access_token, "utf-8");
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- Log.i(TAG, "access_token = " + access_token);
- }
- if(url0.contains("expires_in=")) {
- expires_in = url0.substring(url0.indexOf("expires_in=") + 11, url0.length());
- Log.i(TAG, "expires_in = " + expires_in);
- }
- RenrenUtil.access_token = access_token;
- RenrenUtil.expires_in = expires_in;
-
-
- if(RenrenUtil.access_token.length() != 0) {
- Intent intent = new Intent(RenrenLoginActivity.this, RenrenFriendsActivity.class);
- startActivity(intent);
- }
- }
- super.onPageFinished(view, url);
- }
- });
- }
- }
- </span>
下图表示加载人人授权应用的html登陆成果界面:
第五步:这里以访问我的人人好友列表为例,包括取得好友的名称和照片。主要流程如下:
①人人API利用不同功能的请求函数和相关参数,详细请参考http://wiki.dev.renren.com/wiki/API。此处以获得好友列表为例,其对应函数为friends.getFriends(得到用户的信息,支持批量获取),人人对friends.getFriends的描述和参数列表如下图:
下图表示将请求参数和Secret得到MD5值为签名,并组装请求参数核心代码:
- <span style="font-size:16px;">String method = " friends.getFriends ";
- int count = 30;
-
- List<String> param = new ArrayList<String>();
- param.add("method=" + method);
- param.add("v=1.0");
- param.add("access_token=" + RenrenUtil.access_token);
- param.add("format=JSON");
- param.add("count=" + count);
-
- String signature = getSignature(param, "55f436828286417ab9db7ea17ee9cbde");
-
- List<BasicNameValuePair> paramList = new ArrayList<BasicNameValuePair>();
- paramList.add(new BasicNameValuePair("sig", signature));
- paramList.add(new BasicNameValuePair("method", method));
- paramList.add(new BasicNameValuePair("v", "1.0"));
- paramList.add(new BasicNameValuePair("access_token", RenrenUtil.access_token));
- paramList.add(new BasicNameValuePair("format", "JSON"));
- paramList.add(new BasicNameValuePair("count", "" + count));
- </span>
下图表示将请求参数和SecretKey加密得到它们的MD5值的算法如下:
- <span style="font-size:16px;">
-
-
-
-
-
- public String getSignature(List<String> paramList,String secret) {
- Collections.sort(paramList);
- StringBuffer buffer = new StringBuffer();
- for (String param : paramList) {
- buffer.append(param);
- }
- buffer.append(secret);
- try {
- java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
- StringBuffer result = new StringBuffer();
- try {
- for (byte b : md.digest(buffer.toString().getBytes("utf-8"))) {
- result.append(Integer.toHexString((b & 0xf0) >>> 4));
- result.append(Integer.toHexString(b & 0x0f));
- }
- } catch (UnsupportedEncodingException e) {
- for (byte b : md.digest(buffer.toString().getBytes())) {
- result.append(Integer.toHexString((b & 0xf0) >>> 4));
- result.append(Integer.toHexString(b & 0x0f));
- }
- }
- return result.toString();
- } catch (java.security.NoSuchAlgorithmException ex) { }
- return null;
- }
- </span>
②使用HTTP协议,Android已经对其做了很好的封装,使用起来很方便。使用将第①步得到的参数列表想http://api.renren.com/restserver.do发送Http请求,同时得到HttpResponse响应,判断响应代码是否为200,为200表示成功。然后解析JSON数据为相关对象,核心代码如下:
- <span style="font-size:16px;">String returnValue = "0";
- try {
- HttpPost httpPost = new HttpPost("http://api.renren.com/restserver.do");
- httpPost.setEntity(new UrlEncodedFormEntity(paramList, HTTP.UTF_8));
-
- HttpResponse httpResponse = httpClient.execute(httpPost);
- if(httpResponse.getStatusLine().getStatusCode() == 200) {
- strResult = EntityUtils.toString(httpResponse.getEntity());
- if(! strResult.contains("error_code")) {
- renrenList = Renren.parseRenrenFromJson(strResult);
- returnValue = "1";
- }
- }
- } catch (ClientProtocolException e) {
- strResult = e.getMessage().toString();
- e.printStackTrace();
- } catch (IOException e) {
- strResult = e.getMessage().toString();
- e.printStackTrace();
- } catch (Exception e) {
- strResult = e.getMessage().toString();
- e.printStackTrace();
- }
- return returnValue;
- </span>
网络请求会比较耗时,因此建议使用多线程来处理。在Android中可以使用继承AsyncTask<String, Integer,String>类来实现多线程处理。
③解析JSON数据为人人对象方法:
- <span style="font-size:16px;">
-
-
-
-
- public List<Renren> parseRenrenFromJson(String renrenJsonData) {
- List<Renren> renrenList = new ArrayList<Renren>();
- try {
- JSONArray jsonArray = new JSONArray(renrenJsonData);
- int length = jsonArray.length();
- for (int i = 0; i < length; ++i) {
- JSONObject jsonObject = jsonArray.getJSONObject(i);
- Renren renren = new Renren();
- renren.setId(jsonObject.getString("id"));
- renren.setName(jsonObject.getString("name"));
- renren.setHeadurl(jsonObject.getString("headurl"));
- renrenList.add(renren);
- }
- return renrenList;
- } catch (JSONException e) { }
- return null;
- }
- </span>
下图为JSON数据的数据结构:
人人类Renren.java
- <span style="font-size:16px;">public class Renren {
- protected String id;
- protected String name;
- protected String headurl;
- 省略get、set方法。
- }
- </span>
④在List中显示人人好友列表(图片、名称),并用Adapater来加载数据。由于请求friends.getFriends返回的只有图片的URL,因此图片还需要单独去请求,为了保障用户体验,图片采用异步加载。
- <span style="font-size:16px;">public class FriendsAdapater extends BaseAdapter {
- private AsyncImageLoader asyncImageLoader = new AsyncImageLoader();
-
- @Override
- public int getCount() {
- return renrenList == null ? 0 : renrenList.size();
- }
-
- @Override
- public Object getItem(int position) {
- return renrenList == null ? null :renrenList.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
-
- convertView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.friend, null);
- userName= (TextView) convertView.findViewById(R.id.username);
- userImage = (ImageView) convertView.findViewById(R.id.userimage);
-
- Renren renren = renrenList.get(position);
- if (renren != null) {
- convertView.setTag(renren.getId());
- userImage.setTag(renren.getHeadurl());
- userName.setText(renren.getName());
-
-
- Drawable cachedImage = asyncImageLoader.loadDrawable(renren, new ImageCallback() {
- @Override
- public void imageLoaded(Drawable imageDrawable, String imageUrl) {
- ImageView imageView = (ImageView) friendsList.findViewWithTag(imageUrl);
- if(imageView != null) {
- imageView.setImageDrawable(imageDrawable);
- }
- }
- });
-
- if (cachedImage != null) {
- userImage.setImageDrawable(cachedImage);
- } else {
- userImage.setImageResource(R.drawable.icon);
- }
- }
- return convertView;
- }
- }
- </span>
异步加载图片类AsyncImageLoader.java
- <span style="font-size:16px;">
-
-
-
- public class AsyncImageLoader {
-
- private HashMap<String, SoftReference<Drawable>> imageCache;
-
- public AsyncImageLoader() {
- imageCache = new HashMap<String, SoftReference<Drawable>>();
- }
-
- public Drawable loadDrawable(final Renren renren, final ImageCallback imageCallback) {
- final String imageId = renren.getId();
- final String imageURL = renren.getHeadurl();
-
-
- if (imageCache.containsKey(imageId)) {
- SoftReference<Drawable> softReference = imageCache.get(imageId);
- Drawable drawable = softReference.get();
- if (drawable != null) {
- return drawable;
- }
- }
-
- final Handler handler = new Handler() {
- public void handleMessage(Message message) {
- imageCallback.imageLoaded((Drawable) message.obj, imageURL);
- }
- };
-
-
- new Thread() {
- @Override
- public void run() {
- Drawable drawable = loadImageFromUrl(imageURL);
- imageCache.put(imageURL, new SoftReference<Drawable>(drawable));
- Message message = handler.obtainMessage(0, drawable);
- handler.sendMessage(message);
- }
- }.start();
- return null;
- }
-
-
- public static Drawable loadImageFromUrl(String url) {
- URL m;
- InputStream i = null;
- try {
- m = new URL(url);
- i = (InputStream) m.getContent();
- } catch (MalformedURLException e1) {
- e1.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- Drawable d = Drawable.createFromStream(i, "src");
- return d;
- }
-
-
- public interface ImageCallback {
-
- public void imageLoaded(Drawable imageDrawable, String imageUrl);
- }
- }
- </span>
下图为运行结果:(因涉及好友真实信息,不将部分图片和文字加码,此结果仅表示按照此步骤运行正确)
另外需要注意的是,访问人人API需要访问网络,因此需要在添加网络访问授权
<uses-permissionandroid:name="android.permission.INTERNET" />
此文仅简单介绍了基于人人网API的Android开发的基本步骤,如需了解更多信息,请研究人人官网http://wiki.dev.renren.com/wiki/API。欢迎就上述问题进一步交流。
参考文献:http://wiki.dev.renren.com/wiki/API