现在的位置: 首页 > 综合 > 正文

Android开源代码解读の地图照片应用Panoramio的实现详解(五)

2013年08月16日 ⁄ 综合 ⁄ 共 11076字 ⁄ 字号 评论关闭

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

    }
    
}

抱歉!评论已关闭.