美文网首页Android TVAndroid TVAndroidTV开发
[译]从网络加载数据 - Android TV应用手册教程十六

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

作者: wenju_song | 来源:发表于2017-07-03 09:43 被阅读470次

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

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

    相关文章

      网友评论

      • 25e70af57acd:我也碰到同样问题,利用楼主提供的代码在做一个简单的播放系统,用真机测试的时候也报了Failed to parse the json for media list
        android.os.NetworkOnMainThreadException错误,真机的API19,屏幕分辨率是1080p的,用虚拟机测试(API22)能正常解析JSON,能播放视频,楼主给点建议,到底是怎么回事,另外整个系统没有返回或者HOME键功能?
        25e70af57acd:在什么地方加子线程去改UI楼主?希望解答下。
      • 97b76b65d856:有没有简叔在,希望解答下
      • 97b76b65d856:提示 Failed to parse the json for media list
        android.os.NetworkOnMainThreadException错误,是不是在主线程做了网络请求?
        VideoProvider中的
        201行的urlConnection.getInputStream()));
        106行的 JSONObject jsonObj = parseUrl(url);
        95行的return buildMedia(ctx, VIDEO_LIST_URL);提示NetworkOnMainThreadException

        VerticalGridActivity
        中也提示了错误
        wenju_song:@csxhn 嗯嗯,给你的那个代码是这个教程对应的代码,这一系列的教程是翻译的,你把他修改一下,着眼点是Android TV的学习。
      • 97b76b65d856:您好,昨晚看了下您给的源码,你真机测试能播放吗?我真机测试到播放哪里,黑屏1秒后回到电影列表,是不是JSON解析不对?希望指点下。
        97b76b65d856:感谢答复,不过提示 Failed to parse the json for media list
        android.os.NetworkOnMainThreadException错误,是不是在主线程做了网络请求?
        VideoProvider中的
        201行的urlConnection.getInputStream()));
        106行的 JSONObject jsonObj = parseUrl(url);
        95行的return buildMedia(ctx, VIDEO_LIST_URL);提示NetworkOnMainThreadException

        VerticalGridActivity
        中也提示了错误
        wenju_song: @csxhn 给的视频可能播不出,你可以在浏览器看一下,换一个播放地址
      • 97b76b65d856:您好,这章没有代码吗?有没有做网络请求,想看看
        97b76b65d856:十分感谢
        wenju_song:@csxhn https://github.com/corochann/AndroidTVappTutorial

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

      本文链接:https://www.haomeiwen.com/subject/cqtnfxtx.html