[译]从网络加载数据 - Android TV应用手册教程十六
版权声明:本文为博主原创翻译文章,转载请注明出处。
推荐:
欢迎关注我创建的Android TV 简书专题,会定期给大家分享一些AndroidTv相关的内容:
http://www.jianshu.com/c/37efc6e9799b
video-data-from-web
*您可以在此处查看此帖中使用的JSON视频数据: https://raw.githubusercontent.com/corochann/AndroidTVappTutorial/master/app/src/main/assets/video_lists.json
在线管理数据,不断更新。
在前一章中, 背景数据加载 - Android TV应用程序教程十五,介绍了LoaderManager和Loader类,它有助于在后台加载/准备(可能耗时)的数据。 “耗时”数据准备的一个例子是从网络加载数据。 如果您可以从网络提供数据,应用程序可以随时显示更新的最新信息。
在本章我将实现网页数据加载,以显示我们的视频内容信息。 我们将以json格式准备数据,并将其上传到网络( https://raw.githubusercontent.com/corochann/AndroidTVappTutorial/master/app/src/main/assets/video_lists.json )。 这意味着可以通过改变这个json文件来修改视频内容,而不需要修改任何java源代码。
(注:只有当您的Android TV与本章连接到互联网时,该示例应用才能正常工作。)
视频数据准备
通过从网络动态显示视频内容数据,我更改了视频源。 我正在使用PEXELS VIDEOS的视频内容,这是公共领域的视频,以便我们可以自由使用。
我总结了查找可以自由使用的视频,照片,音乐 ,介绍分发CC许可的媒体内容的网页。
JSON格式的视频数据列表
直到本章,我正在使用MovieProvider类准备视频数据。 它以硬编码的方式准备Movie项目。 相反,我们想以更有组织的方式准备数据,并使用JSON格式来准备视频数据。 这是 JSON格式的真正的视频列表数据 。
我不会在这篇文章中详细介绍JSON格式本身。 对于那些不熟悉JSON的人,我将把一些链接如下解析JSON。
并不难,你也可以通过查看这个帖子的示例代码来解析JSON数据。 JSON数据由JSONObject或JSONArray (此关系类似于常规程序语言中的变量和数组)。
在网络上有一些有用的JSON分析工具,如下所示
您可以尝试复制并粘贴此JSON以直观地了解其具有什么样的数据结构,这有助于您了解如何更容易地解析JSON数据。
数据加载触发器 - VideoItemLoader
我们继续进行。 如前一章所述,数据准备工作现在在loadInBackground中的loadInBackground方法中VideoItemLoader 。
现在我们要修改这个方法来从Web准备数据。
*可能需要一些时间来加载数据,这适合在后台进行! 这是我们上一章介绍Loader的目的。
之前修改loadInBackground方法如下。 这里介绍了新的VideoProvider类来加载Web数据。
VideoItemLoader.java
@Override
public LinkedHashMap<String, List<Movie>> loadInBackground() {
Log.d(TAG, "loadInBackground");
/*
* Executed in background thread.
* Prepare data here, it may take long time (Database access, URL connection, etc).
* return value is used in onLoadFinished() method in Activity/Fragment's LoaderCallbacks.
*/
//LinkedHashMap<String, List<Movie>> videoLists = prepareData();
LinkedHashMap<String, List<Movie>> videoLists = null;
try {
videoLists = VideoProvider.buildMedia(getContext());
} catch (JSONException e) {
Log.e(TAG, "buildMedia failed", e);
//cancelLoad();
}
return videoLists;
}
您可以看到VideoLoader类只触发了视频数据加载。 实际加载过程在VideoItemProvider类中完成。
数据加载过程 - VideoItemProvider
在com.corochann.androidtvapptutorial.data包中创建一个名为VideoItemProvider的java新类。 下面是这个类的整个源代码。
VideoProvider.java
package com.corochann.androidtvapptutorial.data;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.util.Log;
import com.corochann.androidtvapptutorial.model.Movie;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
*
*/
public class VideoProvider {
private static final String TAG = VideoProvider.class.getSimpleName();
public static final String VIDEO_LIST_URL = "https://raw.githubusercontent.com/corochann/AndroidTVappTutorial/master/app/src/main/assets/video_lists.json";
public static final String PREFIX_URL = "http://corochann.com/wp-content/uploads/2015/11/";
private static String TAG_ID = "id";
private static String TAG_MEDIA = "videos";
private static String TAG_VIDEO_LISTS = "videolists";
private static String TAG_CATEGORY = "category";
private static String TAG_STUDIO = "studio";
private static String TAG_SOURCES = "sources";
private static String TAG_DESCRIPTION = "description";
private static String TAG_CARD_THUMB = "card";
private static String TAG_BACKGROUND = "background";
private static String TAG_TITLE = "title";
private static LinkedHashMap<String, List<Movie>> sMovieList;
private static Resources sResources;
private static Uri sPrefixUrl;
public static void setContext(Context context) {
if (null == sResources) {
sResources = context.getResources();
}
}
/**
* It may return null when data is not prepared yet by {@link #buildMedia}.
* Ensure that data is already prepared before call this function.
* @return
*/
public static LinkedHashMap<String, List<Movie>> getMedia() {
return sMovieList;
}
/**
* ArrayList of movies within specified "category".
* If argument is null, then returns all movie list.
* @param category
* @return
*/
public static ArrayList<Movie> getMovieItems(String category) {
if(sMovieList == null) {
Log.e(TAG, "sMovieList is not prepared yet!");
return null;
} else {
ArrayList<Movie> movieItems = new ArrayList<>();
for (Map.Entry<String, List<Movie>> entry : sMovieList.entrySet()) {
String categoryName = entry.getKey();
if(category !=null && !category.equals(categoryName)) {
continue;
}
List<Movie> list = entry.getValue();
for (int j = 0; j < list.size(); j++) {
movieItems.add(list.get(j));
}
}
if(movieItems == null) {
Log.w(TAG, "No data foud with category: " + category);
}
return movieItems;
}
}
public static LinkedHashMap<String, List<Movie>> buildMedia(Context ctx) throws JSONException{
return buildMedia(ctx, VIDEO_LIST_URL);
}
public static LinkedHashMap<String, List<Movie>> buildMedia(Context ctx, String url)
throws JSONException {
if (null != sMovieList) {
return sMovieList;
}
sMovieList = new LinkedHashMap<>();
//sMovieListById = new HashMap<>();
JSONObject jsonObj = parseUrl(url);
if (null == jsonObj) {
Log.e(TAG, "An error occurred fetching videos.");
return sMovieList;
}
JSONArray categories = jsonObj.getJSONArray(TAG_VIDEO_LISTS);
if (null != categories) {
final int categoryLength = categories.length();
Log.d(TAG, "category #: " + categoryLength);
long id;
String title;
String videoUrl;
String bgImageUrl;
String cardImageUrl;
String studio;
for (int catIdx = 0; catIdx < categoryLength; catIdx++) {
JSONObject category = categories.getJSONObject(catIdx);
String categoryName = category.getString(TAG_CATEGORY);
JSONArray videos = category.getJSONArray(TAG_MEDIA);
Log.d(TAG,
"category: " + catIdx + " Name:" + categoryName + " video length: "
+ (null != videos ? videos.length() : 0));
List<Movie> categoryList = new ArrayList<Movie>();
Movie movie;
if (null != videos) {
for (int vidIdx = 0, vidSize = videos.length(); vidIdx < vidSize; vidIdx++) {
JSONObject video = videos.getJSONObject(vidIdx);
String description = video.getString(TAG_DESCRIPTION);
JSONArray videoUrls = video.getJSONArray(TAG_SOURCES);
if (null == videoUrls || videoUrls.length() == 0) {
continue;
}
id = video.getLong(TAG_ID);
title = video.getString(TAG_TITLE);
videoUrl = PREFIX_URL + getVideoSourceUrl(videoUrls);
bgImageUrl = PREFIX_URL + video.getString(TAG_BACKGROUND);
cardImageUrl = PREFIX_URL + video.getString(TAG_CARD_THUMB);
studio = video.getString(TAG_STUDIO);
movie = buildMovieInfo(id, categoryName, title, description, studio,
videoUrl, cardImageUrl, bgImageUrl);
categoryList.add(movie);
}
sMovieList.put(categoryName, categoryList);
}
}
}
return sMovieList;
}
private static Movie buildMovieInfo(long id,
String category,
String title,
String description,
String studio,
String videoUrl,
String cardImageUrl,
String bgImageUrl) {
Movie movie = new Movie();
movie.setId(id);
//movie.setId(Movie.getCount());
//Movie.incrementCount();
movie.setTitle(title);
movie.setDescription(description);
movie.setStudio(studio);
movie.setCategory(category);
movie.setCardImageUrl(cardImageUrl);
movie.setBackgroundImageUrl(bgImageUrl);
movie.setVideoUrl(videoUrl);
return movie;
}
// workaround for partially pre-encoded sample data
private static String getVideoSourceUrl(final JSONArray videos) throws JSONException {
try {
final String url = videos.getString(0);
return (-1) == url.indexOf('%') ? url : URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new JSONException("Broken VM: no UTF-8");
}
}
protected static JSONObject parseUrl(String urlString) {
Log.d(TAG, "Parse URL: " + urlString);
BufferedReader reader = null;
//sPrefixUrl = Uri.parse(sResources.getString(R.string.prefix_url));
sPrefixUrl = Uri.parse(PREFIX_URL);
try {
java.net.URL url = new java.net.URL(urlString);
URLConnection urlConnection = url.openConnection();
reader = new BufferedReader(new InputStreamReader(
urlConnection.getInputStream()));
//urlConnection.getInputStream(), "iso-8859-1"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String json = sb.toString();
return new JSONObject(json);
} catch (Exception e) {
Log.d(TAG, "Failed to parse the json for media list", e);
return null;
} finally {
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
Log.d(TAG, "JSON feed closed", e);
}
}
}
}
}
这个类拥有一个静态成员LinkedHashMap<String, List<Movie>> sMovieList 。 这是我们要从网络下载的数据,加载是在buildMedia方法中完成的。 该方法将按照以下步骤获取数据。
1.获取JSON数据
JSON数据从Web获取,URL定义为
VIDEO_LIST_URL = "https://raw.githubusercontent.com/corochann/AndroidTVappTutorial/master/app/src/main/assets/video_lists.json"
和
被称为buildMedia方法parseUrl(String url)方法正在访问此URL并返回JSONObject 。
2.解析JSON数据
解析JSON数据是通过使用getJSONObject , getJSONArray, getString, getInt方法等构建的JSONObject & JSONArray类来完成的。
你可以与上述代码进行比较,并通过在线JSON查看器进行JSON检查,以了解数据的解析方式。 请注意, JSONObject由{}括起来,而JSONArray由[]括起来。
3.构建Movie项目并将其放在sMovieList
sMovieList
在buildMedia方法结束时, movie实例从解析的数据创建并添加到sMovieList.
VideoProvider.java
movie = buildMovieInfo(id, categoryName, title, description, studio, videoUrl, cardImageUrl, bgImageUrl);
categoryList.add(movie);
sMovieList.put(categoryName, categoryList);
AndroidManifest
确保该应用程序有权访问互联网,否则应用程序无法从网络下载视频列表数据。
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
Build and run
video-data-from-web一旦启动应用程序后,您可以注意到,视频内容已经从上一章的实现发生了变化。 这当然是因为数据源完全从硬编码的源更改为Web上的JSON格式的源。
现在我可以随时更改/更新视频数据,而无需修改JAVA源代码。
跨应用的最佳数据管理架构?
在上一章中,我评论说Loader类是令人失望的,因为Loader实例及其成员不能与其他活动共享。 我们想使用从网页获取的视频列表数据。 但访问互联网准备数据是耗时且费用高昂的操作,我们希望尽可能少的访问网络。 那么如何通过在活动中重用数据来有效地管理数据? 该实现最初是在Google的示例源代码中完成的,我只是按照这个实现(我试着在这里解释它的含义)。
由于数据应该独立于Activity ,我们只需要一个处理/管理数据的类。 本章介绍的VideoProvider类正是这样做的。 有一个静态成员sMovieList声明为,
private static LinkedHashMap<String, List<Movie>> sMovieList;
这是我们从网上下载的数据。 一旦下载了数据,从下次调用buildMedia方法就不会访问web,而只是返回已经创建的数据
VideoProvider.java
public static LinkedHashMap<String, List<Movie>> buildMedia(Context ctx, String url)
throws JSONException {
if (null != sMovieList) {
return sMovieList;
}
...
}
data-management
Loader类仅触发数据加载的时序,实际数据由VideoProvider类进行管理。 其静态实例可以从本应用程序中的所有活动引用,我们可以在活动中访问相同的数据。
关注微信公众号,定期为你推荐移动开发相关文章。
songwenju