Android 使用Jsoup解析html+下载图片

       最近想鼓捣一下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.util.Log;
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);
		if (url == null)
			return;

		byte[] bitmapBytes = new BlogsFetchr().getUrlBytes(url);
		// 下载图片
		final Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0,
				bitmapBytes.length);
		String key = (String) ((ImageView) token).getTag();
		Log.i(TAG, "imageView的TAG是:" + key);
		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;
		}
	}
}

运行效果:

技术分享技术分享


源码下载


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。