Android-搭建简单服务端+ListView异步加载数据
2014年5月6日
本篇博文带给大家的是教大家如何在MyEclipse中搭建一个服务端,并通过手机端与其通信,异步加载数据。
笔者使用的是MyEclipse,各位也可以直接用Eclipse创建Java Web项目,谷歌提供的ADT Bundle是不能创建Web项目,读者可以下载Eclipse For JaveEE Developer这个IDE。
下面来介绍如何在MyEclipse创建一个Web项目,并部署到Tomcat当中,关于Tomcat的配置笔者在这里就不多说了。
创建一个名为Test的Web项目,项目结构如下图所示:
我创建了一个images文件夹和list.xml文件,images文件夹存放的是一些图片,是我们下面要获取的,list.xml是一个xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <contacts> <contact id="1"> <name>青蛙1</name> <image src="http://192.192.8.233:8080/Test/images/1.gif" /> </contact> <contact id="2"> <name>青蛙2</name> <image src="http://192.192.8.233:8080/Test/images/2.gif" /> </contact> <contact id="3"> <name>青蛙3</name> <image src="http://192.192.8.233:8080/Test/images/3.gif" /> </contact> <contact id="4"> <name>青蛙4</name> <image src="http://192.192.8.233:8080/Test/images/4.gif" /> </contact> <contact id="5"> <name>青蛙5</name> <image src="http://192.192.8.233:8080/Test/images/5.gif" /> </contact> <contact id="6"> <name>青蛙6</name> <image src="http://192.192.8.233:8080/Test/images/6.gif" /> </contact> <contact id="7"> <name>青蛙7</name> <image src="http://192.192.8.233:8080/Test/images/7.gif" /> </contact> <contact id="8"> <name>青蛙8</name> <image src="http://192.192.8.233:8080/Test/images/8.gif" /> </contact> <contact id="9"> <name>青蛙9</name> <image src="http://192.192.8.233:8080/Test/images/9.gif" /> </contact> <contact id="10"> <name>青蛙10</name> <image src="http://192.192.8.233:8080/Test/images/10.gif" /> </contact> <contact id="11"> <name>青蛙11</name> <image src="http://192.192.8.233:8080/Test/images/11.gif" /> </contact> <contact id="12"> <name>青蛙12</name> <image src="http://192.192.8.233:8080/Test/images/12.gif" /> </contact> <contact id="13"> <name>青蛙13</name> <image src="http://192.192.8.233:8080/Test/images/13.gif" /> </contact> <contact id="14"> <name>青蛙14</name> <image src="http://192.192.8.233:8080/Test/images/14.gif" /> </contact> <contact id="15"> <name>青蛙15</name> <image src="http://192.192.8.233:8080/Test/images/15.gif" /> </contact> <contact id="16"> <name>青蛙16</name> <image src="http://192.192.8.233:8080/Test/images/16.gif" /> </contact> <contact id="17"> <name>青蛙17</name> <image src="http://192.192.8.233:8080/Test/images/17.gif" /> </contact> <contact id="18"> <name>青蛙18</name> <image src="http://192.192.8.233:8080/Test/images/18.gif" /> </contact> </contacts>
我们可以看到list.xml最外层是一个contacts标签,里面有多个子contact标签,每个子标签包含id、name和image内容,这就是我们下面要解析的内容对应每一个Contact对象。
这里要提一下,我们看到image标签,src是图片url地址,这个地址是我PC的IP地址,读者在测试的时候需要将这个IP地址改为你的PC的IP地址,如何得到?运行->cmd->ipconfig /all查看ipv4地址,就是你电脑的ip地址了。
创建好Web项目之后,我们在电脑上测试一下,在浏览器输入地址:
http://192.192.8.233:8080/Test/list.xml
看到以上内容,说明我们已经可以访问到我们的服务端了,下面我们就可以开发我们的客户端:
我这里创建了一个07_DataAsyncLoad的项目:
目录结构如下:
因为需要联网,在AndroidManifest.xml设置权限:
<!-- 联网权限 --> <uses-permission android:name="android.permission.INTERNET" />
根据服务端list.xml,我们需要定义一个实体类:
/07_DataAsyncLoad/src/com/wwj/domain/Contact.java
package com.wwj.domain; /** * 联系人实体类 * * @author wwj * */ public class Contact { public int id; public String name; public String image; public Contact(int id, String name, String image) { this.id = id; this.name = name; this.image = image; } public Contact() { } }
需要访问服务端并且解析xml文件,我们定义一个服务类:
/07_DataAsyncLoad/src/com/wwj/service/ContactService.java
package com.wwj.service; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.xmlpull.v1.XmlPullParser; import com.wwj.domain.Contact; import com.wwj.utils.MD5; import android.net.Uri; import android.util.Xml; public class ContactService { /** * 获取联系人 * @return */ public static List<Contact> getContacts() throws Exception{ // 服务器文件路径 String path = "http://192.192.8.233:8080/Test/list.xml"; HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection(); conn.setConnectTimeout(5000); //设置超时5秒 conn.setRequestMethod("GET"); //设置请求方式 if(conn.getResponseCode() == 200){ //连接成功返回码200 return parseXML(conn.getInputStream()); } return null; } /** * 利用pull解析器对xml文件进行解析 * @param xml * @return * @throws Exception */ private static List<Contact> parseXML(InputStream xml) throws Exception{ List<Contact> contacts = new ArrayList<Contact>(); Contact contact = null; XmlPullParser pullParser = Xml.newPullParser(); pullParser.setInput(xml, "UTF-8"); int event = pullParser.getEventType(); //取得开始文档语法 while(event != XmlPullParser.END_DOCUMENT){ //只要不等于文档结束事件,循环解析 switch (event) { case XmlPullParser.START_TAG: //开始标签 if("contact".equals(pullParser.getName())){ contact = new Contact(); contact.id = new Integer(pullParser.getAttributeValue(0)); }else if("name".equals(pullParser.getName())){ contact.name = pullParser.nextText(); //取得后面节点的文本值 }else if("image".equals(pullParser.getName())){ contact.image = pullParser.getAttributeValue(0); //取得第一个属性的值 } break; case XmlPullParser.END_TAG: //结束标签 if("contact".equals(pullParser.getName())){ contacts.add(contact); //将contact对象添加到集合中 contact = null; } break; } event = pullParser.next(); //去下一个标签 } return contacts; } /** * 获取网络图片,如果图片存在于缓存中,就返回该图片,否则从网络中加载该图片并缓存起来 * @param path 图片路径 * @return */ public static Uri getImage(String path, File cacheDir) throws Exception{// path -> MD5 ->32字符串.jpg File localFile = new File(cacheDir, MD5.getMD5(path)+ path.substring(path.lastIndexOf("."))); if(localFile.exists()){ return Uri.fromFile(localFile); }else{ HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); if(conn.getResponseCode() == 200){ FileOutputStream outStream = new FileOutputStream(localFile); InputStream inputStream = conn.getInputStream(); byte[] buffer = new byte[1024]; int len = 0; while( (len = inputStream.read(buffer)) != -1){ outStream.write(buffer, 0, len); } inputStream.close(); outStream.close(); return Uri.fromFile(localFile); } } return null; } }
上面代码已经很清楚的定义了获取服务端数据的方法,大致流程是这样的:传递一个网络路径path,通过URL打开连接,通过HttpURLConnection连接服务端,得到输入流,解析xml文件再获得数据。
上面代码获取网络图片,需要进行MD5加密计算,具体方法如下:
/07_DataAsyncLoad/src/com/wwj/utils/MD5.java
package com.wwj.utils; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MD5 { /** * MD5加密算法 * * @param content * @return */ public static String getMD5(String content) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(content.getBytes()); return getHashString(digest); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } /** * 获得哈希字符串 * * @param digest * @return */ private static String getHashString(MessageDigest digest) { StringBuilder builder = new StringBuilder(); for (byte b : digest.digest()) { builder.append(Integer.toHexString((b >> 4) & 0xf)); builder.append(Integer.toHexString(b & 0xf)); } return builder.toString(); } }
好,这样我们的服务类就已经写完了,这时我们在MainActivity进行异步加载数据:
/07_DataAsyncLoad/src/com/wwj/asyntask/MainActivity.java
package com.wwj.asyntask; import java.io.File; import java.util.List; import com.wwj.adapter.ContactAdapter; import com.wwj.asyntask.R; import com.wwj.domain.Contact; import com.wwj.service.ContactService; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.widget.ListView; public class MainActivity extends Activity { ListView listView; File cache; // 缓存文件 Handler handler = new Handler() { public void handleMessage(Message msg) { listView.setAdapter(new ContactAdapter(MainActivity.this, (List<Contact>) msg.obj, R.layout.listview_item, cache)); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) this.findViewById(R.id.listView); cache = new File(Environment.getExternalStorageDirectory(), "cache"); // 实例化缓存文件 if (!cache.exists()) cache.mkdirs(); // 如果文件不存在,创建 // 开一个线程来加载数据 new Thread(new Runnable() { public void run() { try { List<Contact> data = ContactService.getContacts(); // 通过handler来发送消息 handler.sendMessage(handler.obtainMessage(22, data)); } catch (Exception e) { e.printStackTrace(); } } }).start(); } @Override protected void onDestroy() { // 删除缓存 for (File file : cache.listFiles()) { file.delete(); } cache.delete(); super.onDestroy(); } }
这里我们开了一个线程来加载数据,是因为网络操作不能在UI线程中进行,加载完数据后通过Hanlder发送消息,显示列表。
一般情况下,我们获取图片需要另外处理,我们有很多种方法,最常用的就是Handler+Thread和AsyncTask两种,具体实现来看:
/07_DataAsyncLoad/src/com/wwj/adapter/ContactAdapter.java
我们定义了一个列表适配器,用来填充我们的数据,我们的图片异步加载也在这里实现了:
package com.wwj.adapter; import java.io.File; import java.util.List; import android.content.Context; import android.net.Uri; import android.os.AsyncTask; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import com.wwj.asyntask.R; import com.wwj.domain.Contact; import com.wwj.service.ContactService; /** * 自定义适配器 * * @author wwj * */ public class ContactAdapter extends BaseAdapter { private List<Contact> data; // 缓存数据 private int listviewItem; // 条目id private File cache; // 缓存文件 LayoutInflater layoutInflater; public ContactAdapter(Context context, List<Contact> data, int listviewItem, File cache) { this.data = data; this.listviewItem = listviewItem; this.cache = cache; layoutInflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE);// 获取布局填充服务 } /** * 得到数据的总数 */ public int getCount() { return data.size(); } /** * 根据数据索引得到集合所对应的数据 */ public Object getItem(int position) { return data.get(position); } public long getItemId(int position) { return position; } /** * 当listView每显示一个条目的时候,都会调用这个方法 */ public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView = null; TextView textView = null; if (convertView == null) { convertView = layoutInflater.inflate(listviewItem, null); // 获取条目的view对象 imageView = (ImageView) convertView.findViewById(R.id.imageView); textView = (TextView) convertView.findViewById(R.id.textView); convertView.setTag(new DataWrapper(imageView, textView)); } else { DataWrapper dataWrapper = (DataWrapper) convertView.getTag(); imageView = dataWrapper.imageView; textView = dataWrapper.textView; } Contact contact = data.get(position); textView.setText(contact.name); asyncImageLoad(imageView, contact.image); return convertView; } private void asyncImageLoad(ImageView imageView, String path) { AsyncImageTask asyncImageTask = new AsyncImageTask(imageView); asyncImageTask.execute(path); } /** * 使用AsyncTask异步加载图片 * * @author Administrator * */ private final class AsyncImageTask extends AsyncTask<String, Integer, Uri> { private ImageView imageView; public AsyncImageTask(ImageView imageView) { this.imageView = imageView; } protected Uri doInBackground(String... params) {// 子线程中执行的 try { return ContactService.getImage(params[0], cache); } catch (Exception e) { e.printStackTrace(); } return null; } protected void onPostExecute(Uri result) {// 运行在主线程 if (result != null && imageView != null) imageView.setImageURI(result); } } // 使用Handler进行异步加载图片 /* * private void asyncImageLoad(final ImageView imageView, final String path) * { * final Handler handler = new Handler(){ * public void * handleMessage(Message msg) {//运行在主线程中 * Uri uri = (Uri)msg.obj; * if(uri!=null && imageView!= null) imageView.setImageURI(uri); * } * }; * * Runnable runnable = new Runnable() { * public void run() { * try { * Uri uri = * ContactService.getImage(path, cache); * handler.sendMessage(handler.obtainMessage(10, uri)); * } catch (Exception e) { * e.printStackTrace(); * } * } * }; * new Thread(runnable).start(); * } */ private final class DataWrapper { public ImageView imageView; public TextView textView; public DataWrapper(ImageView imageView, TextView textView) { this.imageView = imageView; this.textView = textView; } } }
以上就是本项目所有的代码,运行项目效果如下:
最后附上服务端和客户端源码:http://download.csdn.net/detail/wwj_748/7300567