Android与人人网连接实例:我在听

作者:裘德超

      

       目前,社交网络概念正火。而手机最初设计的目的正是让人们进行通信。人人网作为中国最大的社交网站,用户数量众多,本文通过一个简单的小程序:“我在听”向大家展示renren api的使用。

首先介绍一下“我在听”。功能:在用户听歌时,在不需要用户进行额外操作的情况下,根据用户正在听的曲目,以发状态的形式同步至人人网。在安装完“我在听”之后,点击使用人人网登录,输入账号密码,登录成功后,选择是否自动同步,如果此时用户打开了网络,那么只要用户通过自带的播放器听歌,就会自动发布状态,例如:我在听张国荣的《倩女幽魂》。

       下面开始介绍开发过程;

首先,在人人 api页面http://dev.renren.com/ 里先登录,然后创建一个android应用。填写完表单,创建完成后,可以获得人人给你的唯一标志:

应用ID:xxxxxxx

API Key:xxxxxxxxxxxxxxxxxxxxxxx

Secret Key:xxxxxxxxxxxxxxxxxxxxxx

这三串字符串用于标志你的应用。

 

下面介绍如何获取用户当前正在听的歌的信息:

当系统默认的播放器开始播放下一首歌时,会发出一个广播(intent中包含歌曲名,艺术家等信息),我们只要定义一个接收这个广播的广播接收器,并且从intent中抽取出需要的信息即可。

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.renoqiu"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="10" />
	<uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".IamListenActivity"
            android:label="@string/app_name" >
        </activity>
        <activity
            android:name=".SettingActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name=".MusicBroadcastReceiver">
			<intent-filter>
			    <action android:name="com.android.music.metachanged"></action>
			</intent-filter>            
        </receiver>
        <service android:name=".PushStatusService" >
            <intent-filter>
                <action android:name="com.renoqiu.pushstatus" />
            </intent-filter>
        </service>
    </application>
</manifest>


根据main.xml可知,我们定义了类MusicBroadcastReceiver捕捉action名为com.android.music.metachanged的广播。

 

src/com/renoqiu/ MusicBroadcastReceiver.java


package com.renoqiu;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

public class MusicBroadcastReceiver extends BroadcastReceiver {
	private static final Object SMSRECEIVED = "com.android.music.metachanged";
	@Override
	public void onReceive(Context context, Intent intent) {
		if(intent.getAction().equals(SMSRECEIVED)){
			String trackName=intent.getStringExtra("track");
			String artist=intent.getStringExtra("artist");

			Intent pushStatusIntent = new Intent();
			pushStatusIntent.setAction("com.renoqiu.pushstatus");
    		Bundle myBundle = new Bundle();
    		myBundle.putString("trackName", trackName);
    		myBundle.putString("artist", artist);
    		pushStatusIntent.putExtras(myBundle);
			context.startService(pushStatusIntent);
		}
	}
}


在类MusicBroadcastReceiver中我们抽取了歌曲名和艺术家名,并且调用context.startService()方法创建了一个service。

 

src/com/renoqiu/ PushStatusService.java


package com.renoqiu;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.widget.Toast;

public class PushStatusService extends Service {
	private Handler handler;
	private com.renoqiu.LooperThread thread;
	private SharedPreferences sharedPreferences;
	private boolean syncFlag;
	private ConnectivityManager cm;
	private NetworkInfo ni;

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}
	private boolean checkNet() {
		cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
		if (cm == null) {
			return false;
	    }
		ni = cm.getActiveNetworkInfo();
		if (ni == null || !ni.isAvailable()) {     
			return false;     
		}     
		return true;     
	}    
	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		
		syncFlag = sharedPreferences.getBoolean("syncFlag",false);
		if(syncFlag && checkNet()){
			if(intent != null){
				Bundle myBundle = intent.getExtras();
				String trackName = myBundle.getString("trackName");
				String artist = myBundle.getString("artist");
				String accessToken = sharedPreferences.getString("accessToken","");
				
				if(accessToken != null && !accessToken.equals("")){
					String requestMethod = "status.set";
					//接口名称  
					String url = StatusPublishHelper.API_URL;
					String secretKey = StatusPublishHelper.SECRET_KEY;
					if(artist == null || artist.equals("")){
						artist = "xxx";
					}
					if(trackName == null || trackName.equals("")){
						trackName = "xxx";
					}
					String message = "我在听" + artist + "的《" + trackName + "》。\r\n 通过我在听发布!";
					thread = new LooperThread(handler, requestMethod, "1.0", url, accessToken, message, secretKey);
					thread.start(); /* 启动线程 */
				}else{
					Toast.makeText(PushStatusService.this, "请登陆!", Toast.LENGTH_SHORT).show();
					SharedPreferences.Editor editor = sharedPreferences.edit();
					editor.putBoolean("syncFlag", false);
					editor.commit();
				}
			}
		}
	}

	@Override
	public void onCreate() {
		super.onCreate();
		sharedPreferences = getSharedPreferences("shared", MODE_PRIVATE);
		
		handler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				switch (msg.what) {
					case 0:
						if((Integer)msg.obj != 1){
							Toast.makeText(PushStatusService.this, "同步失败!请检查网络是否打开...", Toast.LENGTH_SHORT).show();
						}else{
							Toast.makeText(PushStatusService.this, "同步成功!", Toast.LENGTH_SHORT).show();
						}
						break;
				}
			}
		};
	}
}


