基于人人网的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”。
第三、四步:
人人网开放平台提供了上述的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
[java] view plaincopy
- <span style="font-size:16px;">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);
- }
- });
- }
- }
- </span>
下图表示加载人人授权应用的html登陆成果界面:
第五步:这里以访问我的人人好友列表为例,包括取得好友的名称和照片。主要流程如下:
①人人API利用不同功能的请求函数和相关参数,详细请参考http://wiki.dev.renren.com/wiki/API。此处以获得好友列表为例,其对应函数为friends.getFriends(得到用户的信息,支持批量获取),人人对friends.getFriends的描述和参数列表如下图:
下图表示将请求参数和Secret得到MD5值为签名,并组装请求参数核心代码:
[java] view plaincopy
- <span style="font-size:16px;">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));
- </span>
下图表示将请求参数和SecretKey加密得到它们的MD5值的算法如下:
[java] view plaincopy
- <span style="font-size:16px;">/**
- * 得到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;
- }
- </span>
②使用HTTP协议,Android已经对其做了很好的封装,使用起来很方便。使用将第①步得到的参数列表想http://api.renren.com/restserver.do发送Http请求,同时得到HttpResponse响应,判断响应代码是否为200,为200表示成功。然后解析JSON数据为相关对象,核心代码如下:
[java] view plaincopy
- <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) { //为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;
- </span>
网络请求会比较耗时,因此建议使用多线程来处理。在Android中可以使用继承AsyncTask<String, Integer,String>类来实现多线程处理。
③解析JSON数据为人人对象方法:
[java] view plaincopy
- <span style="font-size:16px;">/**
- * 解析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;
- }
- </span>
下图为JSON数据的数据结构:
人人类Renren.java
[java] view plaincopy
- <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,因此图片还需要单独去请求,为了保障用户体验,图片采用异步加载。
[java] view plaincopy
- <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
[java] view plaincopy
- <span style="font-size:16px;">/**
- * 异步加载图片类
- * @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);
- }
- }
- </span>
下图为运行结果:(因涉及好友真实信息,不将部分图片和文字加码,此结果仅表示按照此步骤运行正确)
另外需要注意的是,访问人人API需要访问网络,因此需要在添加网络访问授权
<uses-permissionandroid:name="android.permission.INTERNET" />
此文仅简单介绍了基于人人网API的Android开发的基本步骤,如需了解更多信息,请研究人人官网http://wiki.dev.renren.com/wiki/API。欢迎就上述问题进一步交流。
参考文献:http://wiki.dev.renren.com/wiki/API
基于人人网的Android开发流程介绍相关推荐
- 基于模型的嵌入式开发流程_如何使用基于模型的测试来改善工作流程
基于模型的嵌入式开发流程 Unit testing is not enough – so let's start using model-based testing to improve our wo ...
- Spring Security技术栈学习笔记(十三)Spring Social集成第三方登录验证开发流程介绍
开发第三方登录,我们必须首先要了解OAuth协议(本文所讲述的OAuth协议指的是OAuth2协议),本文首先简单介绍OAuth协议,然后基于Spring Social来阐述开发第三方登录需要做哪些准 ...
- (2)FPGA开发流程介绍(第1天)
(2)FPGA开发流程介绍(第1天) 1 文章目录 1)文章目录 2)FPGA初级课程介绍 3)FPGA初级课程架构 4)FPGA开发流程介绍(第1天) 5)技术交流 6)参考资料 2 FPGA初级课 ...
- sawtooth,井字棋演示和交易族开发流程介绍
1.实例演示 这里以官网的XO交易族为例演示,该交易族是一个井字棋游戏,在开始之前,我们需要搭建起来一个单节点的sawtooth环境,详情可以查看上一篇博客: Sawtooth,使用docker启动单 ...
- android studio开发工具介绍,Android应用开发之Android开发工具介绍、Android Studio配置...
本文将带你了解Android应用开发之Android开发工具介绍.Android Studio配置,希望本文对大家学Android有所帮助. 2.1 Android Studio配置 2.1.1 ...
- Android开发流程总结
本文来自肥宝传说之路,引用必须注明出处! Android开发流程总结 首先,发个作品链接先 http://www.mumayi.com/android-550241.html 一.注册开发者账号: 开 ...
- 基于chromium for android开发Android浏览器
在上一篇文章<< Chromium for Android开源了 >>中谈到了Google已经完全开源了Chromium for Android,这样我们就完全可以开发与Chr ...
- 网站的开发流程介绍(转)
网站的开发流程介绍 从本章开始,我们将学习网站设计和布局技术,其中重点是熟悉网站的开发流程,DIV层和表格布局各自的使用场合,以及网站开发的一些经验.技巧:难点是如何进行网页布局. 创建一个商业网站, ...
- Android JNI开发流程介绍
1.什么是JNI JNI全称是Java Native Interface,中文称为Java本地接口.JNI是JAVA语言和C/C++语言沟通的协议,通过JNI,Java代码可以调用C.C++等语言写的 ...
最新文章
- 按下开机键,计算机背后的故事
- 外包 银行数字签名U盘的用法
- android测试点整理
- java fileupload 进度_Java上传文件进度条的实现方法(附demo源码下载)
- Altium Designer Winter09 的使用心得
- Go语言的DES加密(CBC模式, ECB模式) ---- 与java加密互通(转)
- StringBuilder的实现与技巧
- 单点登录Redis存储Session及SessionId问题说明与集群实战-3
- 关闭word_Word教程第2讲:文档的基本操作(含视频)
- php函数库快速记忆法_PHP速成大法
- 【渝粤教育】国家开放大学2018年秋季 0692-22T化工设备机械基础 参考试题
- 企业级项目实战讲解!java的war包能直接改名么
- AngularJs自学心得
- 清华大学医学院 | 体外成熟人卵单细胞多组学研究及总结干细胞分化为配子进展文章...
- element ui el-dialog 居中,并且内容多的时候内部可以滚动
- SQL 高效的万能分页存储过程
- (1)-(Two SUM-在数组中找到两个数,他们的和为给定的数)-(数组遍历)
- C# WinForm程序中使用Unity3D控件
- 小米笔记本、小米游戏本重装原装出厂镜像教程-有百度盘的提取码
- 小程序图片上传及预览和删除
热门文章
- Catalyst 5.8: the Perl MVC Framework
- linux 定时任务,压缩 日志,并删除掉 指定日期之前的 日志
- linux(cat,more,less,head)——对文件显示进行查看操作
- ANDROID深度探索(卷1)HAL与驱动开发 第四章
- 缓存2 动态缓存 memcached
- Java 的布局管理器GridBagLayout的使用方法【图文说明】
- soapUI中多个TestCase之间传递参数
- oracle 10g 学习之函数和存储过程(12)
- c# 使用timer定时器操作,上次定时到了以后,下次还未执行完怎么处理
- 【征稿+竞赛】WACV 2021 第一届无人车视觉学术研讨会