在前面几篇文章中,我们或多或少了解到了ImageManager类的存在,它负责从Panoramio服务器下载搜索区域内的图片数据,同时进行解析。当然,这一切是在独立的后台线程中进行的,下载的情况通过观察者模式通告给ImageList进行显示(ImageManager是被观察对象Subject)。注意,ImageManager是一个单例类。
本文涉及到的知识点有两个:JSON和WeakReference。
1)JSON(www.json.org)是目前流行的网络数据交换格式,它是JavaScript Object Notation的缩写。JSON数据是一系列键值对的集合,相信曾做过web开发的对这个不会陌生。Android自带的JSON API位于libcore\json\src\main\java\org\json目录中,包括JSON.java、JSONArray.java等6个Java源文件,在代码开头处有个声明可以看下:
Note: this class was written without inspecting the non-free org.json sourcecode.
可以看出,这是是Google自己实现的一个JSON解析类。
2)WeakReference是Java四种引用类型之一的弱引用,其他三种分别是强引用StrongReference,软引用SoftReference和虚引用PhantomReference(又叫幽灵引用)。这四种引用和垃圾收集器GC的交互各不相同,下面就来简单分析下:
StrongReference是Java的默认引用,它会尽可能长地存活在Java虚拟机中,当没有任何对象指向它时,GC执行后才会被回收;
SoftReference和WeakReference类似,最大的区别在于软引用会尽可能长地保留它自己,直到出现Java虚拟机内存不足时才会被回收;因此,软引用常用于对内存敏感的程序中;
WeakReference当它所引用的对象在Java虚拟机中不再存在强引用时,GC执行后弱引用才会被回收;
PhantomReference完全类似于没有引用,虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,它必须和引用队列ReferenceQueue结合使用。
除了强引用外,其他几个引用对应的类都定义在Java.lang.ref包中,而且都继承自Reference类,下面看下它们的源代码吧:
package java.lang.ref; /** * Provides an abstract class which describes behavior common to all reference * objects. It is not possible to create immediate subclasses of * {@code Reference} in addition to the ones provided by this package. It is * also not desirable to do so, since references require very close cooperation * with the system's garbage collector. The existing, specialized reference * classes should be used instead. */ public abstract class Reference<T> { /** * The object to which this reference refers. * VM requirement: this field <em>must</em> be called "referent" * and be an object. */ volatile T referent; /** * If non-null, the queue on which this reference will be enqueued * when the referent is appropriately reachable. * VM requirement: this field <em>must</em> be called "queue" * and be a java.lang.ref.ReferenceQueue. */ @SuppressWarnings("unchecked") volatile ReferenceQueue queue; /** * Used internally by java.lang.ref.ReferenceQueue. * VM requirement: this field <em>must</em> be called "queueNext" * and be a java.lang.ref.Reference. */ @SuppressWarnings("unchecked") volatile Reference queueNext; /** * Used internally by the VM. This field forms a singly-linked * list of reference objects awaiting processing by the garbage * collector. */ @SuppressWarnings("unchecked") volatile Reference pendingNext; /** * Constructs a new instance of this class. */ Reference() { super(); } /** * Makes the referent {@code null}. This does not force the reference * object to be enqueued. */ public void clear() { referent = null; } /** * An implementation of .enqueue() that is safe for the VM to call. * If a Reference object is a subclass of any of the * java.lang.ref.*Reference classes and that subclass overrides enqueue(), * the VM may not call the overridden method. * VM requirement: this method <em>must</em> be called "enqueueInternal", * have the signature "()Z", and be private. * * @return {@code true} if this call has caused the {@code Reference} to * become enqueued, or {@code false} otherwise */ @SuppressWarnings("unchecked") private synchronized boolean enqueueInternal() { /* VM requirement: * The VM assumes that this function only does work * if "(queue != null && queueNext == null)". * If that changes, Dalvik needs to change, too. * (see MarkSweep.c:enqueueReference()) */ if (queue != null && queueNext == null) { queue.enqueue(this); queue = null; return true; } return false; } /** * Forces the reference object to be enqueued if it has been associated with * a queue. * * @return {@code true} if this call has caused the {@code Reference} to * become enqueued, or {@code false} otherwise */ public boolean enqueue() { return enqueueInternal(); } /** * Returns the referent of the reference object. * * @return the referent to which reference refers, or {@code null} if the * object has been cleared. */ public T get() { return referent; } /** * Checks whether the reference object has been enqueued. * * @return {@code true} if the {@code Reference} has been enqueued, {@code * false} otherwise */ public boolean isEnqueued() { return queueNext != null; } }
SoftReference.java文件如下所示:
public class SoftReference<T> extends Reference<T> { /** * Constructs a new soft reference to the given referent. The newly created * reference is not registered with any reference queue. * * @param r the referent to track */ public SoftReference(T r) { super(); referent = r; } /** * Constructs a new soft reference to the given referent. The newly created * reference is registered with the given reference queue. * * @param r the referent to track * @param q the queue to register to the reference object with. A null value * results in a weak reference that is not associated with any * queue. */ public SoftReference(T r, ReferenceQueue<? super T> q) { super(); referent = r; queue = q; } }接下来是WeakReference.java,它的实现几乎和SoftReference一样,只是类名不同而已:
public class WeakReference<T> extends Reference<T> { /** * Constructs a new weak reference to the given referent. The newly created * reference is not registered with any reference queue. * * @param r the referent to track */ public WeakReference(T r) { super(); referent = r; } /** * Constructs a new weak reference to the given referent. The newly created * reference is registered with the given reference queue. * * @param r the referent to track * @param q the queue to register to the reference object with. A null value * results in a weak reference that is not associated with any * queue. */ public WeakReference(T r, ReferenceQueue<? super T> q) { super(); referent = r; queue = q; } }而最后的PhantomReference.java的实现稍微有点区别,它的get函数返回null:
public class PhantomReference<T> extends Reference<T> { /** * Constructs a new phantom reference and registers it with the given * reference queue. The reference queue may be {@code null}, but this case * does not make any sense, since the reference will never be enqueued, and * the {@link #get()} method always returns {@code null}. * * @param r the referent to track * @param q the queue to register the phantom reference object with */ public PhantomReference(T r, ReferenceQueue<? super T> q) { super(); referent = r; queue = q; } /** * Returns {@code null}. The referent of a phantom reference is not * accessible. * * @return {@code null} (always) */ @Override public T get() { return null; } }经过上面知识点的分析,ImageManager类也就没什么其他好讲的了,直接看代码以及注释应该就很清楚了:
package com.google.android.panoramio; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.database.DataSetObserver; import android.graphics.Bitmap; import android.os.Handler; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.ref.WeakReference; import java.net.URI; import java.util.ArrayList; public class ImageManager { private static final String TAG = "Panoramio"; /** * Panoramio服务API的基本URL,更多内容可见http://www.programmableweb.com/api/panoramio */ private static final String THUMBNAIL_URL = "//www.panoramio.com/map/get_panoramas.php?order=popularity&set=public&from=0&to=20&miny=%f&minx=%f&maxy=%f&maxx=%f&size=thumbnail"; /** * 用于后台线程将结果反馈给UI线程 */ private Handler mHandler = new Handler(); /** * 存储ImageManager的唯一实例 */ private static ImageManager sInstance; /** * 存储从网络上下载的图片和相关信息 */ private ArrayList<PanoramioItem> mImages = new ArrayList<PanoramioItem>(); /** * 存储对当前搜索结果感兴趣的观察者实例 */ private ArrayList<WeakReference<DataSetObserver>> mObservers = new ArrayList<WeakReference<DataSetObserver>>(); /** * 当处于下载阶段时为true,下载结束后置为false */ private boolean mLoading; private Context mContext; /** * Key for an Intent extra. The value is the zoom level selected by the user. */ public static final String ZOOM_EXTRA = "zoom"; /** * Key for an Intent extra. The value is the latitude of the center of the search * area chosen by the user. */ public static final String LATITUDE_E6_EXTRA = "latitudeE6"; /** * Key for an Intent extra. The value is the latitude of the center of the search * area chosen by the user. */ public static final String LONGITUDE_E6_EXTRA = "longitudeE6"; /** * Key for an Intent extra. The value is an item to display */ public static final String PANORAMIO_ITEM_EXTRA = "item"; /** * 延迟初始化,单例模式 */ public static ImageManager getInstance(Context c) { if (sInstance == null) { sInstance = new ImageManager(c.getApplicationContext()); } return sInstance; } /** * 注意,构造函数必须是private,保证只能通过getInstance获取该类的唯一实例 */ private ImageManager(Context c) { mContext = c; } /** * 如果目前还在下载图片信息则返回true */ public boolean isLoading() { return mLoading; } /** * 清除所有下载内容,并通知观察者 */ public void clear() { mImages.clear(); notifyObservers(); } /** * 内存中添加一个PanoramioItem实例,并通知观察者这个变化 */ private void add(PanoramioItem item) { mImages.add(item); notifyObservers(); } /** * 目前显示的PanoramioItem数目 */ public int size() { return mImages.size(); } /** * 获取内存中指定索引处的PanoramioItem实例 */ public PanoramioItem get(int position) { return mImages.get(position); } /** * 绑定一个观察者(当ImageManager保存的PanoramioItem集合发生变化时通告它们) */ public void addObserver(DataSetObserver observer) { WeakReference<DataSetObserver> obs = new WeakReference<DataSetObserver>(observer); mObservers.add(obs); } /** * 根据新的搜索区域信息,开启独立的线程从服务器下载该区域内的图片信息 * * @param minLong 搜索区域的最小经度 * @param maxLong 搜索区域的最大经度 * @param minLat 搜索区域的最小纬度 * @param maxLat 搜索区域的最大纬度 */ public void load(float minLong, float maxLong, float minLat, float maxLat) { mLoading = true; new NetworkThread(minLong, maxLong, minLat, maxLat).start(); } /** * 当PanoramioItem数据集发生变化时,调用这个函数通告观察者 * 同时,清除无效的观察者对象弱引用 */ private void notifyObservers() { final ArrayList<WeakReference<DataSetObserver>> observers = mObservers; final int count = observers.size(); for (int i = count - 1; i >= 0; i--) { WeakReference<DataSetObserver> weak = observers.get(i); DataSetObserver obs = weak.get(); if (obs != null) { obs.onChanged(); } else { observers.remove(i); } } } /** * 这个线程实现图片数据的下载和解析 * */ private class NetworkThread extends Thread { //搜索区域的最大最小经纬度 private float mMinLong; private float mMaxLong; private float mMinLat; private float mMaxLat; public NetworkThread(float minLong, float maxLong, float minLat, float maxLat) { mMinLong = minLong; mMaxLong = maxLong; mMinLat = minLat; mMaxLat = maxLat; } @Override public void run() { String url = THUMBNAIL_URL; url = String.format(url, mMinLat, mMinLong, mMaxLat, mMaxLong); try { URI uri = new URI("http", url, null); HttpGet get = new HttpGet(uri); HttpClient client = new DefaultHttpClient(); HttpResponse response = client.execute(get); HttpEntity entity = response.getEntity(); String str = convertStreamToString(entity.getContent()); JSONObject json = new JSONObject(str); parse(json); } catch (Exception e) { Log.e(TAG, e.toString()); } } /** * 将JSON对象解析出来 */ private void parse(JSONObject json) { try { JSONArray array = json.getJSONArray("photos"); //JSON数组 int count = array.length(); for (int i = 0; i < count; i++) { JSONObject obj = array.getJSONObject(i); //JSON数组中的对象 long id = obj.getLong("photo_id"); String title = obj.getString("photo_title"); String owner = obj.getString("owner_name"); String thumb = obj.getString("photo_file_url"); String ownerUrl = obj.getString("owner_url"); String photoUrl = obj.getString("photo_url"); double latitude = obj.getDouble("latitude"); double longitude = obj.getDouble("longitude"); Bitmap b = BitmapUtils.loadBitmap(thumb); //加载图片缩略图 if (title == null) { title = mContext.getString(R.string.untitled); } //根据解析出来的数据封装PanoramioItem对象 final PanoramioItem item = new PanoramioItem(id, thumb, b, (int) (latitude * Panoramio.MILLION), (int) (longitude * Panoramio.MILLION), title, owner, ownerUrl, photoUrl); final boolean done = i == count - 1; //是否全部完成 //每解析完一项就通知UI线程 mHandler.post(new Runnable() { public void run() { sInstance.mLoading = !done; sInstance.add(item); } }); } } catch (JSONException e) { Log.e(TAG, e.toString()); } } /** * 将输入流转换成字符串形式,因此使用了字符流API(Reader) */ private String convertStreamToString(InputStream is) { BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8*1024); StringBuilder sb = new StringBuilder(); String line = null; try { while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } } catch (IOException e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return sb.toString(); } } }