在PushStatusService中,首先进行一系列检查,例如:网络是否已经打开,用户是否已经登录,是否开启同步等信息。其中用到了sharedPreferences保存信息。如果条件都满足那么将新建一个线程去通过人人api发状态,其中就需要使用到之前创建应用时所得到的key,在介绍具体如何状态之前先接着介绍一下人人的api。要通过人人发送状态首先必须通过人人的登录认证:OAuth2.0,详情见:http://wiki.dev.renren.com/wiki/Authentication

我们的登录流程开始于通过内嵌在IamListenActivity中的Webkit访问人人OAuth 2.0的Authorize Endpoint:

https://graph.renren.com/oauth/authorize?client_id=YOUR_API_KEY&response_type=token&redirect_uri=YOUR_CALLBACK_URL&display=touch&scope=status_update。

client_id:必须参数。在开发者中心注册应用时获得的API Key。

response_type:必须参数。客户端流程,此值固定为“token”。当用户登录成功,浏览器会被重定向到YOUR_CALLBACK_URL,并且带有参数Access Token。这个Access Token可以标志登录用户,避免需要多次输入用户名密码。此处我们会把accesstoken保存在sharedPreferences中,下次需要使用时,直接从sharedPreferences中获取。

redirect_uri:登录成功,流程结束后要跳转回得URL。redirect_uri所在的域名必须在开发者中心注册应用后,填写在编辑属性选项卡中填写到服务器域名中,人人OAuth2.0用以检查跳转的合法性。

如果用户已经登录,人人OAuth 2.0会校验存储在用户浏览器中的Cookie。如果用户没有登录,人人OAuth 2.0会为用户展示登录页面,让用户输入用户名和密码:

display=touch:一般的智能手机都是这个选项,

scope=status_update:表示我们会用到更新状态的功能。

 

src/com/renoqiu/ IamListenActivity.java


package com.renoqiu;

import java.net.URLDecoder;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class IamListenActivity extends Activity {
	private WebView webView;
    private String accessToken = null;  
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		webView = (WebView) findViewById(R.id.web);
		WebSettings settings = webView.getSettings();  
        settings.setJavaScriptEnabled(true);  
        settings.setSupportZoom(true);  
        settings.setBuiltInZoomControls(true);  
        webView.loadUrl(StatusPublishHelper.AUTHURL);
        webView.requestFocusFromTouch();  
        WebViewClient wvc = new WebViewClient() {  
            @Override  
            public void onPageFinished(WebView view, String url) {  
                super.onPageFinished(view, url);  
                //人人网用户名和密码验证通过后,刷新页面时即可返回accessToken  
                String reUrl = webView.getUrl();  
                if (reUrl != null && reUrl.indexOf("access_token") != -1) {  
                    //截取url中的accessToken  
                    int startPos = reUrl.indexOf("token=") + 6;  
                    int endPos = reUrl.indexOf("&expires_in");  
                    accessToken = URLDecoder.decode(reUrl.substring(startPos, endPos));
                    //保存获取到的accessToken  
                    //share.saveRenrenToken(accessToken);  
                    Toast.makeText(IamListenActivity.this, "验证成功,设置同步后。\n听歌时就能自动传状态哦。:)", Toast.LENGTH_SHORT).show();
                    SharedPreferences settings = (SharedPreferences)getSharedPreferences("shared", MODE_PRIVATE);
                    SharedPreferences.Editor editor = settings.edit();
                    editor.putString("accessToken", accessToken);
                    editor.putBoolean("syncFlag", false);
                    editor.commit();
                    finish();
                }
            }  
        };
        webView.setWebViewClient(wvc);
	}
}


