Android学习笔记_36_ListView数据异步加载与AsyncTask
一、界面布局文件:
1、加入sdcard写入和网络权限:
<!-- 访问internet权限 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- 在SDCard中创建与删除文件权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <!-- 往SDCard写入数据权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2、listview_item.xml文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <!-- 这里给listview又从新建了一个条目文件 布局是水平布局.然后引入,ImageView控件. --> <ImageView android:id="@+id/imageView" android:layout_width="120dp" android:layout_height="120dp" /> <!-- 用来显示照片下面的文字 --> <TextView android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" > </TextView> </LinearLayout>
3、activity_main.xml文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <ListView android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/listView"/> </RelativeLayout>
二、后台代码实现:
1、将文件路径转换成32位字符串:
package com.example.service; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MD5 { 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; } 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(); } }
2、联系人实体类:
package com.example.service; 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() { } }
3、联系人业务类:
解析从服务端传过来的xml文件,获取联系人的图片,对图片进行缓存。
package com.example.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 android.annotation.SuppressLint; import android.net.Uri; import android.util.Xml; public class ContactService { /** * 获取联系人数据 * @return */ public static List<Contact> getContacts() throws Exception { String path = "http://192.168.8.102:8080/Simple/GetImageList"; HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); if (conn.getResponseCode() == 200) { return parseXML(conn.getInputStream()); } return null; } @SuppressLint("UseValueOf") 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 = null; } break; } event = pullParser.next(); } return contacts; } /** * 获取网络图片,如果图片存在于缓存中,就返回该图片,否则从网络中加载该图片并缓存起来 * @param path 图片路径 * @return */ public static Uri getImage(String path, File cacheDir) throws Exception { // path -> MD5 ->32字符串.jpg // 对路径进行MD5加密,过后就可以得到一个32位的字符串. // 得到这个字符串后加.jpg然后放到缓存路径中. // 根据缓存的路径得到缓存数据 // MD5.getMD5(path)+ path.substring(path.lastIndexOf(".") // 通过md5,从最后一个.开始得到.jpg等等. File localFile = new File(cacheDir, MD5.getMD5(path)+ path.substring(path.lastIndexOf("."))); // 在缓存文件夹中判断有没有这个缓存文件,也就是经过MD5加密后的.jpg文件 if (localFile.exists()) { // 判断如果存在这个缓存文件,就返回这个文件的uri对象 return Uri.fromFile(localFile); } else { // 如果缓存文件夹中没有这个文件,就在网络中获取这个图片缓存文件 HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); if (conn.getResponseCode() == 200) { // 往localFile中写数据 FileOutputStream outStream = new FileOutputStream(localFile); InputStream inputStream = conn.getInputStream(); byte[] buffer = new byte[1024]; int len = 0; // 通过输入流,从http中通过get获得的输入流充取得数据 while ((len = inputStream.read(buffer)) != -1) { // 输出流指定了输入的文件,把数据输入到指定的文件中 // new FileOutputStream(localFile); outStream.write(buffer, 0, len); } // 关闭这个流. inputStream.close(); outStream.close(); return Uri.fromFile(localFile); } } return null; } }
4、自定义联系人适配器:
这里加载图片时使用异步加载
package com.example.service; import java.io.File; import java.util.List; import com.example.listviewasync.R; 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; /** * 自定义适配器 * @author Administrator * */ public class ContactAdapter extends BaseAdapter { private List<Contact> data; // 6.指定的某个条目. private int listviewItem; private File cache; private LayoutInflater layoutInflater; // 1.这个构造器就是用来接收数据用的适配器 public ContactAdapter(Context context, List<Contact> data, int listviewItem, File cache) { // 2.接收的数据 this.data = data; // 7.条目是传递过来的. this.listviewItem = listviewItem; this.cache = cache; // 5.取得布局填充服务. layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } /** * 得到数据的总数 */ public int getCount() { return data.size(); } /** * 根据数据索引得到集合中所对应的指定的那个数据 */ public Object getItem(int position) { return data.get(position); } // 这是数据的id这里用条目的id public long getItemId(int position) { return position; } // 3.当listview每显示一个条目的时候就自动调用这个方法 // convertView ,注意listview可以对第一屏显示的内容(条目对象)进行缓存 // 当显示第二屏,第三屏的时候会使用缓存的数据.convertView就是缓存的第一屏数据. public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView = null; TextView textView = null; // 4.如果显示的是第一屏,也就是说convertView还没有进行缓存.就进行创建第一屏 if (convertView == null) { // 这里需要到布局服务,而布局服务需要上下文对象,所以这里在构造器中 // 加一个参数,上下文对象. // 可以看到通过布局服务,可以得到条目对象. // 当第一次显示的时候直接把得到的条目赋值给缓存对象. convertView = layoutInflater.inflate(listviewItem, null); // 每次要为imageView,textView控件赋值参数的时候 // 都要查找到这两个对象.这样是很消耗性能的,所以这里用内部类 // 对这两个对象进行了临时保存 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; } // 实现图片的异步加载版本2,这里使用AsyncTask这个异步类来实现.可以提升性能. private void asyncImageLoad(ImageView imageView, String path) { // a.创建这个对象把imageView传进去. AsyncImageTask asyncImageTask = new AsyncImageTask(imageView); // 异步处理数据。 // b.通过这个类的execute方法执行任务,在子线程中. asyncImageTask.execute(path); } // AsyncTask这个类是一个抽象类,要继承才能使用. // 这个类打开源码可以看到: /* * 定义了三个泛型参数,作为输入参数 * 1.首先会执行execute方法,执行execute方法的时候收先会执行onPreExecute方法.然后再通过线程池对象sExecute 来执 * 行sExecute.execute(mFuture)把工作交给线程池对象.然后线程池选择一条线程去工作. * 2.工作是在线程中执行的:可以看到:mtask先处理任务,然后回调done方法来进行取得结果,然后把结果返回给mFuture.这些都是在子线 * 程中的. * 3.然后把结果交给handler进行发送给主线程,这步是在主线程中执行的. * 4.onPreExecute方法在ui线程中调用 doinBackground方法在子线程中运行:执行任务,取得结果onPostExecute运行在ui * 线程,也就是主线程中,把结果交给消息处理器onProcessUpdate更新ui.比较适合用来构建进度条,在主线程中执行. */ /* * 这个类使用了线程池,AsyncTask这个异步类实现数据的异步传输. AsyncTask<String, Integer, * Uri>。
*第一个参数:指的是doInBackground输入类型参数
*第二个参数:指的是onProgressUpdate方法接收的参数,用于更新UI,可以在doInBackground方法里面调用
*第三个参数:是在子线程中执行doInBackground方法的返回值类型,和执行onPostExecute方法的接收参数,此时子线程已经执行完毕。 */ private final class AsyncImageTask extends AsyncTask<String, Integer, Uri> { private ImageView imageView; public AsyncImageTask(ImageView imageView) { // 设定参数 this.imageView = imageView; } // c.在子线程中运行这个方法,然后把结果给handler对象,这个handler运行在主线程中. // doInBackground这个方法在子线程中执行. protected Uri doInBackground(String... params) {// 子线程中执行的 // String... params这里是可变参数,params是变量名字. try { return ContactService.getImage(params[0], cache); } catch (Exception e) { e.printStackTrace(); } return null; } // d.更新控件. // 这个方法用来取得返回结果进行更新控件. protected void onPostExecute(Uri result) {// 运行在主线程 if (result != null && imageView != null) imageView.setImageURI(result); } } // -------------------------------- // 这里默认的线程最大线程数是128条. // 一.----------------------------------- /* * //3.asyncImageLoad如果调用这个方法实现的时候 每显示一个条目就会创建很多个线程所以这里当显示完图片后会创建 * 很多的线程.,加入数据10000条的话,那么这里就要创建10000个线程 这个结果很可怕,这里的方法是限定开启的线程数量.提高性能 * 限定最多运行的线程数量.这里就使用了AsyncTask这个类:其实 这个类也是通过handler+线程来实现数据的异步加载的,不同的是 * 这里的线程它使用的是线程池.线程池可以限定线程的数量.线程池和连接池的原理差不多的 * 正式因为AsyncTask类是使用Handler+Thread+线程池来实现的 就提升了性能,并且可以实现线程的重用. * //2.这个方法只实现了图片的异步加载,实际上数据的加载,也需要 使用异步的因为,比如,如果主线程在运行过程中阻塞了,这时候 * 还没有运行到数据加载的地方,那么就会出现无响应的错误. //1. 这个是asyncImageLoad,图片异步加载的第一个版本 private * void asyncImageLoad(final ImageView imageView, final String path) { // * 所以这里,这里采用匿名内部类实现,这时候handler也是在主线程中的 // 这里的handler主要作用是往主线程发送数据. final * Handler handler = new Handler(){ // 这个方法也是运行在主线程中. public void * handleMessage(Message msg) {//运行在主线程中 // 在这里进行消息的接收,这个消息是一个uri对象,可以被转换. * Uri uri = (Uri)msg.obj; if(uri!=null && imageView!= null) // * 在给imageView设置之前要判断. imageView.setImageURI(uri); } }; * * Runnable runnable = new Runnable() { public void run() { try { // * getImage方法,已经实现了图片的缓存和从网络中获取的功能. Uri uri = ContactService.getImage(path, * cache); // 因为在子线程中不能更新ui控件的值.所以需要采用hanler技术实现. // * 用handler发送消息,这里handler需要做成final的才可以在内部类中使用 // handler.obtainMessage(10, * uri),用这个方法获取消息,同时也可以快速 // 的创建一个消息出来.10:这个是消息的id,这里用不到随便定义了一个, uri * handler.sendMessage(handler.obtainMessage(10, uri)); } catch (Exception * e) { e.printStackTrace(); } } }; //// 启动这个runnable对象. new * Thread(runnable).start(); } */ // 这个内部类,用来提高性能, /* * // 每次要为imageView,textView控件赋值参数的时候 // 都要查找到这两个对象.这样是很消耗性能的. imageView = * (ImageView) convertView.findViewById(R.id.imageView); textView = * (TextView) convertView.findViewById(R.id.textView); */ // 对查找的对象进行包装. private final class DataWrapper { public ImageView imageView; public TextView textView; public DataWrapper(ImageView imageView, TextView textView) { this.imageView = imageView; this.textView = textView; } } }
5、activity后台代码:
package com.example.listviewasync; import java.io.File; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.view.Menu; import android.widget.ListView; import com.example.service.Contact; import com.example.service.ContactAdapter; import com.example.service.ContactService; public class MainActivity extends Activity { ListView listView; /** 放图片的缓存文件. */ File cache; // 为了实现数据和图片都是异步加载的,这里对数据也需要实现异步 // 1.新建一个handler对象. Handler handler = new Handler() { @SuppressWarnings("unchecked") public void handleMessage(Message msg) { // 2.给listview,指定适配器,这里采用自定义适配器. // (List<Contact>)msg.obj在这里就可以得到发送给主线程的数据消息了. // DataAsyncLoadActivity.this 用来明确指定访问外部类的对象. listView.setAdapter(new ContactAdapter(MainActivity.this, (List<Contact>) msg.obj, R.layout.listview_item, cache)); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 1.首先找到listview控件. listView = (ListView) this.findViewById(R.id.listView); // 2.初始化缓存文件. // Environment.getExternalStorageDirectory得到名字为cache的文件夹,如果在sd卡 // 中得不到名字为cache的文件夹就创建. cache = new File(Environment.getExternalStorageDirectory(), "cache"); // 如果cache文件夹不存在就创建. if (!cache.exists()) cache.mkdirs(); // 在这里开一个线程用来实现数据的异步加载. new Thread(new Runnable() { public void run() { try { // 3.从服务类中取得联系人的列表. List<Contact> data = ContactService.getContacts(); // 创建一个消息对象,把数据发给主线程. handler.sendMessage(handler.obtainMessage(22, data)); // 4.注意这里把ContactAdapter // 5.这里个个给创建的这个适配器进行传参数:这里 // new ContactAdapter(data,R.layout.listview_item,cache) // 第一个:是要放到listview中的数据,第二个:listview需要的布局文件 // 第三个:是缓存文件的目录. // listView.setAdapter(new // ContactAdapter(data,R.layout.listview_item,cache)); } catch (Exception e) { e.printStackTrace(); } } }).start(); } @Override protected void onDestroy() { for (File file : cache.listFiles()) { file.delete(); } cache.delete(); super.onDestroy(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } }
三、服务端创建联系人,以xml形式发送给客户端:
public class GetImageList extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/xml; charset=UTF-8"); PrintWriter pw = response.getWriter(); StringBuffer sBuffer =new StringBuffer("<?xml version='1.0' encoding='utf-8'?>"); sBuffer.append("<contacts>"); Random random = new Random(); int count=1000; for (int i = 1; i <= 100; i++) { int img = random.nextInt(7)+1; count++; sBuffer.append("<contact id='"+count+"'>"); sBuffer.append("<name>tom"+i+"</name>"); sBuffer.append("<image src='http://192.168.8.102:8080/Simple/image/"+img+".png'></image>"); sBuffer.append("</contact>"); } sBuffer.append("</contacts>"); pw.write(sBuffer.toString()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }