android学习笔记---64_ListView数据异步加载与AsyncTask

来源:互联网 发布:教师课件制作软件 编辑:程序博客网 时间:2024/05/07 09:25

2013/5/26
Java技术qq交流群:JavaDream:251572072
64_ListView数据异步加载与AsyncTask
-------------------------------------------
1.因为会在互联网上经常获取数据,所以如果采用同步数据加载的话,那么
  会严重影响性能,那样用户体验很差
-------------------------------------
2.这里举例说明如何采用异步加载数据
----------------------------------------
3.由于主线程(也可叫UI线程)负责处理用户输入事件(点击按钮、触摸屏幕、按键等),
  如果主线程被阻塞,应用就会报ANR错误。为了不阻塞主线程,
  我们需要在子线程中处理耗时的操作,在处理耗时操作的过程中,
  子线程可能需要更新UI控件的显示,由于UI控件的更新重绘是由主线程负责的,
  所以子线程需要通过Handler发送消息给主线程的消息队列,由运行在主线程的消息处理
  代码接收消息后更新UI控件的显示。
  采用线程+Handler实现异步处理时,当每次执行耗时操作都创建一条新线程进行处理,
  性能开销会比较大。另外,如果耗时操作执行的时间比较长,就有可能同时运行着许多线程,
  系统将不堪重负。为了提高性能,我们可以使用AsynTask实现异步处理,
  事实上其内部也是采用线程+Handler来实现异步处理的,只不过是其内部使用了线程池技术,
  有效的降低了线程创建数量及限定了同时运行的线程数。
 ------------------------------------------------------------
 4.当数据量比较大的时候,比如需要同时加载图片和声音等等的时候,最好把这些数据放到手机的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"/>
 -------------------------------------
 5.下面是使用AsyncTask类进行数据异步传输的所有源码
 ---------------------------------------------------
 a.创建android项目:DataAsyncLoad
   /DataAsyncLoad/src/com/credream/adapter/ContactAdapter.java
   package com.credream.adapter;

import java.io.File;
import java.util.List;

import com.credream.dataasyncload.R;
import com.credream.domain.Contact;
import com.credream.service.ContactService;

import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class ContactAdapter extends BaseAdapter {
 private List<Contact> data;
 // 6.指定的某个条目.
 private int listviewItem;
 private File cache;
 // 布局服务
 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>,第一个:指定输入类型,
  * 第二个用不上指定一个整形就行了,返回类型为uri
  * */
    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;
  }
 }
}
-------------------------------------------------------------------------
b./DataAsyncLoad/src/com/credream/dataasyncload/DataAsyncLoadActivity.java
  package com.credream.dataasyncload;

import java.io.File;
import java.util.List;

import com.credream.adapter.ContactAdapter;
import com.credream.domain.Contact;
import com.credream.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 DataAsyncLoadActivity 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(DataAsyncLoadActivity.this, (List<Contact>)msg.obj,
      R.layout.listview_item, cache));
  }  
 };
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.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();      
    }
// 当这个activity被销毁的时候就删除所有的缓存文件.
 @Override
 protected void onDestroy() {
  for(File file : cache.listFiles()){
   file.delete();
  }
  cache.delete();
  super.onDestroy();
 }
   
}
-----------------------------------------------------------
c./DataAsyncLoad/src/com/credream/domain/Contact.java
 package com.credream.domain;
/**联系人的entity
 * .*/
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(){}
}
-------------------------------------------------------
d./DataAsyncLoad/src/com/credream/service/ContactService.java
 package com.credream.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.credream.domain.Contact;
import com.credream.utils.MD5;

import android.net.Uri;
import android.util.Xml;

public class ContactService {

 /**
  * 获取联系人数据
  * @return
  */
 public static List<Contact> getContacts() throws Exception{
  //1.确定请求的url
  String path = "http://192.168.1.100:8080/web/list.xml";
        // 2.把路径包装成url
  HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection();
        // 3.设置超时时间
  conn.setConnectTimeout(5000);
  // 4.设置请求方式
  conn.setRequestMethod("GET");
  // 5.判断是否请求成功.
  if(conn.getResponseCode() == 200){
   // 6.解析xml数据.
   return parseXML(conn.getInputStream());
  }
  return null;
 }

