Android TVAndroid TVAndroidTV开发

[译]从网络加载数据 - Android TV应用手册教程十六

2017-07-03  本文已影响470人  wenju_song

版权声明:本文为博主原创翻译文章,转载请注明出处。

推荐:
欢迎关注我创建的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
上一篇下一篇

猜你喜欢

热点阅读