在前面几篇文章中,我们或多或少了解到了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();
}
}
}