 private static List<Contact> parseXML(InputStream xml) throws Exception{
  // 7.解析xml的方法.
  List<Contact> contacts = new ArrayList<Contact>();
  Contact contact = null;
  // 8.用pull解析
  XmlPullParser pullParser = Xml.newPullParser();
  // 9.设置解析的编码
  pullParser.setInput(xml, "UTF-8");
  // 10.得到解析的类型.比如开始结束
  int event = pullParser.getEventType();
  // 11.只要不是文档结尾就继续解析
  while(event != XmlPullParser.END_DOCUMENT){
   switch (event) {
   // 12.首先关心开始事件.
   case XmlPullParser.START_TAG:
    // 13.判断开始元素是否为contact(联系人).
    // 取得节点的名字pullParser.getName()
    if("contact".equals(pullParser.getName())){
     contact = new Contact();
     // 14.得到整形id
     /*
      * 这里xml的形式是这样的:
      * <contacts>
      * <contact id="1">
      * <name>张飞</name>
      * <image src="http://192.168.1.100:8080/web/images/1.gif"
      * </contact>
      * <contact id="2">
      * <name>德伟</name>
      * <image src="http://192.168.1.100:8080/web/images/1.gif"
      * </contact>
      *
      * </contacts>
      * */
     contact.id = new Integer(pullParser.getAttributeValue(0));
    }else if("name".equals(pullParser.getName())){
     // 这里new 完contact对象后,直接给对应的属性进行赋值.
     contact.name = pullParser.nextText();
    }else if("image".equals(pullParser.getName())){
     contact.image = pullParser.getAttributeValue(0);
    }
    break;
   /*1.注意:pull解析器的判断方式<>为START_TAG,</>为END_TAG
    * */
   case XmlPullParser.END_TAG:
    if("contact".equals(pullParser.getName())){
     contacts.add(contact);
     // 1.第一个联系人添加完成,然后初始化联系人.这里不初始化也可以
     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;
 }
}
------------------------------------------------------------------
e./DataAsyncLoad/src/com/credream/utils/MD5.java
  package com.credream.utils;

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();
    }
}
--------------------------------------------------------------------
f./DataAsyncLoad/res/layout/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:layout_width="120dp"
        android:layout_height="120dp"
        android:id="@+id/imageView"
        />
<!-- 用来显示照片下面的文字 -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textColor="#FFFFFF"
        android:id="@+id/textView"       
        />
</LinearLayout>
------------------------------------------------------------
g./DataAsyncLoad/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" >
    <!-- 这里给listview又从新建了一个条目文件 -->
    <ListView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/listView"/>


</LinearLayout>
-------------------------------------------------------------
h./DataAsyncLoad/AndroidManifest.xml
  <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.credream.dataasyncload"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".DataAsyncLoadActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
  <!-- 访问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"/>
</manifest>
--------------------------------------------------------------------
2013/5/30
Java技术qq交流群:JavaDream:251572072
65_ListView数据的分批加载
-------------------------------------------
数据的分批加载:
-------------------
下面是一个数据分批加载的实例源码:
a.新建android项目:datapageload
b./datapageload/src/com/credream/pageload/DatapageloadActivity.java
package com.credream.pageload;

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

import com.credream.service.DataService;


import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class DatapageloadActivity extends Activity {
    private ListView listView;
    private List<String> data = new ArrayList<String>();
    ArrayAdapter<String> adapter;
    View footer;
    // 注意运行的时候,注意看日志:onScroll(firstVisibleItem=0,visibleItemCount=0,totalItemCount=0)
    // onScroll(firstVisibleItem=0,visibleItemCount=18,totalItemCount=20)
    // 20代表条目总数,18代表每一屏显示的条目数.0代表目前条目的一个索引值.
    /*onScrollStateChanged(scrollState=1)
     * 这里状态scrollState为1的时候代表用户的手指在屏幕上滑动
     * scrollState为2的时候代表,用户已经停止了手指在屏幕上的滑动,
     * 由于惯性屏幕中的条目继续滑动.
     * scrollState为0.那么listview已经停止了滚动回到了空闲状态.
     *这里可以使用这样的方法判断是否到了最后一条:使用条目滚动的最后一条的id+1是否等于
     *条目的总数,就可以判断条目是否已经为所有的条目了.
     * */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // 这里通过获取布局服务getLayoutInflater()来给footer这个view
        // 赋值上界面文件和
        /*
         * resource  ID for an XML layout resource to load (e.g., R.layout.main_page)
           root  Optional view to be the parent of the generated hierarchy.
         * */
        footer = getLayoutInflater().inflate(R.layout.footer, null);
        // 找到这个listview
        listView = (ListView) this.findViewById(R.id.listView);
        // 当滚动listview的时候,会触发这个事件.
        listView.setOnScrollListener(new ScrollListener());
        // 取得20条数据.
        data.addAll(DataService.getData(0, 20));       
        //  适配器,这里用的是数据适配器对象.this, R.layout.listview_item, R.id.textView, data);
        // 第一个上下文对象,第二个条目文件id,R.id.textView这个是在数据中取得每个条目数据然后加载到R.id.textView这个控件中.
        adapter = new ArrayAdapter<String>(this, R.layout.listview_item, R.id.textView, data);
        // 这里可以看到footerView是一个view对象,所以要给他设置一个xml界面文件.
        // 添加页脚一定要在适配器之前,setAdapter()这个方法.
        listView.addFooterView(footer);
        //添加页脚(放在ListView最后),添加页脚一定要在适配器之前.
        /*添加页脚一定要在适配器之前,这是因为在setAdapter内部,首先会判断这个页脚和页眉是否为空,如果为空的话就
         * 跳过添加页脚页眉的那个部分,否则不为null,就会重新建一个适配器,并且把页脚页眉加到原来的适配器中
         * 利用新的适配器(含有页眉页脚的再去执行下面的代码),所以如果把listView.addFooterView(footer);
         * 写在listView.setAdapter(adapter);之后就没办法在添加页脚页眉了,因为这样的话,执行的就不是添加
         * 完页脚页眉的适配器对象,而是没有添加页脚页眉的适配器对象.
         *   listView.addFooterView(footer);listView.removeFooterView(footer);
         *   这里添加这两句的代码,作用是:使得适配器是被包装过的适配器,也就是让原来的适配器具备添加页脚的功能.
         * */
        // 将给listview设置适配器.
        listView.setAdapter(adapter);
        // 这里再把页脚移除,因为只有加载数据的是才会添加页脚.
        listView.removeFooterView(footer);
    }
    private int number = 20;//每次获取多少条数据
    private int maxpage = 5;//总共有多少页
    // 这个变量用来指示,数据有没有加载完成,也就是onScoll是否执行完成.
    private boolean loadfinish = true;
    // 定义这个类,然后用来响应,listview的滚动事件.
    private final class ScrollListener implements OnScrollListener{
  public void onScrollStateChanged(AbsListView view, int scrollState) {
   // 测试这两个方法调用的时机.
   Log.i("DatapageloadActivity", "onScrollStateChanged(scrollState="+ scrollState+ ")");
  }
  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
   // 打印参数,这里打印是为了判断各个方法执行的时机
   Log.i("DatapageloadActivity", "onScroll(firstVisibleItem="+ firstVisibleItem+ ",visibleItemCount="+
     visibleItemCount+ ",totalItemCount="+ totalItemCount+ ")");
   // 在内部类中要使用这个变量要声明为final的.
   final int loadtotal = totalItemCount;
   int lastItemid = listView.getLastVisiblePosition();//获取当前屏幕最后Item的ID
  // 取得当前屏幕中条目的最后一条的id加1后判断是否为总条目数.
   if((lastItemid+1) == totalItemCount){//达到数据的最后一条记录
    // 这里需要当大于0的时候才进行处理,因为第一次的时候firstVisibleItem,totalItemCount为0,这时候
    // lastItemid为-1这时候(lastItemid+1) == totalItemCount条件成立
    // 就会执行int currentpage = totalItemCount%number == 0 ? totalItemCount/number : totalItemCount/number+1;
    // 这段代码,而刚刚开始的时候为0条的时候,可以得到currentpage为0,当为下一屏的时候,totalItemCount=20
    // 这时候currentpage还是为0所以就会出现第一次的时候不出现页脚的情况,因而加了这样的判断
    // if(totalItemCount > 0){只有totalItemCount大于0的时候才处理.
    if(totalItemCount > 0){
     // 取得当前页是第几页.
     int currentpage = totalItemCount%number == 0 ? totalItemCount/number : totalItemCount/number+1;
     int nextpage = currentpage + 1;//下一页
    // 下一页不能超过总条目5也,
     if(nextpage <= maxpage && loadfinish){
      // 在加载下一页数据的时候,loadfinish判断是否数据加载结束的变量应该为false指示
      // 下一页数据没有加载完成.
      loadfinish = false;
      // 这里加载数据最好使用异步加载,因为有可能数据加载时间比较长.
      // 在这里在加载数据的时候首先要显示页脚.
      listView.addFooterView(footer);
      new Thread(new Runnable() {      
       public void run() {
        try {
         // 假如数据是在网络中获取的那么获取的时间可能
         // 很长所以,这里模拟下让线程睡眠3秒钟.模拟睡眠时间.
         Thread.sleep(3000);
        } catch (InterruptedException e) {
         e.printStackTrace();
        }
        // 取得数据loadtotal,代表跳过已经加载的这些数据.number并且每次加载20条数据.
        List<String> result = DataService.getData(loadtotal, number);
        // 这里还是采用handler来实现数据异步.
        handler.sendMessage(handler.obtainMessage(100, result));
       }
      }).start();
     }  
    }
      
   }
  }
    }
    // 新建一个handler来实现数据的异步通信,给ui线程发送数据.
    Handler handler = new Handler(){
  public void handleMessage(Message msg) {
   // 这里把异步加载后的数据加载到data中.
   data.addAll((List<String>) msg.obj);
   // 调用适配器的api,告诉listview数据发生了改变.
   adapter.notifyDataSetChanged();//告诉ListView数据已经发生改变,要求ListView更新界面显示
   // 加这个判断目的是:判断这个listview有没有页脚,如果有就删掉.
   if(listView.getFooterViewsCount() > 0) {
    listView.removeFooterView(footer);
    }
   // 数据加载完成之后,这时候可以把这个标志变量loadfinish = true;
   loadfinish = true;
  }     
    };
   
}
-----------------------------------------------------------------
c./datapageload/src/com/credream/service/DataService.java
  package com.credream.service;

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

