Android Java:ListView 显示来自 API 查询的所有响应时出现问题

Android Java: Trouble With ListView Showing All Responses From API Query

我的应用程序出现问题,该应用程序应该接受用户文本输入并搜索 Google 图书 API。到目前为止,我能够成功接收用户输入,将其添加到查询字符串并启动 AsyncTask,启动 HttpRequest,获取 inputStream 并从中构建一个字符串,从该字符串创建一个项目数组,然后使用自定义适配器在屏幕上为用户显示该图书列表。

我遇到的问题是,当代码运行时(无论用户输入什么,无论是食物、希腊语、android,等等)我只在屏幕上看到一个响应。我不明白为什么它只显示一本书。 API 查询字符串指定 10(我也使用了 2、4 和 7)作为最大结果,但到目前为止我只得到了 1 个列表。在使用调试器检查后,我将范围缩小到 Stringbuilder 在一定长度的输入后停止,或者我的数组列表没有为 JSON 对象的其余部分创建其他索引。

下面是本书 Activity 代码:

package com.ovidioreyna.android.googlebooksapiapp;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.widget.ListView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;

import static com.ovidioreyna.android.googlebooksapiapp.MainActivity.LOG_TAG;


public class BooksActivity extends AppCompatActivity {

    //Creates blank global string which will be used the search parameters in this class
    public String gglBookSearch = "https://www.googleapis.com/books/v1/volumes?q=";

    //After the user enters what they want to search for we pass that in from the main activity
    //and add it to our global variable here. We then add some modifiers so that we can control
    //the amount of info being return. This will later be changed to 10 once the app is functioning

    Context context = this;

    String jsonResponse = "";

    ListView booksListView;
    BookAdapter adapter;

