首页 » Android程序设计:第2版 » Android程序设计:第2版全文在线阅读

《Android程序设计:第2版》步骤4:实现RESTful请求

关灯直达底部

步骤4比目前为止的其他几个步骤要复杂一些。这里需要像类SimpleFinchVideo-ContentProvider一样逐步说明RESTful FinchVideoContentProvider类。首先,FinchVideoContentProvider扩展了RESTfulContentProvider,RESTfulContentProvider又扩展了ContentProvider:


FinchVideoContentProvider extend RESTfulContentProvider {  

RESTfulContentProvider提供异步REST操作,它支持Finch提供者植入定制的请求-响应处理器组件。在探讨升级query方法时,将详细解释这一点。

常量和初始化

FinchVideoContentProvider初始化和简单视频应用的内容提供者很相似。对于简单版的FinchVideoContentProvider,我们设置了一个URI匹配器,其唯一的任务是支持匹配特定的缩略图。没有添加匹配多个缩略图的支持,因为这个视图活动不需要这个功能——它只需要加载单个缩略图:


sUriMatcher.addURI(FinchVideo.AUTHORITY,    FinchVideo.Videos.THUMB + "/#", THUMB_ID);  

创建数据库

在Java代码中使用下面的这个SQL语句创建Finch视频数据库:


CREATE TABLE video (_ID INTEGER PRIMARY KEY AUTOINCREMENT,    title TEXT, description TEXT, thumb_url TEXT,    thumb_width TEXT, thumb_height TEXT, timestamp TEXT,    query_text TEXT, media_id TEXT UNIQUE);  

注意,相对于简单版本,我们增加了以下属性:

thumb_url,thumb_width,thumb_height

它们分别是给定视频的缩略图的URL、宽度和高度。

timestamp

当插入一条新的视频记录时,给它添加当前时间戳。

query_text

在数据库中保存查询文本或查询关键字以及每条查询结果。

media_id

这是从GData API中接收的每个视频响应的唯一值。视频项的media_id必须唯一。

网络Query方法

以下方式是我们所倡导的:在FinchYouTubeProvider查询方法的实现中连接网络以满足YouTube数据的查询请求。它是通过调用它的超类中的方法RESTfulContentProvider.asyncQueryRequest(String queryTag,String queryUri)实现这个功能。在这里,queryTag是唯一字符串,它支持合理地拒绝重复的处理请求,queryUri是完整的需要异步下载的URI。而且,在附加了从应用搜索文本框字段中获取的URLEncoder.encoded查询参数后,URI调用请求如下所示:


/** URI for querying video, expects appended keywords. */private static final String QUERY_URI =        "http://gdata.youtube.com/feeds/api/videos?" +                "max-results=15&format=1&q=";  

注意:你可以很容易学会如何创建满足应用需求的GData YouTube URI。Google在http://gdata.youtube.com创建了beta版的工具。如果你在浏览器中访问该页面,它会显示包含了很多选项的Web UI,你可以通过定制这个UI的方式来创建如前一个代码列表中给出的URI。我们使用该UI选择15项结果,并且选择使用移动视频格式。

我们的网络查询方法执行了URI匹配,并增加了以下任务,即操作序列中的“第4步:实现RESTful请求”:


/** * Content provider query method that converts its parameters into a YouTube * RESTful search query. * * @param uri a reference to the query URI. It may contain "q= * which are sent to the google YouTube * API where they are used to search the YouTube video database. * @param projection * @param where not used in this provider. * @param whereArgs not used in this provider. * @param sortOrder not used in this provider. * @return a cursor containing the results of a YouTube search query. */@Overridepublic Cursor query(Uri uri, String projection, String where,                    String whereArgs, String sortOrder){    Cursor queryCursor;    int match = sUriMatcher.match(uri);    switch (match) {        case VIDEOS:            // the query is passed out of band of other information passed            // to this method -- it's not an argument.            String queryText = uri.                getQueryParameter(FinchVideo.Videos.QUERY_PARAM_NAME);①            if (queryText == null) {                // A null cursor is an acceptable argument to the method,                // CursorAdapter.changeCursor(Cursor c), which interprets                // the value by canceling all adapter state so that the                // component for which the cursor is adapting data will                // display no content.                return null;            }            String select = FinchVideo.Videos.QUERY_TEXT_NAME +                    " = '" + queryText + "'";            // quickly return already matching data            queryCursor =                mDb.query(VIDEOS_TABLE_NAME, projection,                    select,                    whereArgs,                    null,                    null, sortOrder);②        // make the cursor observe the requested query        queryCursor.setNotificationUri(                getContext.getContentResolver, uri);③        /*         * Always try to update results with the latest data from the         * network.         *         * Spawning an asynchronous load task thread guarantees that         * the load has no chance to block any content provider method,         * and therefore no chance to block the UI thread.         *         * While the request loads, we return the cursor with existing         * data to the client.         *         * If the existing cursor is empty, the UI will render no         * content until it receives URI notification.         *         * Content updates that arrive when the asynchronous network         * request completes will appear in the already returned cursor,         * since that cursor query will match that of         * newly arrived items.         */        if (!"".equals(queryText)) {            asyncQueryRequest(queryText, QUERY_URI + encode(queryText));④        }        break;    case VIDEO_ID:    case THUMB_VIDEO_ID:        long videoID = ContentUris.parseId(uri);        queryCursor =                mDb.query(VIDEOS_TABLE_NAME, projection,                        FinchVideo.Videos._ID + " = " + videoID,                        whereArgs, null, null, null);        queryCursor.setNotificationUri(                getContext.getContentResolver, uri);        break;    case THUMB_ID:        String uriString = uri.toString;        int lastSlash = uriString.lastIndexOf("/");        String mediaID = uriString.substring(lastSlash + 1);        queryCursor =                mDb.query(VIDEOS_TABLE_NAME, projection,                        FinchVideo.Videos.MEDIA_ID_NAME + " = " +                                mediaID,                        whereArgs, null, null, null);        queryCursor.setNotificationUri(                getContext.getContentResolver, uri);            break;        default:            throw new IllegalArgumentException("unsupported uri: " +                    QUERY_URI);    }    return queryCursor;}  

以下是关于代码的一些说明:

① 从输入的URI中提取查询参数。只需要把URI中的查询参数传递给query方法,而URI中的其他参数不需要传递,因为它们在query方法中的功能不同,不能用于保存查询关键字。

② 首先检查和查询关键字匹配的本地数据库中已有的数据。

③ 设置通知URI,当提供者改变数据时,query方法返回的游标会接收到更新事件。该操作会启动第6步,当提供者发起数据变化的事件通知时,会触发视图更新。一旦接收到通知,当UI重新绘制时会执行第7步。注意,第6步和第7步没有给出描述,但是这里可以讨论这些步骤,因为它们和URI通知及查询相关。

④ 扩展异步查询,下载给定查询URI。asyncQueryRequest方法封装了每次请求创建的新的线程连接服务。注意,在我们给出的图中,这是第5步;异步请求会扩展线程,从而真正初始化网络通信,YouTube服务会返回响应。

RESTfulContentProvider:REST helper

现在,我们来分析FinchVideoProvider,它继承了RESTful ContentProvider以便执行RESTful请求。首先,要考虑的是给定YouTube请求的行为。正如我们看到的,查询请求和主线程异步运行。RESTful提供者需要处理一些特殊情况,例如某个用户查找“Funny Cats”,而另一个用户正在查询同样的关键字,提供者会删掉第二次请求。另一方面,例如某个用户查找“dogs”,并且在“dogs”查找完成之前又查找了“cats”,provider支持“dogs”查询和“cats”查询并发运行,因为用户可能还会搜索“dogs”,这样就可以复用之前搜索的缓存。

RESTfulContentProvider支持子类扩展异步请求,而且当请求数据到达时,支持使用简单的名为ResponseHandler的插件来自定义处理方式。子类应该覆盖抽象方法RESTfulContentProvider.newResponseHandler,以返回专门用于解析由宿主提供者所请求的响应数据的处理程序。每个处理程序覆盖ResponseHandler.handleResponse(HttpResponse)方法,提供自定义的处理或包含在传递的HttpResponse对象中的HttpEntitys。例如,提供者使用YouTubeHandler来解析YouTube RSS订阅,把读取的每个数据项插入到数据库视频记录中。后面将详细说明这一点。

此外,RESTfulContentProvider类支持子类轻松地执行异步请求,并拒绝重复请求。RESTfulContentProvider通过唯一标签跟踪每个请求,支持子类丢弃重复查询。Finch VideoContentProvider以用户的查询关键字作为请求标签,因为它们能唯一标识某个给定的搜索请求。

FinchVideoContentProvider重写了newResponseHandler方法,如下:


/** * Provides a handler that can parse YouTube GData RSS content. * * @param requestTag unique tag identifying this request. * @return a YouTubeHandler object. */@Overrideprotected ResponseHandler newResponseHandler(String requestTag) {    return new YouTubeHandler(this, requestTag);}  

现在,探讨RESTfulContentProvider的实现,解释它提供给子类的操作。类UriRequestTask提供了runnable接口,可以异步执行REST请求。RESTfulContentProvider使用map mRequestsInProgress,以字符串作为关键字来保证请求的唯一性:


/** * Encapsulates functions for asynchronous RESTful requests so that subclass * content providers can use them for initiating requests while still using * custom methods for interpreting REST-based content such as RSS, ATOM, * JSON, etc. */public abstract class RESTfulContentProvider extends ContentProvider {    protected FileHandlerFactory mFileHandlerFactory;    private Map<String, UriRequestTask> mRequestsInProgress =            new HashMap<String, UriRequestTask>;    public RESTfulContentProvider(FileHandlerFactory fileHandlerFactory) {        mFileHandlerFactory = fileHandlerFactory;    }    public abstract Uri insert(Uri uri, ContentValues cv, SQLiteDatabase db);    private UriRequestTask getRequestTask(String queryText) {        return mRequestsInProgress.get(queryText);①    }    /**     * Allows the subclass to define the database used by a response handler.     *     * @return database passed to response handler.     */    public abstract SQLiteDatabase getDatabase;    public void requestComplete(String mQueryText) {        synchronized (mRequestsInProgress) {            mRequestsInProgress.remove(mQueryText);②        }    }    /**     * Abstract method that allows a subclass to define the type of handler     * that should be used to parse the response of a given request.     *     * @param requestTag unique tag identifying this request.     * @return The response handler created by a subclass used to parse the     * request response.     */    protected abstract ResponseHandler newResponseHandler(String requestTag);    UriRequestTask newQueryTask(String requestTag, String url) {        UriRequestTask requestTask;        final HttpGet get = new HttpGet(url);        ResponseHandler handler = newResponseHandler(requestTag);        requestTask = new UriRequestTask(requestTag, this, get,③                handler, getContext);        mRequestsInProgress.put(requestTag, requestTask);        return requestTask;    }    /**     * Creates a new worker thread to carry out a RESTful network invocation.     *     * @param queryTag unique tag that identifies this request.     *     * @param queryUri the complete URI that should be accessed by this request.     */    public void asyncQueryRequest(String queryTag, String queryUri) {        synchronized (mRequestsInProgress) {            UriRequestTask requestTask = getRequestTask(queryTag);            if (requestTask == null) {                requestTask = newQueryTask(queryTag, queryUri);④                Thread t = new Thread(requestTask);                // allows other requests to run in parallel.                t.start;            }        }    }...}  

以下是关于上述代码的一些说明:

① getRequestTask方法使用mRequestsInProgress方法访问正在执行的请求,看是否有相同的请求,它允许asyncQueryRequest通过简单的if语句阻塞重复请求。

② 请求会在ResponseHandler.handleResponse方法返回后完成,RESTfulContentProvider删除mRequestsInProgress。

③ newQueryTask,创建UriRequestTask实例,UriRequestTask是Runnable实例,会打开HTTP连接,然后在合适的handler上调用handleResponse。

④ 最后,代码包含了一个唯一的请求,创建任务以运行它,然后在线程中封装任务用于异步执行。

虽然RESTfulContentProvider是可重用的任务系统的核心,但为了完整性,我们还要对框架中的其他组件进行介绍。

UriRequestTask。UriRequestTask封装了处理REST请求的异步操作。它是一个简单的类,支持在run方法中执行RESTful GET方法。该操作是步骤4的一部分,即操作序列中的“实现RESTful请求”。正如我们所讨论的,一旦UriRequestTask接收到响应,它会把该响应传递给ResponseHandler.handleResponse方法。我们期望handleResponse方法会执行数据库插入操作,在YouTubeHandler中将看到这一功能:


/** * Provides a runnable that uses an HttpClient to asynchronously load a given * URI. After the network content is loaded, the task delegates handling of the * request to a ResponseHandler specialized to handle the given content. */public class UriRequestTask implements Runnable {    private HttpUriRequest mRequest;    private ResponseHandler mHandler;    protected Context mAppContext;    private RESTfulContentProvider mSiteProvider;    private String mRequestTag;    private int mRawResponse = -1;    public UriRequestTask(HttpUriRequest request,                          ResponseHandler handler, Context appContext)    {        this(null, null, request, handler, appContext);    }    public UriRequestTask(String requestTag,                          RESTfulContentProvider siteProvider,                          HttpUriRequest request,                          ResponseHandler handler, Context appContext)    {        mRequestTag = requestTag;        mSiteProvider = siteProvider;        mRequest = request;        mHandler = handler;        mAppContext = appContext;    }    public void setRawResponse(int rawResponse) {        mRawResponse = rawResponse;    }    /**     * Carries out the request on the complete URI as indicated by the protocol,     * host, and port contained in the configuration, and the URI supplied to     * the constructor.     */    public void run {        HttpResponse response;        try {            response = execute(mRequest);            mHandler.handleResponse(response, getUri);        } catch (IOException e) {            Log.w(Finch.LOG_TAG, "exception processing asynch request", e);        } finally {            if (mSiteProvider != null) {                mSiteProvider.requestComplete(mRequestTag);            }        }    }    private HttpResponse execute(HttpUriRequest mRequest) throws IOException {        if (mRawResponse >= 0) {            return new RawResponse(mAppContext, mRawResponse);        } else {            HttpClient client = new DefaultHttpClient;            return client.execute(mRequest);        }    }    public Uri getUri {        return Uri.parse(mRequest.getURI.toString);    }}  

YouTubeHandler。正如在抽象方法RESTfulContentProvider.newResponseHandler中一样,FinchVideoContentProvider方法返回YouTubeHandler来处理YouTube RSS订阅。YouTubeHandler在内存中使用XML Pull解析器解析输入的数据,遍历获取到的XML RSS数据并处理。YouTubeHandler包含一些复杂特性,但是总体而言,它只是根据需要匹配XML标签来创建ContentValues对象,该对象可以插入到FinchVideoContentProvider的数据库中。当处理程序把解析出的结果都插入提供者数据库时,会执行第5步的一部分。


/** * Parses YouTube Entity data and inserts it into the finch video content * provider. */public class YouTubeHandler implements ResponseHandler {    public static final String MEDIA = "media";    public static final String GROUP = "group";    public static final String DESCRIPTION = "description";    public static final String THUMBNAIL = "thumbnail";    public static final String TITLE = "title";    public static final String CONTENT = "content";    public static final String WIDTH = "width";    public static final String HEIGHT = "height";    public static final String YT = "yt";    public static final String DURATION = "duration";    public static final String FORMAT = "format";    public static final String URI = "uri";    public static final String THUMB_URI = "thumb_uri";    public static final String MOBILE_FORMAT = "1";    public static final String ENTRY = "entry";    public static final String ID = "id";    private static final String FLUSH_TIME = "5 minutes";    private RESTfulContentProvider mFinchVideoProvider;    private String mQueryText;    private boolean isEntry;    public YouTubeHandler(RESTfulContentProvider restfulProvider,                          String queryText)    {        mFinchVideoProvider = restfulProvider;        mQueryText = queryText;    }    /*     * Handles the response from the YouTube GData server, which is in the form     * of an RSS feed containing references to YouTube videos.     */    public void handleResponse(HttpResponse response, Uri uri)            throws IOException    {        try {            int newCount = parseYoutubeEntity(response.getEntity);①            // only flush old state now that new state has arrived            if (newCount > 0) {                deleteOld;            }        } catch (IOException e) {            // use the exception to avoid clearing old state, if we cannot            // get new state. This way we leave the application with some            // data to work with in absence of network connectivity.            // we could retry the request for data in the hope that the network            // might return.        }    }    private void deleteOld {        // delete any old elements, not just ones that match the current query.        Cursor old = null;        try {            SQLiteDatabase db = mFinchVideoProvider.getDatabase;            old = db.query(FinchVideo.Videos.VIDEO, null,                    "video." + FinchVideo.Videos.TIMESTAMP +                            " < strftime('%s', 'now', '-" + FLUSH_TIME + "')",                    null, null, null, null);            int c = old.getCount;            if (old.getCount > 0) {                StringBuffer sb = new StringBuffer;                boolean next;                if (old.moveToNext) {                    do {                        String ID = old.getString(FinchVideo.ID_COLUMN);                        sb.append(FinchVideo.Videos._ID);                        sb.append(" = ");                        sb.append(ID);                        // get rid of associated cached thumb files                        mFinchVideoProvider.deleteFile(ID);                        next = old.moveToNext;                        if (next) {                            sb.append(" OR ");                        }                    } while (next);                }                String where = sb.toString;                db.delete(FinchVideo.Videos.VIDEO, where, null);                Log.d(Finch.LOG_TAG, "flushed old query results: " + c);            }        } finally {            if (old != null) {                old.close;            }        }    }    private int parseYoutubeEntity(HttpEntity entity) throws IOException {        InputStream youTubeContent = entity.getContent;        InputStreamReader inputReader = new InputStreamReader(youTubeContent);            int inserted = 0;            try {                XmlPullParserFactory factory = XmlPullParserFactory.newInstance;                factory.setNamespaceAware(false);                XmlPullParser xpp = factory.newPullParser;                xpp.setInput(inputReader);                int eventType = xpp.getEventType;                String startName = null;                ContentValues mediaEntry = null;                // iterative pull parsing is a useful way to extract data from                // streams, since we don't have to hold the DOM model in memory                // during the parsing step.                while (eventType != XmlPullParser.END_DOCUMENT) {                    if (eventType == XmlPullParser.START_DOCUMENT) {                    } else if (eventType == XmlPullParser.END_DOCUMENT) {                    } else if (eventType == XmlPullParser.START_TAG) {                        startName = xpp.getName;                        if ((startName != null)) {                            if ((ENTRY).equals(startName))         {                                mediaEntry = new ContentValues;                                mediaEntry.put(FinchVideo.Videos.QUERY_TEXT_NAME,                                        mQueryText);                            }                            if ((MEDIA + ":" + CONTENT).equals(startName)) {                                int c = xpp.getAttributeCount;                                String mediaUri = null;                                boolean isMobileFormat = false;                                for (int i = 0; i < c; i++) {                                    String attrName = xpp.getAttributeName(i);                                    String attrValue = xpp.getAttributeValue(i);                                    if ((attrName != null) &&                                            URI.equals(attrName))                                    {                                        mediaUri = attrValue;                                    }                                    if ((attrName != null) && (YT + ":" + FORMAT).                                            equals(MOBILE_FORMAT))                                    {                                        isMobileFormat = true;                                    }                                }                                if (isMobileFormat && (mediaUri != null)) {                                    mediaEntry.put(URI, mediaUri);                                    }                                }                                if ((MEDIA + ":" + THUMBNAIL).equals(startName)) {                                    int c = xpp.getAttributeCount;                                    for (int i = 0; i < c; i++) {                                        String attrName = xpp.getAttributeName(i);                                        String attrValue = xpp.getAttributeValue(i);                                        if (attrName != null) {                                            if ("url".equals(attrName)) {                                                mediaEntry.put(                                                        FinchVideo.Videos.                                                                THUMB_URI_NAME,                                                        attrValue);                                            } else if (WIDTH.equals(attrName))                                            {                                                mediaEntry.put(                                                        FinchVideo.Videos.                                                                THUMB_WIDTH_NAME,                                                        attrValue);                                            } else if (HEIGHT.equals(attrName))                                            {                                                mediaEntry.put(                                                        FinchVideo.Videos.                                                                THUMB_HEIGHT_NAME,                                                        attrValue);                                            }                                        }                                    }                                }                                if (ENTRY.equals(startName)) {                                    isEntry = true;                                }                            }                        } else if(eventType == XmlPullParser.END_TAG) {                            String endName = xpp.getName;                            if (endName != null) {                                if (ENTRY.equals(endName)) {                                    isEntry = false;                                } else if (endName.equals(MEDIA + ":" + GROUP)) {                                    // insert the complete media group                                    inserted++;                                    // Directly invoke insert on the finch video                                    // provider, without using content resolver. We                                    // would not want the content provider to sync this                                    // data back to itself.                                    SQLiteDatabase db =                                            mFinchVideoProvider.getDatabase;                                    String mediaID = (String) mediaEntry.get(                                            FinchVideo.Videos.MEDIA_ID_NAME);                                    // insert thumb uri                                    String thumbContentUri =                                            FinchVideo.Videos.THUMB_URI + "/" + mediaID;                                    mediaEntry.put(FinchVideo.Videos.                                            THUMB_CONTENT_URI_NAME,                                            thumbContentUri);                                    String cacheFileName =                                            mFinchVideoProvider.getCacheName(mediaID);                                    mediaEntry.put(FinchVideo.Videos._DATA,                                            cacheFileName);                                    Uri providerUri = mFinchVideoProvider.                                            insert(FinchVideo.Videos.CONTENT_URI,                                                    mediaEntry, db);②                                    if (providerUri != null) {                                        String thumbUri = (String) mediaEntry.                                                get(FinchVideo.Videos.THUMB_URI_NAME);                                        // We might consider lazily downloading the                                        // image so that it was only downloaded on                                        // viewing. Downloading more aggressively                                        // could also improve performance.                                        mFinchVideoProvider.                                                cacheUri2File(String.valueOf(ID),                                                thumbUrl);③                                    }                                }                            }                        } else if (eventType == XmlPullParser.TEXT) {                            // newline can turn into an extra text event                            String text = xpp.getText;                            if (text != null) {                                text = text.trim;                                if ((startName != null) && (!"".equals(text))){                                    if (ID.equals(startName) && isEntry) {                                        int lastSlash = text.lastIndexOf("/");                                        String entryId =                                                text.substring(lastSlash + 1);                                        mediaEntry.put(FinchVideo.Videos.MEDIA_ID_NAME,                                                entryId);                                    } else if ((MEDIA + ":" + TITLE).                                            equals(startName))                                     {                                        mediaEntry.put(TITLE, text);                                    } else if ((MEDIA + ":" +                                            DESCRIPTION).equals(startName))                                    {                                        mediaEntry.put(DESCRIPTION, text);                                    }                                }                            }                        }                            eventType = xpp.next;            }            // an alternate notification scheme might be to notify only after            // all entries have been inserted.        } catch (XmlPullParserException e) {            Log.d(Ch11.LOG_TAG,                "could not parse video feed", e);        } catch (IOException e) {            Log.d(Ch11.LOG_TAG,                "could not process video stream", e);        }        return inserted;    }} 

以下是关于上述代码的一些说明:

① 处理程序通过在parseYoutubeEntity方法中解析YouTube HTTP实体实现了handleResponse,parseYoutubeEntity方法会插入新的视频数据。然后,处理程序查询出一段时间之前的元素并删除。

② 处理程序完成了媒体元素的解析,使用其包含的内容提供者插入新解析的ContentValues对象。注意,这个操作在我们描述的操作序列中属于步骤5“响应处理程序将元素添加到本地缓存”。

③ 提供者在插入一条新的媒体项后,会初始化自身的异步请求,下载缩略图内容。后面将很快解释提供者的这个特性。

插入和ResponseHandlers

下面详细探讨步骤5,Finch视频提供者中insert的实现方式和简单的视频提供者的几乎相同。此外,正如我们在应用中看到的,视频插入是query方法的副产品。值得一提的是,insert方法可以分成两部分,内容提供者客户端调用第一部分,响应处理程序调用第二部分,其实现代码如下所示。第一种方式委托给第二种方式。我们把insert方法分成两部分,是因为响应处理程序是内容提供者的一部分,而且不需要将内容解析程序再定向到其本身:


@Overridepublic Uri insert(Uri uri, ContentValues initialValues) {    // Validate the requested uri    if (sUriMatcher.match(uri) != VIDEOS) {        throw new IllegalArgumentException("Unknown URI " + uri);    }    ContentValues values;    if (initialValues != null) {        values = new ContentValues(initialValues);    } else {        values = new ContentValues;    }    SQLiteDatabase db = getDatabase;    return insert(uri, initialValues, db);}  

YouTubeHandler使用以下方式,直接把记录插入到简单的视频数据库中。注意,如果数据库中已经包含准备插入的媒体的mediaID,就不需要插入该记录。通过这种方式可以避免视频项重复,当把新的数据和老的且尚未过期的数据集成起来时,可能会出现视频项重复:


public Uri insert(Uri uri, ContentValues values, SQLiteDatabase db) {    verifyValues(values);    // Validate the requested uri    int m = sUriMatcher.match(uri);    if (m != VIDEOS) {        throw new IllegalArgumentException("Unknown URI " + uri);    }    // insert the values into a new database row    String mediaID = (String) values.get(FinchVideo.Videos.MEDIA_ID);    Long rowID = mediaExists(db, mediaID);    if (rowID == null) {        long time = System.currentTimeMillis;        values.put(FinchVideo.Videos.TIMESTAMP, time);        long rowId = db.insert(VIDEOS_TABLE_NAME,                FinchVideo.Videos.VIDEO, values);        if (rowId >= 0) {            Uri insertUri =                    ContentUris.withAppendedId(                            FinchVideo.Videos.CONTENT_URI, rowId);            mContentResolver.notifyChange(insertUri, null);            return insertUri;        } else {            throw new IllegalStateException("could not insert " +                    "content values: " + values);        }    }    return ContentUris.withAppendedId(FinchVideo.Videos.CONTENT_URI, rowID);}  

文件管理:缩略图存储

现在,我们已经了解了RESTful提供者框架是如何运作的,接下来将解释提供者是如何处理缩略图的。

前面描述了ContentResolver.openInputStream方法作为内容提供者为客户端打开文件的方式。在Finch视频实例中,我们使用该特征提供缩略图服务。把图像保存成文件使得我们能够避免使用数据库的blob类型及其带来的性能开销,并且当客户端请求这些图片时,可以只下载这些图片。如果内容提供者要提供文件服务,必须重写ContentProvider.openFile方法,ContentProvider.openFile方法会打开要提供服务的文件描述符。该方法最简单的实现方式是调用openFileHelper,执行一些便捷的功能,支持ContentResolver读取_data变量,加载其引用的文件。如果provider没有重写该方法,你会看到如下异常:"No files supported by provider at..."。这种简单的实现方式只支持“只读”访问方式,如下所示:


/** * Provides read-only access to files that have been downloaded and stored * in the provider cache. Specifically, in this provider, clients can * access the files of downloaded thumbnail images. */@Overridepublic ParcelFileDescriptor openFile(Uri uri, String mode)        throws FileNotFoundException{    // only support read-only files    if (!"r".equals(mode.toLowerCase)) {        throw new FileNotFoundException("Unsupported mode, " +          mode + ", for uri: " + uri);        }    return openFileHelper(uri, mode);}  

最后,通过ResponseHandler的FileHandler实现,从每个媒体程序对应的YouTube缩略图URL下载图像数据。该FileHandlerFactory支持管理在特定的缓存目录下保存的缓存文件,而且该FileHandlerFactory支持选择在哪里保存这些文件:


/** * Creates instances of FileHandler objects that use a common cache directory. * The cache directory is set in the constructor to the file handler factory. */public class FileHandlerFactory {    private String mCacheDir;    public FileHandlerFactory(String cacheDir) {        mCacheDir = cacheDir;        init;    }    private void init {        File cacheDir = new File(mCacheDir);        if (!cacheDir.exists) {            cacheDir.mkdir;        }    }    public FileHandler newFileHandler(String id) {        return new FileHandler(mCacheDir, id);    }    // not really used since ContentResolver uses _data field.    public File getFile(String ID) {        String cachePath = getFileName(ID);        File cacheFile = new File(cachePath);        if (cacheFile.exists) {            return cacheFile;        }        return null;    }    public void delete(String ID) {        String cachePath = mCacheDir + "/" + ID;        File cacheFile = new File(cachePath);        if (cacheFile.exists) {            cacheFile.delete;        }    }    public String getFileName(String ID) {        return mCacheDir + "/" + ID;    }}/** * Writes data from URLs into a local file cache that can be referenced by a * database ID. */public class FileHandler implements ResponseHandler {    private String mId;    private String mCacheDir;    public FileHandler(String cacheDir, String id) {        mCacheDir = cacheDir;        mId = id;    }    public    String getFileName(String ID) {        return mCacheDir + "/" + ID;    }    public void handleResponse(HttpResponse response, Uri uri)            throws IOException    {        InputStream urlStream = response.getEntity.getContent;        FileOutputStream fout =                new FileOutputStream(getFileName(mId));        byte bytes = new byte[256];        int r = 0;        do {            r = urlStream.read(bytes);            if (r >= 0) {                fout.write(bytes, 0, r);            }        } while (r >= 0);        urlStream.close;        fout.close;    }}