res/layout/main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

	<WebView
	    android:id="@+id/web"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent" />
</LinearLayout>


由代码可知,我们通过webkit访问Authorize Endpoint后,从返回的链接中抽取了accesstoken,并且保存起来了。有了access token之后,我们就可以通过renren的api,进行更新状态的操作了,下面的LooperThread就是用于更新状态,并且给出用户反馈的类。

“为了确保应用与人人API 服务器之间的安全通信,防止Secret Key盗用,数据篡改等恶意攻击,人人API服务器使用了签名机制(即sig参数)来认证应用。签名是由请求参数和应用的私钥Secret Key经过MD5加密后生成的字符串。应用在调用人人API之前,要计算出签名,并追加到请求参数中。“(关于签名的计算规则参见:http://wiki.dev.renren.com/wiki/Calculate_signature

 

 

src/com/renoqiu/ LooperThread.java

package com.renoqiu;

import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;

import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class LooperThread extends Thread{
	private String requestMethod;
	private String v;
	private String url;
	private String accessToken;
	private String status;
	private String secretKey;
	private Handler fatherHandler;
	public LooperThread(Handler fatherHandler, String requestMethod, String v, String url, String accessToken, String message, String secretKey) {
		this.requestMethod = requestMethod;
		this.v = v;
		this.url = url;
		this.accessToken = accessToken;
		this.status = message; 
		this.secretKey = secretKey;
		this.fatherHandler = fatherHandler;
	}

	public void run() {
		Message msg = new Message();
		msg.obj = updateStatus(status);
		msg.what = 0;
		fatherHandler.sendMessage(msg);
	}
	public  int updateStatus(String status) {  
		int success = 0;
        //生成签名 字典序排列
        StringBuilder sb = new StringBuilder();  
        sb.append("access_token=").append(accessToken)  
            .append("format=").append("JSON")  
            .append("method=").append(requestMethod)  
            .append("status=").append(status)  
            .append("v=").append(v)
            .append(secretKey);
        String sig = StatusPublishHelper.getMD5(sb.toString());  

        HttpPost httpRequest = new HttpPost(url);
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("access_token", accessToken));
		params.add(new BasicNameValuePair("method", requestMethod)); 
		params.add(new BasicNameValuePair("v", v)); 
		params.add(new BasicNameValuePair("status", status)); 
		params.add(new BasicNameValuePair("format", "JSON"));
		params.add(new BasicNameValuePair("sig", sig));
		 try { 
			httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
			HttpResponse httpResponse = new DefaultHttpClient().execute(httpRequest);
			if (httpResponse.getStatusLine().getStatusCode() == 200){
				String result = EntityUtils.toString(httpResponse .getEntity());
				 JSONObject json = new JSONObject(result);
				 success = (Integer)json.get("result");
				 Log.v("org.reno", result);
			}
        }catch (Exception e){
			return success;
		} 
  
        return success;  
    }
}


上面代码中,首先计算出所有参数以字典序升序排列后,拼接在一起后的md5值作为签名。然后向http://api.renren.com/restserver.do发送post请求,其中包括之前获得的access_token,所调用的方法名(此处我们调用的是更新状态的方法名,关于各种api详见:http://wiki.dev.renren.com/wiki/API),及方法的参数,此处包括版本,状态内容,返回类型(此处为json),最后是签名。

params.add(newBasicNameValuePair("access_token", accessToken));

              params.add(newBasicNameValuePair("method", requestMethod));

              params.add(newBasicNameValuePair("v", v));

              params.add(newBasicNameValuePair("status", status));

              params.add(newBasicNameValuePair("format", "JSON"));

              params.add(newBasicNameValuePair("sig", sig));

然后等待服务器的回复,并且解析json格式的数据,判断是否发送成功。

到此,基本的发状态的过程就结束了。

下面的类用于提供用户登陆以及让用户选择是否开启自动同步。

src/com/renoqiu/ SettingActivity.java



package com.renoqiu;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.Toast;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ToggleButton;