    //inflates the book_layout.xml so we can see the book info
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.book_list);

        //passes the user input to this activity
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            String textSearchUpdate = extras.getString("EDITTEXTUPDATE");
            //takes user input and appends it to the initial Google Books API Query
            updateApiSearch(textSearchUpdate);
        }

        //creates an instance of BooksAsynTask and executes it
        BooksAsyncTask task = new BooksAsyncTask();
        task.execute();
    }

    //Sets the appropriate info to the appropriate textviews
    private void updateUi(ArrayList<Books> books) {

        //creates an instance of the book list
        booksListView = (ListView) findViewById(R.id.book_list);

        //creates an instance of the book adapter
        adapter = new BookAdapter(context, new ArrayList<Books>());

        //sets the adapter to the listview.xml
        booksListView.setAdapter(adapter);

        //in case user initiates multiple searches then we need to clear the adapter to start with
        //clean adapter for the new search
        adapter.clear();
        adapter.addAll(books);
        adapter.notifyDataSetChanged();
    }


    private class BooksAsyncTask extends AsyncTask<URL, Void, ArrayList<Books>> {
        @Override
        protected ArrayList<Books> doInBackground(URL... urls) {
            //creates a new url object from the @gglBookSearch query
            URL url = createUrl(gglBookSearch);

            try {
                //creates a string that will hold the raw data for the jsonarray
                jsonResponse = makeHttpRequest(url);
                Log.i(LOG_TAG, "JSON Results: " + jsonResponse);
            } catch (IOException e) {
                Log.e(LOG_TAG, "Error with make request", e);
            }

            //takes the raw string data and extracts it into an array
            return extractBooks(jsonResponse);
        }

        @Override
        protected void onPostExecute(ArrayList<Books> books) {
            //checks to make sure that the arraylist books is not empty
            if (books == null) {
                return;
            }
            //if all goes well and books is populated then it displays that info
            updateUi(books);
        }

        private URL createUrl(String stringUrl) {
            //creates a new url object from the string passed in
            URL url = null;
            try {
                url = new URL(stringUrl);
            } catch (MalformedURLException exception) {
                Log.e(LOG_TAG, "Error with creating URL", exception);
            }

            return url;
        }

        public ArrayList<Books> extractBooks(String jsonResponse) {

            //craetes a new Arraylist that will hold all the json data
            ArrayList<Books> books = new ArrayList<>();

            //checks to see that the string passed in is not empty
            if (TextUtils.isEmpty(jsonResponse)) {
                return null;
            }
            try {
                //creates a jsonObject to hold the data. Then delves into the Array items
                JSONObject baseJsonResponse = new JSONObject(jsonResponse);
                JSONArray itemsArray = baseJsonResponse.getJSONArray("items");
                Log.i(LOG_TAG, "Length of books array: " + itemsArray.length());

                //create empty local variables in case the JSON objects are null
                String bookTitle = "";
                String bookSubtitle = "";
                String author = "";
                String publishedDate = "";

                for (int i = 0; i < itemsArray.length(); i++) {
                    //checks to see that the Array isn't empty
                    if (itemsArray.length() > 0) {
                        JSONObject firstItem = itemsArray.getJSONObject(i);

                        JSONObject volumeInfo = firstItem.getJSONObject("volumeInfo");

                        //checks to see if object is null, if not then executes code
                        if (!volumeInfo.isNull("title")) {
                            bookTitle = volumeInfo.getString("title");
                        }

                        //checks to see if object is null, if not then executes code
                        if (!volumeInfo.isNull("subtitle")) {
                            bookSubtitle = volumeInfo.getString("subtitle");
                        }

                        //creates an jsonArray object from the authors array
                        JSONArray authorArray = volumeInfo.getJSONArray("authors");
                        if (!authorArray.isNull(i)) {
                            if (authorArray.length() == 1) {
                                author = authorArray.getString(i);
                            } else {
                                for (int k = 0; k < authorArray.length(); k++) {
                                    //checks to see if object is null, if not then executes code
                                    if (!authorArray.isNull(k)) {
                                        author = authorArray.getString(k) + ",";
                                    }
                                }
                            }
                        }

                        //checks to see if object is null, if not then executes code
                        if (!volumeInfo.isNull("publishedDate"))
                            publishedDate = volumeInfo.getString("publishedDate");

                        //create a new instance of the Books class
                        Books book = new Books(bookTitle, bookSubtitle, author, publishedDate);
                        books.add(book);
                    }

                    return books;
                }


            } catch (JSONException e) {
                Log.e(LOG_TAG, "Problem with parsing the books JSON results", e);
            }
            //if there is an error in the code above it returns null
            return null;
        }

        private String makeHttpRequest(URL url) throws IOException {


            //checks to see that the url object passed in is not empty. If empty then returns an
            //empty string
            if (url == null) {
                return jsonResponse = "";
            }
            //creates two empty objects
            HttpURLConnection urlConnection = null;
            InputStream inputStream = null;

            //this is where we try to establish a connection and if successful take the input info
            //and convert it into a usable form
            try {
                urlConnection = (HttpURLConnection) url.openConnection();
                urlConnection.setRequestMethod("GET");
                urlConnection.setReadTimeout(10000 /* milliseconds */);
                urlConnection.setConnectTimeout(15000 /* milliseconds */);
                urlConnection.connect();

                if (urlConnection.getResponseCode() == 200) {
                    //if we get a success code from the server we proceed to convert the bits and
                    //bytes into a usuable form and ultimately convert it into a usable string.
                    inputStream = urlConnection.getInputStream();
                    jsonResponse = readFromStream(inputStream);
                } else {
                    //if we don't get a success code from the server we log it.
                    Log.e(LOG_TAG, "Error Response Code: " + urlConnection.getResponseCode());
                }
            } catch (IOException e) {
                Log.e(LOG_TAG, "Problem With Connection: ", e);
            } finally {
                //regardless if we are successful gettting an inputstream or not we disconnect and
                //close the inputstream
                if (urlConnection != null) {
                    urlConnection.disconnect();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            }

            //returns the string @jsonResponse whether it's empty or has information
            return jsonResponse;
        }

        //uses StringBuilder to take the raw bits and make a legible string from it
        private String readFromStream(InputStream inputStream) throws IOException {
            StringBuilder output = new StringBuilder();
            if (inputStream != null) {
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
                BufferedReader reader = new BufferedReader(inputStreamReader);
                String line = reader.readLine();
                while (line != null) {
                    output.append(line);
                    line = reader.readLine();
                }
            }

            //once the entire input stream has been built we output it to a string
            return output.toString();
        }
    }

    //takes the input text from the user and concatonates it to the query as well as adding a limit
    //of 10 books at a time
    public void updateApiSearch(String updatedEditText) {
        gglBookSearch += updatedEditText;
        gglBookSearch += "&maxResults=10";
    }
}

这里是整个仓库的 link 如果有人想弄乱整个代码:

https://github.com/ovidioreyna/GoogleBooksAPIApp

在此先感谢您的帮助

更新:

所以我创建了一个日志事件来显示 jsonResponse = makeHttpRequest(url); 之后的 JSON 结果,日志显示:

