一、界面分页加载、滚动加载
ListView实现像Android Market那样分页加载、滚动加载,需要这个ListView添加滚动条监听事件OnScrollListener,使用ListView的OnScroll方法来实现。如下参考代码。
public class ListViewScroll extends Activity {
ListView listView ;
private int lastItem = 0;
private listViewAdapter adapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
listView = (ListView) findViewById(R.id.myList);
adapter = new listViewAdapter();
listView.setAdapter(adapter);
listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
lastItem = firstVisibleItem + visibleItemCount - 1;
Log.i("test" , "Scroll>>>lastItem:" + lastItem);
//显示50条ListItem,即0-49,因为onScroll是在“滑动”执行过之后才触发,所以用adapter.count<=41作条件
int scrolllength=101;
if (adapter.count<=scrolllength) {
if (firstVisibleItem+visibleItemCount==totalItemCount) {
adapter.count += 10;
adapter.notifyDataSetChanged();
listView.setSelection(lastItem);
int currentPage=adapter.count/10;
Toast.makeText(getApplicationContext(), "第"+currentPage+"页", Toast.LENGTH_LONG).show();
}
}
else {
listView.removeFooterView(loadingLayout);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
});
}
class listViewAdapter extends BaseAdapter {
int count = 10; /* starting amount */
public int getCount() {
return count;
}
public Object getItem(int pos) {
return pos;
}
public long getItemId(int pos) {
return pos;
}
public View getView(int pos, View v, ViewGroup p) {
Log.i("test", "getView>>>pos:" + pos);
TextView view;
if (v == null) {
view = new TextView(ListViewScroll.this);
} else {
view = (TextView) v;
}
view.setText("ListItem " + pos);
view.setTextSize(20f);
view.setGravity(Gravity.CENTER);
view.setHeight(60);
return view;
}
}
}
main.xml
view plaincopy to clipboardprint?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView android:cacheColorHint="#00000000" android:id="@+id/myList"
android:layout_width="fill_parent" android:layout_height="fill_parent"
>
</ListView>
</LinearLayout>
二、异步加载图片
ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,不用让用户等待下去,下面就说实现方法,先贴上主方法的代码:
public class AsyncImageLoader {
private HashMap<String, SoftReference<Drawable>> imageCache;
public AsyncImageLoader() {
imageCache = new HashMap<String, SoftReference<Drawable>>();
}
public Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) {
if (imageCache.containsKey(imageUrl)) {
SoftReference<Drawable> softReference = imageCache.get(imageUrl);
Drawable drawable = softReference.get();
if (drawable != null) {
return drawable;
}
}
final Handler handler = new Handler() {
public void handleMessage(Message message) {
imageCallback.imageLoaded((Drawable) message.obj, imageUrl);
}
};
new Thread() {
@Override
public void run() {
Drawable drawable = loadImageFromUrl(imageUrl);
imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
Message message = handler.obtainMessage(0, drawable);
handler.sendMessage(message);
}
}.start();
return null;
}
public static Drawable loadImageFromUrl(String url) {
URL m;
InputStream i = null;
try {
m = new URL(url);
i = (InputStream) m.getContent();
} catch (MalformedURLException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Drawable d = Drawable.createFromStream(i, "src");
return d;
}
public interface ImageCallback {
public void imageLoaded(Drawable imageDrawable, String imageUrl);
}
}
以上代码是实现异步获取图片的主方法,SoftReference是软引用,是为了更好的为了系统回收变量,重复的URL直接返回已有的资源,实现回调函数,让数据成功后,更新到UI线程。
几个辅助类文件:
public class ImageAndText {
private String imageUrl;
private String text;
public ImageAndText(String imageUrl, String text) {
this.imageUrl = imageUrl;
this.text = text;
}
public String getImageUrl() {
return imageUrl;
}
public String getText() {
return text;
}
}
public class ViewCache {
private View baseView;
private TextView textView;
private ImageView imageView;
public ViewCache(View baseView) {
this.baseView = baseView;
}
public TextView getTextView() {
if (textView == null) {
textView = (TextView) baseView.findViewById(R.id.text);
}
return textView;
}
public ImageView getImageView() {
if (imageView == null) {
imageView = (ImageView) baseView.findViewById(R.id.image);
}
return imageView;
}
}
public class ImageAndTextListAdapter extends ArrayAdapter<ImageAndText> {
private ListView listView;
private AsyncImageLoader asyncImageLoader;
public ImageAndTextListAdapter(Activity activity, List<ImageAndText> imageAndTexts, ListView listView) {
super(activity, 0, imageAndTexts);
this.listView = listView;
asyncImageLoader = new AsyncImageLoader();
}
public View getView(int position, View convertView, ViewGroup parent) {
Activity activity = (Activity) getContext();
// Inflate the views from XML
View rowView = convertView;
ViewCache viewCache;
if (rowView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
rowView = inflater.inflate(R.layout.image_and_text_row, null);
viewCache = new ViewCache(rowView);
rowView.setTag(viewCache);
} else {
viewCache = (ViewCache) rowView.getTag();
}
ImageAndText imageAndText = getItem(position);
// Load the image and set it on the ImageView
String imageUrl = imageAndText.getImageUrl();
ImageView imageView = viewCache.getImageView();
imageView.setTag(imageUrl);
Drawable cachedImage = asyncImageLoader.loadDrawable(imageUrl, new ImageCallback() {
public void imageLoaded(Drawable imageDrawable, String imageUrl) {
ImageView imageViewByTag = (ImageView) listView.findViewWithTag(imageUrl);
if (imageViewByTag != null) {
imageViewByTag.setImageDrawable(imageDrawable);
}
}
});
if (cachedImage == null) {
imageView.setImageResource(R.drawable.default_image);
}else{
imageView.setImageDrawable(cachedImage);
}
// Set the text on the TextView
TextView textView = viewCache.getTextView();
textView.setText(imageAndText.getText());
return rowView;
}
}
ImageAndTextListAdapter是实现ListView的Adapter,里面有个技巧就是imageView.setTag(imageUrl),setTag是存储数据的,这样是为了保证在回调函数时,listview去更新自己对应item,大家仔细阅读就知道了。
最后贴出布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
这里只是参考代码,其实我不赞成用SoftReference来做缓存,我也不赞成用Drawable drawable = loadImageFromUrl(imageUrl)方法来获取Drawable。这里是因为把整个图片直接放进缓存中,所以很容易会出现内存溢出的错误,所以才用SoftReference来做缓存。但是用SoftReference来做缓存的缺点是当内存要溢出的时候,它还是会删除我们的记录。所以我们只能减小缓存的值,我们可以考虑用Drawable drawable = Drawable.createFromPath(imgPath)
这样我们就可以在缓存中只是存放图片的路径,这样就不会轻易报内存溢出的错误了。所以缓存这里我们只需要有简单的HashMap就可以了。我做了一个简单的测试,在设定JVM允许5kb的内存空间情况下,添加十万条记录都不会内存溢出,这足够我们的视信V博使用的了。测试代码如下:
int MAXIMUM_CAPACITY = 100000;
HashMap<Object, Object> hm = new HashMap<Object, Object>();
for ( int i=0; i<(MAXIMUM_CAPACITY); i++ ) {
try {
System.out.println("i=" + i);
hm.put("13b51be48c662437c1909540c9390494+"+i, "/data/data/com.vms/files/5fe83748-32b8-45f7-b90b-42e42436edbc.jpg");
} catch ( Exception e ) {
e.printStackTrace();
} finally {
System.out.println("cm.size=" + hm.size());
}
}
System.gc();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Set<Map.Entry<Object, Object>> set = hm.entrySet();
for (Map.Entry<Object, Object> entry : set) {
System.out.println( entry.getKey() + "===" + entry.getValue());
}
三、多缓存技术
VMS2.0采用的是内存缓存HashMap和本机数据库存储的双缓存技术。取数据的流程是先向内存中查找记录,如果不存在,就接着向本机数据库中查找响应的记录,如果不存在只能想服务器下载。如下参考代码:
/**
* @param imgCallback回调接口
* @return返回内存中缓存的图像,第一次加载返回null
*/
public Drawable loadDrawable(final ImageCallback imgCallback) {
Log.i("ail->eTag", eTag);
Log.i("ail->fileName", fileName);
//如果eTag不存在
if ( eTag == null || "null".equals(eTag) || "".equals(eTag) ) {
return null;
} else if ( fileName == null || "null".equals(fileName) || "".equals(fileName) ) {
return null;
}
//如果缓存中存在对象
if (CacheManager.hasCache(eTag)) {
Log.i("has map cache", eTag);
Cache cache = CacheManager.getCacheInfo(eTag);
if (new File((String) cache.getValue()).exists()) {
return Drawable.createFromPath((String) cache.getValue());
} else {
CacheManager.clearOnly(eTag);
}
}
//如果数据库中存在对象
Cache cache = null;
if ((cache = sqlCache.hasSqlCache(eTag)) != null) {
// Cache cache = sqlCache.getCacheInfo(eTag);
Log.i("has sql cache", eTag);
File file = new File((String) cache.getValue());
if ( file.exists() && file.isFile() && file.length() > 0 ) {
executorService.submit(new Runnable(){
public void run() {
CacheManager.putCacheInfo(eTag, filePath, timeOut, false);
sqlCache.resetCacheTime(eTag);
}
});
return Drawable.createFromPath((String) cache.getValue());
} else {
sqlCache.clearCache(cache);
}
}
//如果不存在缓存,就开启一条线程去服务器下载图片
executorService.submit(new Runnable () {
public void run() {
try {
final Drawable drawable = vs.loadImgFromUrl(filePath, imgUrl);
if ( drawable != null ) { //存在图像才加入缓存
Cache cache = new Cache();
cache.setKey(eTag);
cache.setValue(filePath);
cache.setTimeOut(timeOut);
CacheManager.putCacheInfo(eTag, filePath, timeOut, false);
sqlCache.putCacheInfo(cache);
}
handler.post(new Runnable () {
public void run() {
imgCallback.imgLoaded(drawable, eTag);
}
});
} catch ( Exception e ) {
e.printStackTrace();
}
}
});
return null;
}
四、V博数据预读机制
考虑到手机网速的问题,所以在V博页面加载数据的时候需要预先向服务器下载数据存放到手机磁盘上。这个其实比较容易实现,我们只需要多加一条后台线程让它去向服务器下载需要的数据便可以。但是在读取文件和下载文件之间需要协调好,这里需要一个同步锁。如下代码:
/**下载vblog文件*/
public void downVbFile(File file) {
synchronized(lock) {
Log.i("downVbFile", "downVbFile");
this.vbFile = file;
this.unlock = true;
Log.i("downVbFile", unlock+"");
lock.notify();
}
}
/**获取下载的vblog文件*/
public File getVbFile() {
synchronized(lock) {
if ( false == unlock ) {
try {
Log.i("getVbFile", unlock+"");
lock.wait(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.i("getVbFile", unlock+"");
File file = this.vbFile;
this.vbFile = null;
this.unlock = false;
return file;
}
}
五、虽然目前VMS2.0的V博比1.0有了很大的提升,但是目前还是存在着数据量大的问题。从服务器上下载的数据量太大直接影响V博打开的速度,所以这是接下来需要研究的课题。目前从服务器下载数据是采用类似web service的xml数据格式。xml数据格式固然是非常方便,但是数据量太大。