public class DataService {
// 这个方法用来取得数据,这个方法有分页功能.
 // 这里用 分页 limit ,0,20
 public static List<String> getData(int offset, int maxResult){//分页 limit 0,20
  List<String> data = new ArrayList<String>();
  // 这里有个循环.往集合中放入测试数据.
  for(int i=0 ; i < 20 ; i++){
   data.add("ListView数据的分批加载"+ i);
  }
  // 返回数据集合
  return data;
 }
}
--------------------------------------------------------------
d./datapageload/res/layout/footer.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页脚的界面文件
这里要求显示的是一个小的进度条,环形的进度条.
注意:这里要给环形进度条设定宽高.这样才能显示.
style="?android:attr/progressBarStyle"
这句就是指定了环形进度条的显示样式.这里用的中等大小的环形进度条.
-->
    <ProgressBar android:id="@+id/c81_forthBar"
        android:layout_width="50dp"
     android:layout_height="wrap_content"
         style="?android:attr/progressBarStyle" />
   <!-- 这个textview用来在环形进度条旁显示提示信息
   android:gravity="center_vertical"垂直居中显示.
   -->
    <TextView android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical"
    android:textSize="20sp"
    android:text="数据正在加载..."
    />
</LinearLayout>
---------------------------------------------------------
e./datapageload/res/layout/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="vertical" >
<!-- 定义listview的条目
这里只要求显示字符串就行.
-->
    <TextView
         android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:textSize="18sp"
    android:textColor="#FFFFFF"
        android:id="@+id/textView"
        />

</LinearLayout>
-----------------------------------------------------------
f./datapageload/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" >
<!-- 定义用于显示的listview -->
    <ListView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/listView" />

</LinearLayout>
----------------------------------------------------------