最近想鼓捣一下CSDN客户端,这篇博客主要介绍如何使用Jsoup解析html页面通过标签获取所需内容,并下载指定图片资源。
一、导入Jsoup JAR包
JAR包下载地址:jsoup 1.6.1
注意导入包到项目时,直接将解压后的jar文件全部复制到libs文件目录下即可,否则运行时会报错。
二、下载html页面并解析
代码:
package com.example.testcsdn;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import android.util.Log;
/**
* 通过给定链接地址,解析获取的html资源,返回封装好的ArrayList<Blog>对象
*/
public class BlogsFetchr {
private static final String TAG = "BlogsFetchr";
/**
* 下载URL指定的资源
*
* @return 返回为类型byte[]
* */
public byte[] getUrlBytes(String urlSpec) throws IOException {
URL url = new URL(urlSpec);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 这里强制转换,是因为下面要用到HttpURLConnection.getInputStream
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = conn.getInputStream();
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
// 连接不成功
Log.i(TAG, "连接不成功");
return null;
}
byte[] buffer = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
out.close();
return out.toByteArray();
} finally {
conn.disconnect();
}
}
/**
* 下载URL指定的资源(即将getUrlBytes方法的返回值byte[]转换成String类型)
*
* @return 返回类型为String
*/
private String getUrl(String urlSpec) {
String result = null;
try {
result = new String(getUrlBytes(urlSpec));
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
public ArrayList<Blog> downloadBlogItems(String urlSpec) {
ArrayList<Blog> blogs = new ArrayList<>();
String htmlString = getUrl(urlSpec);
// 解析htmlString
parserItems(blogs, htmlString);
return blogs;
}
private void parserItems(ArrayList<Blog> blogs, String htmlString) {
Document doc = Jsoup.parse(htmlString);
Elements units = doc.getElementsByClass("blog_list");
for (int i = 0; i < units.size(); i++) {
Blog blog = new Blog();
Element unit_ele = units.get(i);
Element dl_ele = unit_ele.getElementsByTag("dl").get(0);
Element dl_dt_ele = dl_ele.getElementsByTag("dt").get(0);
Element dt_a_ele = dl_dt_ele.child(0);
String iconUrl = dt_a_ele.child(0).attr("src"); // 博主头像链接
Log.i(TAG, "文章" + i + "的博主头像链接:" + iconUrl);
Elements fls = unit_ele.getElementsByClass("fl");
Element fl_ele = fls.get(0);
Element fl_a1_ele = fl_ele.child(0);
String bloggerId = fl_a1_ele.text(); // 博主Id
Log.i(TAG, "文章" + i + "的作者:" + bloggerId);
blog.setBloggerIconUrl(iconUrl);
blog.setBloggerId(bloggerId);
blogs.add(blog);
}
}
}
如代码所示,使用Jsoup解析html十分简单。
可以使用浏览器,右键审查元素,得到下图所示的工具框,可以很快的找到页面中元素所对应的标签,再使用Jsoup API获取标签的值。
如果想要下载博客列表中子项,博主的头像。可以先通过解析html获取图片的url,然后再使用HttpURLConnection直接下载。
下面创建一个ThumbnailDownloader<Token>类,继承HandlerThread,用于等待并处理图片下载请求,同时更新UI:
package com.example.testcsdn;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.widget.ImageView;
public class ThumbnailDownloader<Token> extends HandlerThread {
// Token表示泛型,"类名<泛型>"以保证在类内可以使用Token,就像Token已经是定义好的类一样
private static final String TAG = "ThumbnailDownloader";
private static final int MESSAGE_DOWNLOAD = 0;
private Handler mHandler; // 发送下载图片的指令,和处理下载图片的指令的使者
private Handler mResponseHandler; // 来自主线程的Handler,更新UI
private Listener<Token> mListener;
private Map<Token, String> requestMap = Collections
.synchronizedMap(new HashMap<Token, String>());
// 保存ImageView和URL的键值对,并是线程安全的
private LruCache<String, Bitmap> mMemoryCache;
// 缓存图片的类,当存储图片的大小大于LruCache设定的值,系统自动释放内存
public ThumbnailDownloader(Handler handler) {
super(TAG);
mResponseHandler = handler;
// 创建一个名为TAG的HandlerThread,是拥有自己Looper的独立线程
// super(TAG) 相当于new HandlerThread(TAG)
int maxMemory = (int) Runtime.getRuntime().maxMemory(); // 系统最大运行内存
int mCacheSize = maxMemory / 8; // 分配给缓存的内存大小
mMemoryCache = new LruCache<String, Bitmap>(mCacheSize) {
// 必须重写此方法,来测量Bitmap的大小
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
public interface Listener<Token> { // 回调方法,在主线程中实现
void onThumbnailDownloaded(Token token, Bitmap thumbnail);
}
public void setListener(Listener<Token> listener) {
mListener = listener;
}
@Override
public void onLooperPrepared() {
// 在此线程的Looper启动循环准备时段运行的方法
mHandler = new Handler() { // 在当前线程新建的Handler,只会在当前线程运行
@Override
public void handleMessage(Message message) {
// 处理发送过来的图片下载消息,下载图片并更新UI
if (message.what == MESSAGE_DOWNLOAD) {
Token token = (Token) message.obj;
try {
handleRequest(token);
// 处理消息
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
}
private void handleRequest(final Token token) throws IOException {
final String url = requestMap.get(token);
// 解决图片加载错位问题
String key = (String) ((ImageView) token).getTag();
if (url == null)
return;
// -----------------------------------
byte[] bitmapBytes = new BlogsFetchr().getUrlBytes(url);
// 下载图片
final Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0,
bitmapBytes.length);
mMemoryCache.put(key, bitmap); // 存入缓存
mResponseHandler.post(new Runnable() {
@Override
public void run() {
// 更新UI
if (requestMap.get(token) != url)
return;
requestMap.remove(token);
mListener.onThumbnailDownloaded(token, bitmap);// 更新UI
}
});
}
public void clearQueue() {
mHandler.removeMessages(MESSAGE_DOWNLOAD);
requestMap.clear();
}
public void queueThumbnail(Token token, String url) {
// 将下载图片命令加入"ThumbnailDownloader"消息队列,
// 在PhotoGalleryFragment中被调用
requestMap.put(token, url);
Message message = mHandler.obtainMessage(MESSAGE_DOWNLOAD, token);
// 获取Message,并且自动与mHandler绑定在一起
// 参数一: what,int型,用于描述消息
// 参数二: obj,随消息发送的指定对象
// 参数三: target,处理消息的Handler,这里由于使用自动和mHandler绑定,故缺省
message.sendToTarget(); // 发送消息给目标Handler
}
public Bitmap getCacheImage(String key) {
// 获取缓存中的图片
Bitmap bitmap = mMemoryCache.get(key);
return bitmap;
}
}
MainActivity:
package com.example.testcsdn;
import java.util.ArrayList;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private ListView mListView;
private ArrayList<Blog> mBlogs; // 博客列表
private String testUrl = "http://blog.csdn.net/column.html"; // 访问的链接,这里测试的CSDN博客专栏的首页
private BlogsFetchr fetchr; // 下载html页面和解析它的工具对象
private MyAdapter adapter;
private ThumbnailDownloader<ImageView> mThumbnailDownloader; // 图片下载器
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fetchr = new BlogsFetchr();
mBlogs = new ArrayList<Blog>();
Log.i(TAG, "mBlogs.size:" + mBlogs.size());
Blog blog = new Blog();
blog.setBloggerId("hello");
mBlogs.add(blog);
update(testUrl);
// 开启响应下载图片消息的线程
mThumbnailDownloader = new ThumbnailDownloader<ImageView>(new Handler());
mThumbnailDownloader
.setListener(new ThumbnailDownloader.Listener<ImageView>() {
@Override
public void onThumbnailDownloaded(ImageView imageView,
Bitmap thumbnail) {
// 更新UI,上图
imageView.setImageBitmap(thumbnail);
}
});
mThumbnailDownloader.start();
mThumbnailDownloader.getLooper(); // 必须要在start之后
}
private void update(final String testUrl) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
mBlogs = fetchr.downloadBlogItems(testUrl); // 下载博客列表
return null;
};
@Override
protected void onPostExecute(Void result) {
// 更新ListView
mListView = (ListView) findViewById(R.id.listview_blogcolumn);
adapter = new MyAdapter(mBlogs);
mListView.setAdapter(adapter);
}
}.execute();
}
private class MyAdapter extends ArrayAdapter<Blog> {
public MyAdapter(ArrayList<Blog> blogs) {
super(MainActivity.this, 0, blogs);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(
R.layout.listview_item, null);
}
ImageView imageView = (ImageView) convertView
.findViewById(R.id.imageView);
TextView textView = (TextView) convertView
.findViewById(R.id.textView);
textView.setText(getItem(position).getBloggerId());
String imageUrl = getItem(position).getBloggerIconUrl();
String imageTag = imageUrl.replaceAll("[^\\w]", "");
imageView.setTag(imageTag);
// 去掉字符串中非(字母、数字、下划线)
// 给imageView设置一个标签,用于存取于Cache
Bitmap bitmap = null;
if ((bitmap = mThumbnailDownloader.getCacheImage(imageTag)) != null) {
// 如果在缓存中存在
imageView.setImageBitmap(bitmap);
} else {
// 发送下载图片消息
mThumbnailDownloader.queueThumbnail(imageView, imageUrl);
}
return convertView;
}
}
}
运行效果: