现在的位置: 首页 > 综合 > 正文

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

2013年10月01日 ⁄ 综合 ⁄ 共 10836字 ⁄ 字号 评论关闭

作者:贺小令

       人人网(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

import android.app.Activity;
……
/**
 * @author hexiaoling
 */
public class RenrenLoginActivity extends Activity {
	public final static String TAG = "RenrenLoginActivity";
	private WebView renrenLoginWebView; // WebView 控件,用于显示从人人网请求得到html授权页面
	
	@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); // 得到 WebView 控件
		
		//对WebView进行设置(对JS的支持,对缩放的支持,对缓存模式的支持)
		WebSettings webSettings = renrenLoginWebView.getSettings();
		webSettings.setJavaScriptEnabled(true); 
		webSettings.setBuiltInZoomControls(true);
		webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
		
		// 根据client_id取得到人人服务器人人对你的应用授权,如果成功则返回人人网登陆页面的html文件,并在WebView控件上显示
		// 此时用户需要输入自己人人账号的用户名、密码并点击登陆
		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() {

			//击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边
			@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();//让webview处理https请求
			}
			
			@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=")) { // 从URL中解析得到 access_token
						access_token = url0.substring(url0.indexOf("access_token=") + 13, url0.length() - 19);
						try {
							access_token = URLDecoder.decode(access_token, "utf-8"); // 制定为utf-8编码
						} catch (UnsupportedEncodingException e) {
							e.printStackTrace();
						}
						Log.i(TAG, "access_token = " + access_token);
					}
					if(url0.contains("expires_in=")) { // 从URL中解析得到 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; // 将解析得到的 access_token 保存起来
					RenrenUtil.expires_in = expires_in; // 将解析得到的 expires_in 保存起来
					
					//输入用户名、密码登陆成功,进行页面跳转
					if(RenrenUtil.access_token.length() != 0) {
						Intent intent = new Intent(RenrenLoginActivity.this, RenrenFriendsActivity.class);
						startActivity(intent);
					}
				}
				super.onPageFinished(view, url);
			}			
		});
	}
}

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




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






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




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


String method = " friends.getFriends "; // 人人API中定义的方法,用于得到当前登录用户的好友列表。
			int count = 30; // 得到用户数30个
			
			List<String> param = new ArrayList<String>();
			param.add("method=" + method);
			param.add("v=1.0"); // 版本固定参数
			param.add("access_token=" + RenrenUtil.access_token); //RenrenUtil.access_token 是在LoginActivity中已经保存的数据
			param.add("format=JSON"); // 返回JSON数据
			param.add("count=" + count); //得到用户数
			
			String signature = getSignature(param, "55f436828286417ab9db7ea17ee9cbde"); //第二个参数为 Secret Key
			
			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));

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


/**
	 * 得到MD5签名
	 * @param paramList
	 * @param secret
	 * @return
	 */
	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);  //符串末尾追加上应用的Secret Key
  		 try {//下面是将拼好的字符串转成MD5值,然后返回
  		    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;
   }

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


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) { //为200表示执行成功
					strResult = EntityUtils.toString(httpResponse.getEntity()); //得到返回数据(为JSON数据)
					if(! strResult.contains("error_code")) {
						renrenList = Renren.parseRenrenFromJson(strResult); //解析JSON数据为相应对象
						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;


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

 

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

/**
	 * 解析JSON数据为人人对象
	 * @param renrenJsonData
	 * @return
	 */
	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;
	}

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




人人类Renren.java


public class Renren {
	protected String id;
	protected String name;
	protected String headurl;
省略get、set方法。
}


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

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;
		}
	}


异步加载图片类AsyncImageLoader.java


/**
 * 异步加载图片类
 * @author hexiaoling
 */
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);
             }
         };
         
         //开辟一个新线程去下载图片,并用Handler去更新UI
         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;
     }
  
     //从URL下载图片
	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);
     }
}

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





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

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

 

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

 

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




抱歉!评论已关闭.