public class SettingActivity extends Activity {
	private ToggleButton syncToggleButton;
	private Button loginBtn;
	private SharedPreferences sharedPreferences;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.setting);
		syncToggleButton = (ToggleButton)findViewById(R.id.syncToggleButton);
		sharedPreferences = (SharedPreferences)getSharedPreferences("shared", MODE_PRIVATE);
		boolean syncFlag = sharedPreferences.getBoolean("syncFlag",false);
		syncToggleButton.setChecked(syncFlag);
		syncToggleButton.setOnCheckedChangeListener(new OnCheckedChangeListener(){
			@Override
			public void onCheckedChanged(CompoundButton buttonView,
					boolean isChecked) {
				if(isChecked == false){
					SharedPreferences.Editor editor = sharedPreferences.edit();
	                editor.putBoolean("syncFlag", isChecked);
	                editor.commit();
				}else{
					String accessToken = sharedPreferences.getString("accessToken","");
					if(accessToken != null && !accessToken.equals("") ){
						SharedPreferences.Editor editor = sharedPreferences.edit();
		                editor.putBoolean("syncFlag", isChecked);
		                editor.commit();
					}else{
						syncToggleButton.setChecked(false);
						Toast.makeText(SettingActivity.this, "请先登陆!", Toast.LENGTH_SHORT).show();
					}
					
				}
			}
		});
		loginBtn = (Button)findViewById(R.id.loginBtn);
		loginBtn.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(View arg0) {
				Intent intent = new Intent(SettingActivity.this, IamListenActivity.class);
				startActivity(intent);
			}});
	}
}


res/layout/setting.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
	    <Button
        android:id="@+id/loginBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="" 
        android:background="@drawable/btn_login"
        android:layout_marginTop="10dp"/>

    <RelativeLayout
        android:id="@+id/relativeLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/toggleTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:layout_marginTop="20dp"
            android:text="@string/toggleSync" />

        <ToggleButton
            android:id="@+id/syncToggleButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@+id/toggleTextView" />

    </RelativeLayout>

</LinearLayout>


最后一个类包含了各种链接常量,和把字符串转换为md5的方法:

src/com/renoqiu/ StatusPublishHelper.java


package com.renoqiu;

import java.security.MessageDigest;

public class StatusPublishHelper {
	// 你的应用ID
	public static final String APP_ID = "xxxxx";
	// 应用的API Key
	public static final String API_KEY = "xxxxxxxxxxxxxxxxxxxxxxx";
	// 应用的Secret Key
	public static final String SECRET_KEY = "xxxxxxxxxxxxxxxx";
	
	public static final String API_URL = "http://api.renren.com/restserver.do";
	public static final String AUTHURL = "https://graph.renren.com/oauth/authorize?client_id="  
	        + API_KEY +"&response_type=token"  
	        + "&redirect_uri=http://www.renoqiu.com/iamlisten.html&display=touch"  
	        + "&scope=status_update";  
	public static String getMD5(String s) {  
        try {  
            MessageDigest md5 = MessageDigest.getInstance("MD5");  
  
            byte[] byteArray = s.getBytes("UTF-8");  
            byte[] md5Bytes = md5.digest(byteArray);  
  
            StringBuffer hexValue = new StringBuffer();  
  
            for (int i = 0; i < md5Bytes.length; i++) {  
                int val = ((int) md5Bytes[i]) & 0xff;  
                if (val < 16)  
                    hexValue.append("0");  
                hexValue.append(Integer.toHexString(val));  
            }  
  
            return hexValue.toString();  
  
        } catch (Exception e) {  
            e.printStackTrace();  
            return null;  
        }
    }
}

下图为测试使用的效果。


  




SourceCode下载链接:

https://github.com/renoqiu/IamListening

需要注意的是:下载的源代码并不能直接使用,读者需要自行修改IamListening/src/com/renoqiu/StatusPublishHelper.java类下的APP_ID, API_KEY, SECRET_KEY 这三个常量为你申请的应用的对应的值后,就可以正常使用了。

http://code.google.com/p/iamlisten/downloads/list可以从这里现在一个笔者编译好的apk文件,进行测试。

 

参考链接:

http://wiki.dev.renren.com/wiki/API

http://wiki.dev.renren.com/wiki/Calculate_signature

http://wiki.dev.renren.com/wiki/Authentication

 



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值