最近在倒腾自己的毕业设计,需要通过Android手机采集sensors 的数据,然后通过post方式向服务器发送json格式的数据,以json数组的方式发送。服务器端采用SpringMVC+hibernate的方式搭建。服务器端接收客户端传来的json数据,通过service保存到数据库,然后返回json格式的消息。客户端接收返回消息,显示上传成功或失败。
看似很easy的事情,其实好多坑,坑了我好久。下面一一详细说明。
1、 首先是服务器端的配置,搭建Spring+hibernate的具体配置方法就不细说了,可以参考我上篇文章。首先看下Controller怎么写:
@Controller
@RequestMapping("/upload")
public class UploadController {
@Autowired
private SensordataService sensordataService;
@RequestMapping(value = "/data",method = RequestMethod.POST)
@ResponseBody
public JsonResponse uploadData(@RequestBody List<Sensordata> list){
try {
sensordataService.saveAll(list);
System.out.println("save to database");
return new JsonResponse(WebStatus.UPLOAD_SUCCESSFULL,"");
}catch (Exception e) {
return new JsonResponse(WebStatus.UPLOAD_FAILED,e.getMessage());
}
}
}
首先@RequestBody的作用是让传进来的jsonArray格式的Sensordata数组自动封装成List<Sensordata>形式。非常方便。如果传来的是单个的Sensordata对象,则可以写成
@Request Sensordata data.
其次@ResponseBody 将会根据传进来的数据格式,自动把返回的对象封装成相应的格式,比如我这里传入的时json格式,则@ResponseBody则自动把返回的JsonResponse对象封装成json格式
当然,这些需要提前,首先是设置POST的内容为json格式,这个后面细说。其次是需要在Spring-MVC.xml的配置文件中添加下面一行
<mvc:annotation-driven />
最后增加依赖:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.8.3</version>
</dependency>
这样,服务器端基本完毕,可以单独写个类测试下,是否可以接收Json格式的数据,这里建议不要在android下写,因为android下也有坑。出错了你就不知道到底是哪里错了。
2. 服务器端涉及的类
第一个是POJO类,对应数据库的sensordata表。用于保存androd传来的sensor数据。
package org.bnu.model;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
/**
* Created by xie on 14-11-6.
*/
@Entity
@Table(name = "sensordata")
public class Sensordata {
@Id
@GeneratedValue
@Column(name="id")
@Getter@Setter
private Integer id;
@Column(name="accX")
@Getter@Setter
private Double accX;
@Column(name = "accY")
@Getter@Setter
private Double accY;
@Column(name = "accZ")
@Getter@Setter
private Double accZ;
@Column(name = "gyroX")
@Getter@Setter
private Double gyroX;
@Column(name = "gyroY")
@Getter@Setter
private Double gyroY;
@Column(name = "gyroZ")
@Getter@Setter
private Double gyroZ;
@Column(name = "magnetX")
@Getter@Setter
private Double magnetX;
@Column(name = "magnetY")
@Getter@Setter
private Double magnetY;
@Column(name = "magnetZ")
@Getter@Setter
private Double magnetZ;
@Column(name = "orientX")
@Getter@Setter
private Double orientX;
@Column(name = "orientY")
@Getter@Setter
private Double orientY;
@Column(name = "orientZ")
@Getter@Setter
private Double orientZ;
@Column(name = "timestamp")
@Getter@Setter
private String timestamp;
}
第一个类是返回的对象,用于返回消息。
package org.bnu.util;
import lombok.Getter;
import lombok.Setter;
/**
* Created by xie on 14-11-8.
*/
public class JsonResponse {
@Getter@Setter
private String status;
@Getter@Setter
private String errorMessage;
public JsonResponse(String status,String errorMessage){
this.status=status;
this.errorMessage=errorMessage;
}
}
3. Android客户端
首先第一点需要申明的是,Android从3.0开始就不允许在主线程,即UI线程里直接访问web。
为什么?访问Web往往需要较长时间,在UI线程访问Web则会导致UI线程阻塞,等待网络返回。结果就是你的界面卡住,不能做其他交互。
为了防止这类情况,android从系统上禁止在UI线程访问web。说明了这个大坑后,先看下AndroidManifest.xml的配置,增加网络访问权限。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sensorcollect"
android:versionCode="1"
android:versionName="1.0" >
<!-- 取得网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 取得访问网络状态权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 取得访问wifi状态权限 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 取得改变wifi状态权限 -->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.sensorcollect.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
然后,我们在UI线程外,访问web,把数据上传到服务器,这里是用AsynTask.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt2.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
if(isConnected(getApplicationContext())) {
new DataUploadTask(getApplicationContext()).execute();
}else{
Toast.makeText(getApplicationContext(),"web is not connected",Toast.LENGTH_LONG).show();
}
}
});
}
用button控件触发AsynTask, AsynTask写法如下:
private class DataUploadTask extends AsyncTask<Void,Void,String>{
private Context context;
public DataUploadTask(Context context){
this.context=context;
}
@Override
protected String doInBackground(Void... params) {
WebService webService=new WebService(context);
boolean res=webService.upload();
if(res){
return WebStatus.UPLOAD_SUCCESSFULL;
}else{
return WebStatus.UPLOAD_FAILED;
}
}
@Override
protected void onPostExecute(String s) {
if(s.equals(WebStatus.UPLOAD_SUCCESSFULL)){
Toast.makeText(context,"data upload successfully",Toast.LENGTH_LONG).show();
}else{
Toast.makeText(context,"data upload failed",Toast.LENGTH_LONG).show();
}
}
}
在doInBackground中,调用webService 的upload方法,实现数据的上传。根据返回值,在PostExcute显示数据上传成功或失败
下面看webService如何实现,主要看如果用post的方式发送json数据。
package com.example.sensorcollect;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import android.util.Log;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import android.content.Context;
import org.json.JSONException;
import org.json.JSONObject;
public class WebService {
private static DbManager dbManager;
private static Context context;
private static final String TAG="sensorCollect";
public WebService(Context context){
dbManager=new DbManager(context);
this.context=context;
}
public boolean upload(){
String json_str=dbManager.queryAllInJson();
StringBuffer sb=new StringBuffer();
try {
<span style="white-space:pre"> </span>String url="http://172.16.144.48:8080/dataserver/upload/data";
HttpClient client=new DefaultHttpClient();
HttpPost request=new HttpPost(url);
<span style="white-space:pre"> </span>System.out.println(json_str);
<span style="white-space:pre"> </span>StringEntity stringEntity=new StringEntity(json_str);
<span style="white-space:pre"> </span>stringEntity.setContentType("application/json");
<span style="white-space:pre"> </span>request.setEntity(stringEntity);
HttpResponse response=client.execute(request);
HttpEntity entity=response.getEntity();
<span style="white-space:pre"> </span>StringBuilder builder=new StringBuilder();
if(entity!=null){
BufferedReader reader=new BufferedReader(new InputStreamReader(entity.getContent()));
String line="";
while((line=reader.readLine())!=null){
<span style="white-space:pre"> </span>builder.append(line+"\n");
}
reader.close();
}
JSONObject object=new JSONObject(builder.toString());
String status=object.getString("status");
<span style="white-space:pre"> </span>if(status.equals(WebStatus.UPLOAD_SUCCESSFULL)) {
<span style="white-space:pre"> </span>return true;
<span style="white-space:pre"> </span>}else if(status.equals(WebStatus.UPLOAD_FAILED)){
<span style="white-space:pre"> </span>Log.i(TAG,object.getString("errorMessage"));
<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>return false;
<span style="white-space:pre"> </span>}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return false;
}
}
首先把待传数据转成json数组的格式。然后StringEntity把json串塞进去,并把Content-type设为application/json格式。这步非常重要,这样服务器端就知道传入的是json格式的数据,spring就可以自动将数据从json格式的转成List<Sensordata>的形式。
4. 小结。JSON格式的数据处理可以用alibaba提供的fastjson,听说性能很高,主要是用起来非常方便,只需要引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.41</version>
</dependency>
List<T> 转JSON数组
List<SensorBean> datalist=queryAll();
String json=JSON.toJSONString(datalist);
JSON数组转List<T>
List<Sensordata> list=JSON.parseArray(json_str,Sensordata);