Android深入探究笔记之六 -- 与互联网互访数据
2010年12月12日
Android 从网络上获取数据
1. 普通 J2EE 代码下的网络编程
public class ImageTool { public static void main(String[] args) throws Exception { String path = "http://hiphotos.baidu.com/z%D6%DCy%D4%C6f%B7%E5/p ic/item/6ca59b1de32de389a5866972.jpg"; URL url = new URL(path); // GET 必须大写 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); // 超时 5秒 conn.setReadTimeout(5 * 1000); InputStream is = conn.getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while((len = is.read(buffer)) != -1 ) { bos.write(buffer, 0, len); } byte[] data = bos.toByteArray(); File file = new File("zhiling.jpg"); FileOutputStream fos = new FileOutputStream(file); fos.write(data); fos.close(); } }
2. 链接网络权限
3. ImageView : 显示图片的控件
imageView.setImageBitmap(Bitmap);
4. Bitmap : 位图
Bitmap bitmap = BitmapFactory.decodeByteArray(ByteArray, 0, ByteArray.length);
5.代码示例 : 获取网络上的一张图片,显示在 Android 手机上
1. AndroidManifest.xml -- 声明链接网络的权限
2. main.xml -- 布局
3. strings.xml -- 字符串定义
Hello World, ImageActivity! 图片查看器 图片路径 查看 获取失败
4. Service -- 获取网络上的图片的字节数组
package wjh.android.image.service; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class ImageService { public static byte[] catchImage(String path) throws Exception { URL url = new URL(path); // GET HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); // 超时 5秒 conn.setReadTimeout(5 * 1000); InputStream is = conn.getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while((len = is.read(buffer)) != -1 ) { bos.write(buffer, 0, len); } is.close(); bos.close(); return bos.toByteArray(); } }
5. Main Activity
public class ImageActivity extends Activity { private Button button; private EditText pathText; private ImageView imageView; private static final String TAG = "ImageActivity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); button = (Button) findViewById(R.id.button); imageView = (ImageView) findViewById(R.id.image); pathText = (EditText) findViewById(R.id.path); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String path = pathText.getText().toString(); try { // 获取图片数据 byte[] imageBytes = ImageService.catchImage(path); Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); imageView.setImageBitmap(bitmap); } catch (Exception e) { Log.i(TAG, e.toString()); Toast.makeText(ImageActivity.this, R.string.error, Toast.LENGTH_LONG); } } }); } }
6. 从网络上获取视频的最新资讯(从网络上获取 XML 数据)
① 新建 web project -- myvideoweb
本项目采用 struts 为视图层框架
** Service
public class VideoServiceImpl implements VideoService { /** * 获取最新视频资讯 */ public List getLastVideoMesasages() { List vms = new ArrayList(); vms.add(new VideoMessage(89, "喜羊羊与灰太狼全集", 67)); vms.add(new VideoMessage(12, "实拍舰载直升东海救援演习", 23)); vms.add(new VideoMessage(19, "喀麦隆VS荷兰", 90)); return vms; } }
** Action
public class VideoAction extends Action { private VideoService service = new VideoServiceImpl(); @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { List vms = service.getLastVideoMesasages(); request.setAttribute("videos", vms); return mapping.findForward("videoListPage"); } }
** View
${video.title} ${video.timeLength}
** 若访问 http://localhost:8080/myvideoweb/video/list.do,结果为:
- - 喜羊羊与灰太狼全集 67 - 实拍舰载直升东海救援演习 23 - 喀麦隆VS荷兰 90
② 新建 Android 项目
** Service
public class VideoService { /** * 解析从网络上获取到的 XML 文档。返回解析后的 VideoMessage 集合 */ public static List getLastVideos() throws Throwable { // 注意,这里不能写为 localhost 和 127.0.0.1,因为程序最终会在手机上运行。它们就代表手机本身的 ip 了。这里应该写成电脑的本机 IP Addr. String path = "http://10.4.74.20:8080/myvideoweb/video/list.do"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); InputStream is = conn.getInputStream(); return parseXML(is); } /** * 从指定输入流中解析出 VideoMessage 集合并返回 */ private static List parseXML(InputStream is) throws Exception { List videos = null; VideoMessage vs = null; XmlPullParser pullParser = Xml.newPullParser(); pullParser.setInput(is, "UTF-8"); int eventType = pullParser.getEventType(); // 触发第一个事件 while (eventType != XmlPullParser.END_DOCUMENT) { // 如果不为文档结束事件 switch (eventType) { case XmlPullParser.START_DOCUMENT: // 文档开始事件 videos = new ArrayList(); break; case XmlPullParser.START_TAG: // 标签开始事件 if ("video".equals(pullParser.getName())) { int id = Integer.parseInt(pullParser.getAttributeValue(0)); vs = new VideoMessage(); vs.setId(id); } if (vs != null) { if ("title".equals(pullParser.getName())) { vs.setTitle(pullParser.nextText()); } if ("timelength".equals(pullParser.getName())) { vs.setTimeLength(Integer.parseInt((pullParser .nextText()))); } } break; case XmlPullParser.END_TAG: // 标签结束事件 if ("video".equals(pullParser.getName())) { videos.add(vs); vs = null; } break; } eventType = pullParser.next(); } return videos; } }
③.Main Activity
public class ManiActivity extends Activity { private static final String TAG = "ManiActivity"; private ListView listView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); List vms = null; // 调用 Service try { vms = VideoService.getLastVideos(); } catch (Throwable e) { e.printStackTrace(); Log.e(TAG, e.toString()); Toast.makeText(this, R.string.error, Toast.LENGTH_LONG); } // 给 ListView 绑定显示数据 listView = (ListView) findViewById(R.id.listView); List> data = new ArrayList>(); for (VideoMessage vm : vms) { Map item = new HashMap(); item.put("title", vm.getTitle()); item.put("timelength", vm.getTimeLength()); data.add(item); } ListAdapter adapter = new SimpleAdapter(this, data, R.layout.item, new String[]{"title", "timelength"}, new int[]{R.id.title, R.id.timelength}); listView.setAdapter(adapter); } }
8 Android JSON
public void testJson() throws Throwable { // JSONArray 的构造参数,是一个 JSON 数组,数组中的每个元素都是一个标准的 JSON 数据。 JSONArray jsonArray = new JSONArray("[{name:'wjh', age:10, addr:'sc'}], [{name='java', age=15, addr='arr'}]"); Log.i(TAG, jsonArray.getJSONObject(0).getString("age")); // print:10 jsonArray = new JSONArray("[{name:'wjh', age:10, addr:'sc'},{name:'wjh2', age:102, addr:'sc2'}]"); Log.i(TAG, jsonArray.getJSONObject(1).getString("name")); // print:wjh2 }
9. 获取 json 格式的数据
**服务器端 Action
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { VideoForm formbean = (VideoForm)form; List videos = service.getLastVides(); // 当接收到 format=json 参数时执行 if 内部操作 if("json".equals(formbean.getFormat())){ //[{id:32,title:"喀麦隆VS荷兰",timelength:40},{id:32,title:"喀麦隆VS荷兰",timelength:40}] // 组装 json 格式的数据 StringBuilder json = new StringBuilder(); json.append('['); for(Video video : videos){ json.append('{'); json.append("id:").append(video.getId()).append(', '); json.append("title:\"").append(video.getTitle()).a ppend("\","); json.append("timelength:").append(video.getTimelen gth()); json.append('}'); json.append(','); } json.deleteCharAt(json.length()-1); json.append(']'); request.setAttribute("json", json); return mapping.findForward("json"); }else{ request.setAttribute("videos", videos); return mapping.findForward("video"); } }
** Android 客户端解析获取的 JSON 数据
Service :
public static List getLastVideosFromJSON() throws Throwable { String path = "http://10.4.74.20:8080/videoweb/video/list.do?for mat=json"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); InputStream is = conn.getInputStream(); // 从输入流中解析出 JSON 的字符串格式数据 byte[] bytes = ReadeStreamUtil.readStreamTobyteArr(is); String jsonStr = new String(bytes, "UTF-8"); // JSON 接收的参数必须是 数组格式的 JSON 字符串数据,例如 "[{name:'XXX',age:'21'},{name:'AAA',age:'222'},{}, {}]" JSONArray jsonArray = new JSONArray(jsonStr); List videos = new ArrayList(); for(int i=0; i vms = null; try { // XML 格式 //vms = VideoService.getLastVideos(); // JSON 格式 vms = VideoService.getLastVideosFromJSON(); } catch (Throwable e) { e.printStackTrace(); Log.e(TAG, e.toString()); Toast.makeText(this, R.string.error, Toast.LENGTH_LONG); } listView = (ListView) findViewById(R.id.listView); List> data = new ArrayList>(); for (VideoMessage vm : vms) { Map item = new HashMap(); item.put("title", vm.getTitle()); item.put("timelength", vm.getTimeLength()); data.add(item); } ListAdapter adapter = new SimpleAdapter(this, data, R.layout.item, new String[]{"title", "timelength"}, new int[]{R.id.title, R.id.timelength}); listView.setAdapter(adapter); }
10. 将数据以 GET 方式提交到网络上的 Web 应用。
** 布局[略]
** Service
public class VideoService { /** * 保存视频资讯(向服务器端发送请求实现保存) */ public static boolean save(String name, String timelength) throws Exception { String path = "http://10.4.74.20:8080/myvideoweb/video/manage.do "; Map params = new HashMap(); params.put("method", "save"); params.put("timelength", timelength); params.put("name", name); return sendGetRequest(path, params, "UTF-8"); } /** * 发出一个 可带参数的 GET 请求。 * @param params 参数键值 */ private static boolean sendGetRequest(String path, Map params, String encoding) throws Exception { StringBuilder pathWithParams = new StringBuilder(path); /* 组装参数 ?method=save,name=sss,timelength=20 */ pathWithParams.append("?"); for (Map.Entry param : params.entrySet()) { pathWithParams.append(param.getKey()).append("=") /* 应该要对参数进行 URL 编码, 避免中文乱码问题 */ .append(URLEncoder.encode(param.getValue(),encodin g)) .append("&"); } // 去掉后缀的多余的 '&'符号 if(pathWithParams.indexOf("&") != -1) { pathWithParams.deleteCharAt(pathWithParams.lastInd exOf("&")); } // 发送请求 URL url = new URL(pathWithParams.toString()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5*1000); conn.setRequestMethod("GET"); // 响应码为 200 表示响应成功 if(conn.getResponseCode() == 200) { return true; } return false; } }
** Activity
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); timelengthText = (EditText) findViewById(R.id.timelength); nameText = (EditText) findViewById(R.id.name); button = (Button) findViewById(R.id.button); // 为 button 添加点击事件 button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String name = nameText.getText().toString(); String timelength = timelengthText.getText().toString(); try { // 调用 Service 方法,向服务器发送数据 boolean status = VideoService.save(name, timelength); if(status) { Toast.makeText(MainActivity.this, R.string.success, Toast.LENGTH_LONG).show(); } else { Toast.makeText(MainActivity.this, R.string.error, Toast.LENGTH_LONG).show(); } } catch (Exception e) { e.printStackTrace(); } } }); }
11. 将数据以 post 方式提交到网络上的 Web 应用。
用 HTTP 协议,将 Android 的数据以 POST 的方式提交到服务器保存。
1. 观察浏览器 POST 方式提交数据时的 HTTP 协议内容:
/* 以下为请求头 */
POST /videoweb/video/manage.do HTTP/1.1 (: 请求方式(POST GET) 请求的路径 所使用的 HTTP 协议版本 )
(:此处无空行 -- 浏览器所能接受的数据类型,最后面的 */* 表示任意)
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost:8080/videoweb/ (: 请求来源的 URl -- 比如,用户通过点击百度搜索结果的链接进入我的网站,我想统计,那么就可以使用这个数据)
Accept-Language: zh-cn,en-us;q=0.5 (: 浏览器所接收的语言)
Content-Type: application/x-www-form-urlencoded (: 实体数据内容类型,x-www-form-urlencoded 为表单默认数据类型,是经过 URL 编码后的数据)
Accept-Encoding: gzip, deflate (: 表示浏览器可接受的数据类型)
(:此处无空行 -- 用于表示当前使用的浏览器和版本,可以在串中查找 MSIE 6.0 来判断浏览器是否是 IE 6.0)
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; staticlogin:product=cboxf2010&act=login&info=ZmlsZ W5hbWU9vfDJvbTKsNQyMDEwxaO98sbsvaKw5i5leGUmbWFjPUQ2 OTUzRkRBOUUxNzQyQkNBQUM4QjQ3MDYxMTZDRjdEJnBhc3Nwb3J 0PSZ2ZXJzaW9uPTIwMTAuNi4zLjYuMiZjcmFzaHR5cGU9MQ==&v erify=7dda578e3701edc6e428f5b6a6d955e2)
Host: 192.168.1.136:8080 (: 来源 IP)
Content-Length: 49 (: 实体数据的内容长度)
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=13360465242B5FA4827B2B19CAB4F673 (: 如果浏览器支持 Cookie 的话,会带有这个值。JSESSIONID 为服务器会话跟踪的一种机制)
(此处一定为空行)
/* 以下为实体数据 */
method=save&name=%E6%82%A8%E5%A5%BD&timelength=23
Post方式提交, HTTP协议所必须的头字段有两个:
Content-Type: application/x-www-form-urlencoded (: 实体数据内容类型,x-www-form-urlencoded 为表单默认数据类型,是经过 URL 编码后的数据)
Content-Length: 49 (: 实体数据的内容长度)
2. 在 客户端手动 POST 提交数据
Service:
/** * 发出一个POST请求 * @param path 请求的路径,不带参数 * @param params 参数 * @return * @throws Exception */ private static boolean sendPostRequest(String path, Map params, String encoding) throws Exception{ // 组装参数,其格式应该为: method=save&name=liming&timelength=78 StringBuilder sb = new StringBuilder(""); if(params!=null && !params.isEmpty()){ for(Map.Entry entry : params.entrySet()){ sb.append(entry.getKey()).append('=') // 防止中文乱码问题,对参数统一进行 URL 编码 .append(URLEncoder.encode(entry.getValue(), encoding)).append('&'); } // 截取字符串后缀的多余的 "&" if(sb.indexOf("&") != -1) { sb.deleteCharAt(sb.length() - 1); } } byte[] data = sb.toString().getBytes();//得到实体数据的二进制表示方式 URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("POST"); conn.setConnectTimeout(5*1000); //特别注意:通过 post 方式提交数据,必须要设置允许对外输出数据 conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", String.valueOf(data.length)); OutputStream outStream = conn.getOutputStream(); outStream.write(data); outStream.flush(); outStream.close(); // 相应码为 200 表示成功 if(conn.getResponseCode()==200){ return true; } return false; }
布局:
在界面输入文件的位置,点击上传
将这个文件保存在服务器上。
服务器就是一个普通的文件上传处理代码
MainActivity:
读取到界面的参数,调用 Service,传参
乱码问题: POST 方式提交数据发送乱码,就添加编码转换过滤器
Android 还集成了一个第三方开源项目: HttpClient。这个项目是 apache 组织开发的封装了 HTTP 相关操作的方法。
/** * 使用 HttpClient 完成数据 POST 方式提交到服务器, 不推荐使用这种方式。因为它是开源项目,里面会做很多本应用中没必要的封装,或多或少会影响效率。这对于手机平台,非常敏感。 */ private static boolean sendPostRequestHttpClient(String path, Map params, String encoding) throws Exception{ // 组装参数对象 List paramPairs = new ArrayList(); if(params!=null && !params.isEmpty()){ for(Map.Entry entry : params.entrySet()){ BasicNameValuePair param = new BasicNameValuePair(entry.getKey(), entry.getValue()); paramPairs.add(param); } } // 被 URL 编码后的实体数据对象 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramPairs, encoding); HttpPost post = new HttpPost(path); post.setEntity(entity); DefaultHttpClient client = new DefaultHttpClient();//浏览器 // org.apache.http.HttpResponse HttpResponse response = client.execute(post); // 获得状态行 response.getStatusLine(),再获取状态码.(状态码在状态行里面 -- 也就是第一行) if(response.getStatusLine().getStatusCode()==200){ return true; } return false; }
13. 通过 HTTP 协议实现文件上传
/* 定义分隔线 */
HTTP 的 ContentType=multipart/form-data;boundary=--------- ---7da31c29608b2
其实体数据是由分割线分隔开的参数
在使用分隔线的时候有些需要注意:
例如定义的分隔线为:---------------------7da31c29608b2
那么,在实体数据中使用它就变成了:-----------------------7da31c29608b2
多两杠
需要特别注意
并且最后一条分割线的最后还多了两条杠 : -----------------------7da31c29608b2--
这就表示实体数据结束了
其格式具体为:
---------分隔线(回车换行\r\n)
Content-Disposition: form-data; name="name"(回车换行\r\n)
(回车换行\r\n)
实体数据(回车换行\r\n)
---------分隔线(回车换行\r\n)
例:
POST /videoweb/video/manage.do HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost:8080/videoweb/
Accept-Language: zh-cn,en-us;q=0.5
Content-Type: multipart/form-data; boundary=---------------------------7da31cf110e5e
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; staticlogin:product=cboxf2010&act=login&info=ZmlsZ W5hbWU9vfDJvbTKsNQyMDEwxaO98sbsvaKw5i5leGUmbWFjPUQ2 OTUzRkRBOUUxNzQyQkNBQUM4QjQ3MDYxMTZDRjdEJnBhc3Nwb3J 0PSZ2ZXJzaW9uPTIwMTAuNi4zLjYuMiZjcmFzaHR5cGU9MQ==&v erify=7dda578e3701edc6e428f5b6a6d955e2)
Host: 192.168.1.136:8080
Content-Length: 580
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=B175C70051A38FAFF324580E59EB4457
-----------------------------7daab2e110e5e
Content-Disposition: form-data; name="method" (注释:这里有一个回车换行 )
(注释: 这里是一个空行,必须的,加上上一行末尾的回车换行,一共是两个回车换行)
save
-----------------------------7daab2e110e5e
Content-Disposition: form-data; name="name"
aaa
-----------------------------7daab2e110e5e
Content-Disposition: form-data; name="timelength"
33
-----------------------------7daab2e110e5e (注释:以下是文件路径)
Content-Disposition: form-data; name="video"; filename="C:\Documents and Settings\Administrator\妗
2010年12月12日
Android 从网络上获取数据
1. 普通 J2EE 代码下的网络编程
public class ImageTool { public static void main(String[] args) throws Exception { String path = "http://hiphotos.baidu.com/z%D6%DCy%D4%C6f%B7%E5/p ic/item/6ca59b1de32de389a5866972.jpg"; URL url = new URL(path); // GET 必须大写 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); // 超时 5秒 conn.setReadTimeout(5 * 1000); InputStream is = conn.getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while((len = is.read(buffer)) != -1 ) { bos.write(buffer, 0, len); } byte[] data = bos.toByteArray(); File file = new File("zhiling.jpg"); FileOutputStream fos = new FileOutputStream(file); fos.write(data); fos.close(); } }
2. 链接网络权限
3. ImageView : 显示图片的控件
imageView.setImageBitmap(Bitmap);
4. Bitmap : 位图
Bitmap bitmap = BitmapFactory.decodeByteArray(ByteArray, 0, ByteArray.length);
5.代码示例 : 获取网络上的一张图片,显示在 Android 手机上
1. AndroidManifest.xml -- 声明链接网络的权限
2. main.xml -- 布局
3. strings.xml -- 字符串定义
Hello World, ImageActivity! 图片查看器 图片路径 查看 获取失败
4. Service -- 获取网络上的图片的字节数组
package wjh.android.image.service; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class ImageService { public static byte[] catchImage(String path) throws Exception { URL url = new URL(path); // GET HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); // 超时 5秒 conn.setReadTimeout(5 * 1000); InputStream is = conn.getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while((len = is.read(buffer)) != -1 ) { bos.write(buffer, 0, len); } is.close(); bos.close(); return bos.toByteArray(); } }
5. Main Activity
public class ImageActivity extends Activity { private Button button; private EditText pathText; private ImageView imageView; private static final String TAG = "ImageActivity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); button = (Button) findViewById(R.id.button); imageView = (ImageView) findViewById(R.id.image); pathText = (EditText) findViewById(R.id.path); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String path = pathText.getText().toString(); try { // 获取图片数据 byte[] imageBytes = ImageService.catchImage(path); Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); imageView.setImageBitmap(bitmap); } catch (Exception e) { Log.i(TAG, e.toString()); Toast.makeText(ImageActivity.this, R.string.error, Toast.LENGTH_LONG); } } }); } }
6. 从网络上获取视频的最新资讯(从网络上获取 XML 数据)
① 新建 web project -- myvideoweb
本项目采用 struts 为视图层框架
** Service
public class VideoServiceImpl implements VideoService { /** * 获取最新视频资讯 */ public List getLastVideoMesasages() { List vms = new ArrayList(); vms.add(new VideoMessage(89, "喜羊羊与灰太狼全集", 67)); vms.add(new VideoMessage(12, "实拍舰载直升东海救援演习", 23)); vms.add(new VideoMessage(19, "喀麦隆VS荷兰", 90)); return vms; } }
** Action
public class VideoAction extends Action { private VideoService service = new VideoServiceImpl(); @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { List vms = service.getLastVideoMesasages(); request.setAttribute("videos", vms); return mapping.findForward("videoListPage"); } }
** View
${video.title} ${video.timeLength}
** 若访问 http://localhost:8080/myvideoweb/video/list.do,结果为:
- - 喜羊羊与灰太狼全集 67 - 实拍舰载直升东海救援演习 23 - 喀麦隆VS荷兰 90
② 新建 Android 项目
** Service
public class VideoService { /** * 解析从网络上获取到的 XML 文档。返回解析后的 VideoMessage 集合 */ public static List getLastVideos() throws Throwable { // 注意,这里不能写为 localhost 和 127.0.0.1,因为程序最终会在手机上运行。它们就代表手机本身的 ip 了。这里应该写成电脑的本机 IP Addr. String path = "http://10.4.74.20:8080/myvideoweb/video/list.do"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); InputStream is = conn.getInputStream(); return parseXML(is); } /** * 从指定输入流中解析出 VideoMessage 集合并返回 */ private static List parseXML(InputStream is) throws Exception { List videos = null; VideoMessage vs = null; XmlPullParser pullParser = Xml.newPullParser(); pullParser.setInput(is, "UTF-8"); int eventType = pullParser.getEventType(); // 触发第一个事件 while (eventType != XmlPullParser.END_DOCUMENT) { // 如果不为文档结束事件 switch (eventType) { case XmlPullParser.START_DOCUMENT: // 文档开始事件 videos = new ArrayList(); break; case XmlPullParser.START_TAG: // 标签开始事件 if ("video".equals(pullParser.getName())) { int id = Integer.parseInt(pullParser.getAttributeValue(0)); vs = new VideoMessage(); vs.setId(id); } if (vs != null) { if ("title".equals(pullParser.getName())) { vs.setTitle(pullParser.nextText()); } if ("timelength".equals(pullParser.getName())) { vs.setTimeLength(Integer.parseInt((pullParser .nextText()))); } } break; case XmlPullParser.END_TAG: // 标签结束事件 if ("video".equals(pullParser.getName())) { videos.add(vs); vs = null; } break; } eventType = pullParser.next(); } return videos; } }
③.Main Activity
public class ManiActivity extends Activity { private static final String TAG = "ManiActivity"; private ListView listView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); List vms = null; // 调用 Service try { vms = VideoService.getLastVideos(); } catch (Throwable e) { e.printStackTrace(); Log.e(TAG, e.toString()); Toast.makeText(this, R.string.error, Toast.LENGTH_LONG); } // 给 ListView 绑定显示数据 listView = (ListView) findViewById(R.id.listView); List> data = new ArrayList>(); for (VideoMessage vm : vms) { Map item = new HashMap(); item.put("title", vm.getTitle()); item.put("timelength", vm.getTimeLength()); data.add(item); } ListAdapter adapter = new SimpleAdapter(this, data, R.layout.item, new String[]{"title", "timelength"}, new int[]{R.id.title, R.id.timelength}); listView.setAdapter(adapter); } }
8 Android JSON
public void testJson() throws Throwable { // JSONArray 的构造参数,是一个 JSON 数组,数组中的每个元素都是一个标准的 JSON 数据。 JSONArray jsonArray = new JSONArray("[{name:'wjh', age:10, addr:'sc'}], [{name='java', age=15, addr='arr'}]"); Log.i(TAG, jsonArray.getJSONObject(0).getString("age")); // print:10 jsonArray = new JSONArray("[{name:'wjh', age:10, addr:'sc'},{name:'wjh2', age:102, addr:'sc2'}]"); Log.i(TAG, jsonArray.getJSONObject(1).getString("name")); // print:wjh2 }
9. 获取 json 格式的数据
**服务器端 Action
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { VideoForm formbean = (VideoForm)form; List videos = service.getLastVides(); // 当接收到 format=json 参数时执行 if 内部操作 if("json".equals(formbean.getFormat())){ //[{id:32,title:"喀麦隆VS荷兰",timelength:40},{id:32,title:"喀麦隆VS荷兰",timelength:40}] // 组装 json 格式的数据 StringBuilder json = new StringBuilder(); json.append('['); for(Video video : videos){ json.append('{'); json.append("id:").append(video.getId()).append(', '); json.append("title:\"").append(video.getTitle()).a ppend("\","); json.append("timelength:").append(video.getTimelen gth()); json.append('}'); json.append(','); } json.deleteCharAt(json.length()-1); json.append(']'); request.setAttribute("json", json); return mapping.findForward("json"); }else{ request.setAttribute("videos", videos); return mapping.findForward("video"); } }
** Android 客户端解析获取的 JSON 数据
Service :
public static List getLastVideosFromJSON() throws Throwable { String path = "http://10.4.74.20:8080/videoweb/video/list.do?for mat=json"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); InputStream is = conn.getInputStream(); // 从输入流中解析出 JSON 的字符串格式数据 byte[] bytes = ReadeStreamUtil.readStreamTobyteArr(is); String jsonStr = new String(bytes, "UTF-8"); // JSON 接收的参数必须是 数组格式的 JSON 字符串数据,例如 "[{name:'XXX',age:'21'},{name:'AAA',age:'222'},{}, {}]" JSONArray jsonArray = new JSONArray(jsonStr); List videos = new ArrayList(); for(int i=0; i vms = null; try { // XML 格式 //vms = VideoService.getLastVideos(); // JSON 格式 vms = VideoService.getLastVideosFromJSON(); } catch (Throwable e) { e.printStackTrace(); Log.e(TAG, e.toString()); Toast.makeText(this, R.string.error, Toast.LENGTH_LONG); } listView = (ListView) findViewById(R.id.listView); List> data = new ArrayList>(); for (VideoMessage vm : vms) { Map item = new HashMap(); item.put("title", vm.getTitle()); item.put("timelength", vm.getTimeLength()); data.add(item); } ListAdapter adapter = new SimpleAdapter(this, data, R.layout.item, new String[]{"title", "timelength"}, new int[]{R.id.title, R.id.timelength}); listView.setAdapter(adapter); }
10. 将数据以 GET 方式提交到网络上的 Web 应用。
** 布局[略]
** Service
public class VideoService { /** * 保存视频资讯(向服务器端发送请求实现保存) */ public static boolean save(String name, String timelength) throws Exception { String path = "http://10.4.74.20:8080/myvideoweb/video/manage.do "; Map params = new HashMap(); params.put("method", "save"); params.put("timelength", timelength); params.put("name", name); return sendGetRequest(path, params, "UTF-8"); } /** * 发出一个 可带参数的 GET 请求。 * @param params 参数键值 */ private static boolean sendGetRequest(String path, Map params, String encoding) throws Exception { StringBuilder pathWithParams = new StringBuilder(path); /* 组装参数 ?method=save,name=sss,timelength=20 */ pathWithParams.append("?"); for (Map.Entry param : params.entrySet()) { pathWithParams.append(param.getKey()).append("=") /* 应该要对参数进行 URL 编码, 避免中文乱码问题 */ .append(URLEncoder.encode(param.getValue(),encodin g)) .append("&"); } // 去掉后缀的多余的 '&'符号 if(pathWithParams.indexOf("&") != -1) { pathWithParams.deleteCharAt(pathWithParams.lastInd exOf("&")); } // 发送请求 URL url = new URL(pathWithParams.toString()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5*1000); conn.setRequestMethod("GET"); // 响应码为 200 表示响应成功 if(conn.getResponseCode() == 200) { return true; } return false; } }
** Activity
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); timelengthText = (EditText) findViewById(R.id.timelength); nameText = (EditText) findViewById(R.id.name); button = (Button) findViewById(R.id.button); // 为 button 添加点击事件 button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String name = nameText.getText().toString(); String timelength = timelengthText.getText().toString(); try { // 调用 Service 方法,向服务器发送数据 boolean status = VideoService.save(name, timelength); if(status) { Toast.makeText(MainActivity.this, R.string.success, Toast.LENGTH_LONG).show(); } else { Toast.makeText(MainActivity.this, R.string.error, Toast.LENGTH_LONG).show(); } } catch (Exception e) { e.printStackTrace(); } } }); }
11. 将数据以 post 方式提交到网络上的 Web 应用。
用 HTTP 协议,将 Android 的数据以 POST 的方式提交到服务器保存。
1. 观察浏览器 POST 方式提交数据时的 HTTP 协议内容:
/* 以下为请求头 */
POST /videoweb/video/manage.do HTTP/1.1 (: 请求方式(POST GET) 请求的路径 所使用的 HTTP 协议版本 )
(:此处无空行 -- 浏览器所能接受的数据类型,最后面的 */* 表示任意)
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost:8080/videoweb/ (: 请求来源的 URl -- 比如,用户通过点击百度搜索结果的链接进入我的网站,我想统计,那么就可以使用这个数据)
Accept-Language: zh-cn,en-us;q=0.5 (: 浏览器所接收的语言)
Content-Type: application/x-www-form-urlencoded (: 实体数据内容类型,x-www-form-urlencoded 为表单默认数据类型,是经过 URL 编码后的数据)
Accept-Encoding: gzip, deflate (: 表示浏览器可接受的数据类型)
(:此处无空行 -- 用于表示当前使用的浏览器和版本,可以在串中查找 MSIE 6.0 来判断浏览器是否是 IE 6.0)
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; staticlogin:product=cboxf2010&act=login&info=ZmlsZ W5hbWU9vfDJvbTKsNQyMDEwxaO98sbsvaKw5i5leGUmbWFjPUQ2 OTUzRkRBOUUxNzQyQkNBQUM4QjQ3MDYxMTZDRjdEJnBhc3Nwb3J 0PSZ2ZXJzaW9uPTIwMTAuNi4zLjYuMiZjcmFzaHR5cGU9MQ==&v erify=7dda578e3701edc6e428f5b6a6d955e2)
Host: 192.168.1.136:8080 (: 来源 IP)
Content-Length: 49 (: 实体数据的内容长度)
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=13360465242B5FA4827B2B19CAB4F673 (: 如果浏览器支持 Cookie 的话,会带有这个值。JSESSIONID 为服务器会话跟踪的一种机制)
(此处一定为空行)
/* 以下为实体数据 */
method=save&name=%E6%82%A8%E5%A5%BD&timelength=23
Post方式提交, HTTP协议所必须的头字段有两个:
Content-Type: application/x-www-form-urlencoded (: 实体数据内容类型,x-www-form-urlencoded 为表单默认数据类型,是经过 URL 编码后的数据)
Content-Length: 49 (: 实体数据的内容长度)
2. 在 客户端手动 POST 提交数据
Service:
/** * 发出一个POST请求 * @param path 请求的路径,不带参数 * @param params 参数 * @return * @throws Exception */ private static boolean sendPostRequest(String path, Map params, String encoding) throws Exception{ // 组装参数,其格式应该为: method=save&name=liming&timelength=78 StringBuilder sb = new StringBuilder(""); if(params!=null && !params.isEmpty()){ for(Map.Entry entry : params.entrySet()){ sb.append(entry.getKey()).append('=') // 防止中文乱码问题,对参数统一进行 URL 编码 .append(URLEncoder.encode(entry.getValue(), encoding)).append('&'); } // 截取字符串后缀的多余的 "&" if(sb.indexOf("&") != -1) { sb.deleteCharAt(sb.length() - 1); } } byte[] data = sb.toString().getBytes();//得到实体数据的二进制表示方式 URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("POST"); conn.setConnectTimeout(5*1000); //特别注意:通过 post 方式提交数据,必须要设置允许对外输出数据 conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", String.valueOf(data.length)); OutputStream outStream = conn.getOutputStream(); outStream.write(data); outStream.flush(); outStream.close(); // 相应码为 200 表示成功 if(conn.getResponseCode()==200){ return true; } return false; }
布局:
在界面输入文件的位置,点击上传
将这个文件保存在服务器上。
服务器就是一个普通的文件上传处理代码
MainActivity:
读取到界面的参数,调用 Service,传参
乱码问题: POST 方式提交数据发送乱码,就添加编码转换过滤器
Android 还集成了一个第三方开源项目: HttpClient。这个项目是 apache 组织开发的封装了 HTTP 相关操作的方法。
/** * 使用 HttpClient 完成数据 POST 方式提交到服务器, 不推荐使用这种方式。因为它是开源项目,里面会做很多本应用中没必要的封装,或多或少会影响效率。这对于手机平台,非常敏感。 */ private static boolean sendPostRequestHttpClient(String path, Map params, String encoding) throws Exception{ // 组装参数对象 List paramPairs = new ArrayList(); if(params!=null && !params.isEmpty()){ for(Map.Entry entry : params.entrySet()){ BasicNameValuePair param = new BasicNameValuePair(entry.getKey(), entry.getValue()); paramPairs.add(param); } } // 被 URL 编码后的实体数据对象 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramPairs, encoding); HttpPost post = new HttpPost(path); post.setEntity(entity); DefaultHttpClient client = new DefaultHttpClient();//浏览器 // org.apache.http.HttpResponse HttpResponse response = client.execute(post); // 获得状态行 response.getStatusLine(),再获取状态码.(状态码在状态行里面 -- 也就是第一行) if(response.getStatusLine().getStatusCode()==200){ return true; } return false; }
13. 通过 HTTP 协议实现文件上传
/* 定义分隔线 */
HTTP 的 ContentType=multipart/form-data;boundary=--------- ---7da31c29608b2
其实体数据是由分割线分隔开的参数
在使用分隔线的时候有些需要注意:
例如定义的分隔线为:---------------------7da31c29608b2
那么,在实体数据中使用它就变成了:-----------------------7da31c29608b2
多两杠
需要特别注意
并且最后一条分割线的最后还多了两条杠 : -----------------------7da31c29608b2--
这就表示实体数据结束了
其格式具体为:
---------分隔线(回车换行\r\n)
Content-Disposition: form-data; name="name"(回车换行\r\n)
(回车换行\r\n)
实体数据(回车换行\r\n)
---------分隔线(回车换行\r\n)
例:
POST /videoweb/video/manage.do HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost:8080/videoweb/
Accept-Language: zh-cn,en-us;q=0.5
Content-Type: multipart/form-data; boundary=---------------------------7da31cf110e5e
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; staticlogin:product=cboxf2010&act=login&info=ZmlsZ W5hbWU9vfDJvbTKsNQyMDEwxaO98sbsvaKw5i5leGUmbWFjPUQ2 OTUzRkRBOUUxNzQyQkNBQUM4QjQ3MDYxMTZDRjdEJnBhc3Nwb3J 0PSZ2ZXJzaW9uPTIwMTAuNi4zLjYuMiZjcmFzaHR5cGU9MQ==&v erify=7dda578e3701edc6e428f5b6a6d955e2)
Host: 192.168.1.136:8080
Content-Length: 580
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=B175C70051A38FAFF324580E59EB4457
-----------------------------7daab2e110e5e
Content-Disposition: form-data; name="method" (注释:这里有一个回车换行 )
(注释: 这里是一个空行,必须的,加上上一行末尾的回车换行,一共是两个回车换行)
save
-----------------------------7daab2e110e5e
Content-Disposition: form-data; name="name"
aaa
-----------------------------7daab2e110e5e
Content-Disposition: form-data; name="timelength"
33
-----------------------------7daab2e110e5e (注释:以下是文件路径)
Content-Disposition: form-data; name="video"; filename="C:\Documents and Settings\Administrator\妗