10-09 20:50:36.846 32119-32463/com.ovidioreyna.android.googlebooksapiapp I/MainActivity: JSON Results: { "kind": "books#volumes", "totalItems": 1431, "items": [  {   "kind": "books#volume",   "id": "vdXmDAAAQBAJ",   "etag": "pSHAL8HBKSs",   "selfLink": "https://www.googleapis.com/books/v1/volumes/vdXmDAAAQBAJ",   "volumeInfo": {    "title": "Molly on the Range",    "subtitle": "Recipes and Stories from an Unlikely Life on a Farm",    "authors": [     "Molly Yeh"    ],    "publisher": "Rodale",    "publishedDate": "2016-10-04",    "description": "In 2013, food blogger and classical musician Molly Yeh left Brooklyn to live on a farm on the North Dakota-Minnesota border, where her fiancé was a fifth-generation Norwegian-American sugar beet farmer. Like her award-winning blog My Name is Yeh, Molly on the Range chronicles her life through photos, more than 120 new recipes, and hilarious stories from life in the city and on the farm. Molly’s story begins in the suburbs of Chicago in the 90s, when things like Lunchables and Dunkaroos were the objects of her affection; continues into her New York years, when Sunday mornings meant hangovers and bagels; and ends in her beloved new home, where she’s currently trying to master the art of the hotdish. Celebrating Molly's Jewish/Chinese background with recipes for Asian Scotch Eggs and Scallion Pancake Challah Bread and her new hometown Scandinavian recipes for Cardamom Vanilla Cake and Marzipan Mandel Bread, Molly on the Range will delight everyone, from longtime readers to those discovering her glorious writing and recipes for the first time.",    "industryIdentifiers": [     {      "type": "ISBN_13",      "identifier": "9781623366957"     },     {      "type": "ISBN_10",      "identifier": "162336695X"     }    ],    "readingModes": {     "text": false,     "image": false    },    "pageCount": 304,    "printType": "BOOK",    "categories": [     "Cooking"    ],    "maturityRating": "NOT_MATURE",    "allowAnonLogging": false,    "contentVersion": "1.1.1.0.preview.0",    "imageLinks": {     "smallThumbnail": "http://books.google.com/books/content?id=vdXmDAAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",     "thumbnail": "http://books.google.com/books/content?id=vdXmDAAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"    },    "language": "en",    "previewLink": "http://books.google.com/books?id=vdXmDAAAQBAJ&printsec=frontcover&dq=Food&hl=&cd=1&source=gbs_api",    "infoLink": "http://books.google.com/books?id=vdXmDAAAQBAJ&dq=Food&hl=&source=gbs_api",    "canonicalVolumeLink": "http://books.google.com/books/about/Molly_on_the_Range.html?hl=&id=vdXmDAAAQBAJ"   },   "saleInfo": {    "country": "US",    "saleability": "NOT_FOR_SALE",    "isEbook": false   },   "accessInfo": {    "country": "US",    "viewability": "PARTIAL",    "embeddable": true,    "publicDomain": false,    "textToSpeechPermission": "ALLOWED",    "epub": {     "isAvailable": false    },    "pdf": {     "isAvailable": false    },    "webReaderLink": "http://books.google.com/books/reader?id=vdXmDAAAQBAJ&hl=&printsec=frontcover&output=reader&source=gbs_api",    "accessViewStatus": "SAMPLE",    "quoteSharingAllowed": false   },   "searchInfo": {    "textSnippet": "Celebrating Molly&#39;s Jewish/Chinese background with recipes for Asian Scotch Eggs and Scallion Pancake Challah Bread and her new hometown Scandinavian recipes for Cardamom Vanilla Cake and Marzipan Mandel Bread, Molly on the Range will ..."   }  },  {   "kind": "books#volume",   "id": "DY-dBAAAQBAJ",   "etag": "t4JLTMFKOGc",   "selfLink": "https://www.googleapis.com/books/v1/volumes/DY-dBAAAQBAJ",   "volumeInfo": {    "title": "Real Baby Food",    "subtitle": "Easy, All-Natural Recipes for Your Baby and Toddler",    "authors": [     "Jenna Helwig"    ],    "publisher": "Houghton Mifflin Harcourt",    "publishedDate": "2015-04-28",    "description": "Simple Recipes for a Wholesome Start Nothing compares with making your own baby food: It's fresh and unprocessed, you choose what goes into it, and it is a delicious way to introduce your child to a world of flavors. In Real Baby Foo
10-09 20:50:36.861 32119-32463/com.ovidioreyna.android.googlebooksapiapp I/MainActivity: Length of books array: 10

我还添加了一个日志事件来显示数组的长度,它显示为 10。这很奇怪,因为只有两个实例是从 HttpRequest 方法写入的。

您在 for 循环中过早归还了书籍。在 BooksActivity 的第 144-186 行中仔细查看。

            for (int i = 0; i < itemsArray.length(); i++) {
                //checks to see that the Array isn't empty
                if (itemsArray.length() > 0) {
                    JSONObject firstItem = itemsArray.getJSONObject(i);

                    JSONObject volumeInfo = firstItem.getJSONObject("volumeInfo");

                    //checks to see if object is null, if not then executes code
                    if (!volumeInfo.isNull("title")) {
                        bookTitle = volumeInfo.getString("title");
                    }

                    //checks to see if object is null, if not then executes code
                    if (!volumeInfo.isNull("subtitle")) {
                        bookSubtitle = volumeInfo.getString("subtitle");
                    }

                    //creates an jsonArray object from the authors array
                    JSONArray authorArray = volumeInfo.getJSONArray("authors");
                    if (!authorArray.isNull(i)) {
                        if (authorArray.length() == 1) {
                            author = authorArray.getString(i);
                        } else {
                            for (int k = 0; k < authorArray.length(); k++) {
                                //checks to see if object is null, if not then executes code
                                if (!authorArray.isNull(k)) {
                                    author = authorArray.getString(k) + ",";
                                }
                            }
                        }
                    }

                    //checks to see if object is null, if not then executes code
                    if (!volumeInfo.isNull("publishedDate"))
                        publishedDate = volumeInfo.getString("publishedDate");

                    //create a new instance of the Books class
                    Books book = new Books(bookTitle, bookSubtitle, author, publishedDate);
                    books.add(book);
                }
 //See that? You return your books with only one extracted from your itemArray!!!!
                return books;
            }