Статьи

Android SDK: создание приложения для сканирования книг — отображение информации о книге

С помощью библиотеки ZXing вы можете создавать приложения для Android с функцией сканирования штрих-кода. В Android SDK: создание считывателя штрих-кода мы реализовали базовое считывание штрих-кода с помощью библиотеки в простом приложении. В этой серии руководств мы будем опираться на то, что мы узнали, чтобы создать приложение, которое будет сканировать книги и извлекать связанную с ними информацию из API Google Книг.

Эта серия статей о создании приложения для сканирования книг состоит из трех частей:

Вот предварительный просмотр приложения, над которым мы работаем:

Приложение для сканирования книг

После запуска финального приложения пользователь сможет сканировать штрих-коды книг. Приложение представит подборку данных о отсканированных книгах, в том числе о том, где они доступны для покупки. Приложение также предоставит ссылку на страницу отсканированной книги в Google Книгах.


В последнем разделе мы выполнили поисковый запрос в Google Книгах, используя отсканированный номер ISBN (EAN) , а затем вернули возвращенные данные в приложение в виде строки. Теперь давайте разберем возвращаемую строку, которая является фидом JSON . Мы сделаем это в методе onPostExecute внутреннего класса AsyncTask, который мы создали. Добавьте схему метода после метода doInBackground, который мы завершили в прошлый раз.

1
2
3
protected void onPostExecute(String result) {
//parse search results
}

Когда вы анализируете фид JSON или любые другие входящие данные из внешнего источника данных, вам необходимо сначала ознакомиться со структурой входящих данных. Просмотрите разделы » Поиск» и » Объем» в документации по API Google Книг, чтобы увидеть структуру того, что будет возвращать поисковый запрос.

Мы будем использовать библиотеки Java JSON для обработки результатов поиска. Добавьте следующий импорт в ваш основной класс активности.

01
02
03
04
05
06
07
08
09
10
11
import java.io.BufferedInputStream;
import java.net.URL;
import java.net.URLConnection;
 
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
 
import android.net.Uri;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

Давайте начнем работать с результатом поиска JSON . В вашем методе onPostExecute добавьте блоки try и catch, чтобы справиться с любыми входными исключениями.

1
2
3
4
5
6
try{
//parse results
}
catch (Exception e) {
//no result
}

В блоке catch отвечайте на случаи, когда не было правильного результата поиска.

1
2
3
4
5
6
7
8
9
e.printStackTrace();
titleText.setText(«NOT FOUND»);
authorText.setText(«»);
descriptionText.setText(«»);
dateText.setText(«»);
starLayout.removeAllViews();
ratingCountText.setText(«»);
thumbView.setImageBitmap(null);
previewBtn.setVisibility(View.GONE);

Мы просто устанавливаем все элементы пользовательского интерфейса, чтобы указать, что верный результат поиска не был возвращен. Это может произойти, если отсканированная книга отсутствует в Google Книгах или что-то не так при вводе данных.

Вернувшись в блок try , установите кнопку предварительного просмотра, чтобы она была видимой, при условии, что у нас есть правильный результат:

1
previewBtn.setVisibility(View.VISIBLE);

Теперь начните извлекать объекты JSON из возвращенной строки.

1
2
JSONObject resultObject = new JSONObject(result);
JSONArray bookArray = resultObject.getJSONArray(«items»);

Как видно из структуры, описанной в инструкциях по поиску в документации по API Google Книг, результат запроса будет содержать массив с именем « items », содержащий любые результаты, соответствующие переданному номеру ISBN . Поскольку мы ожидаем только один результат для отсканированной книги, давайте получим первый элемент в массиве.

1
JSONObject bookObject = bookArray.getJSONObject(0);

Теперь нам нужен объект volumeInfo, который содержит некоторые данные, которые мы хотим отобразить.

1
JSONObject volumeObject = bookObject.getJSONObject(«volumeInfo»);

Этот объект даст нам доступ к большинству элементов данных книги, которые мы ищем.

В некоторых случаях вы обнаружите, что результаты поиска в Google Книгах будут возвращать некоторые, но не все элементы данных, поэтому мы будем использовать повторяющиеся блоки try и catch для обработки случаев, когда отдельные значения данных отсутствуют. Начните с заголовка.

1
2
3
4
5
try{ titleText.setText(«TITLE: «+volumeObject.getString(«title»));
catch(JSONException jse){
    titleText.setText(«»);
    jse.printStackTrace();
}

Если строку можно получить, мы отобразим ее в соответствующем текстовом представлении . В противном случае мы установим для него отображение пустой строки с блоком catch, позволяющим приложению продолжить обработку результатов поиска, несмотря на этот отсутствующий элемент.

Далее давайте обработаем автора, который представляется в виде массива, если их больше одного. Мы построим всю строку, используя цикл.

01
02
03
04
05
06
07
08
09
10
11
12
13
StringBuilder authorBuild = new StringBuilder(«»);
try{
    JSONArray authorArray = volumeObject.getJSONArray(«authors»);
    for(int a=0; a<authorArray.length(); a++){
        if(a>0) authorBuild.append(«, «);
        authorBuild.append(authorArray.getString(a));
    }
    authorText.setText(«AUTHOR(S): «+authorBuild.toString());
}
catch(JSONException jse){
    authorText.setText(«»);
    jse.printStackTrace();
}

Авторы будут разделены запятыми, если их несколько, в противном случае будет отображаться одно имя автора. Теперь разберем дату публикации.

1
2
3
4
5
try{ dateText.setText(«PUBLISHED: «+volumeObject.getString(«publishedDate»));
catch(JSONException jse){
    dateText.setText(«»);
    jse.printStackTrace();
}

Далее мы обработаем описание.

1
2
3
4
5
try{ descriptionText.setText(«DESCRIPTION: «+volumeObject.getString(«description»));
catch(JSONException jse){
    descriptionText.setText(«»);
    jse.printStackTrace();
}

Теперь давайте разберемся со звездным рейтингом. Помните, что мы создали макет и объявили массив для хранения звездных изображений . Вернитесь к вашему методу onCreate на мгновение и создайте экземпляр массива после существующего кода.

1
2
3
4
starViews=new ImageView[5];
for(int s=0; s<starViews.length; s++){
    starViews[s]=new ImageView(this);
}

Может быть максимум пять звезд. Вернитесь в onPostExecute после последнего блока catch, который мы добавили для описания, добавьте блоки try и catch для звездочек.

1
2
3
4
5
6
7
try{
//set stars
}
catch(JSONException jse){
    starLayout.removeAllViews();
    jse.printStackTrace();
}

Когда мы получим количество звездочек, присвоенных книге, мы динамически добавим представления изображений в макет, который мы создали для рейтинга звезд. Если звездный рейтинг не может быть получен, мы удалим все ранее добавленные представления в блоке catch . В этом блоке try извлекают количество звездочек из возвращенного JSON и приводят его к целому числу.

1
2
double decNumStars = Double.parseDouble(volumeObject.getString(«averageRating»));
int numStars = (int)decNumStars;

Установите количество звездочек, отображаемых в данный момент в качестве тега объекта макета. Мы будем использовать это позже, когда будем иметь дело с сохранением состояния приложения. Удалите все существующие виды из макета для повторного сканирования.

1
2
starLayout.setTag(numStars);
starLayout.removeAllViews();

Теперь мы можем просто зациклить, чтобы добавить соответствующее количество звездочек.

1
2
3
4
for(int s=0; s<numStars; s++){
    starViews[s].setImageResource(R.drawable.star);
    starLayout.addView(starViews[s]);
}

Помните, что в прошлый раз мы добавили изображение звезды в приложение drawables. После блока «Звездная секция» мы можем иметь дело с подсчетом рейтинга.

1
2
3
4
5
try{ ratingCountText.setText(» — «+volumeObject.getString(«ratingsCount»)+» ratings»);
catch(JSONException jse){
    ratingCountText.setText(«»);
    jse.printStackTrace();
}

Теперь давайте проверим, есть ли у книги предварительный просмотр в Google Книгах. Установите кнопку предварительного просмотра, чтобы включить или отключить соответственно.

01
02
03
04
05
06
07
08
09
10
try{
    boolean isEmbeddable = Boolean.parseBoolean
        (bookObject.getJSONObject(«accessInfo»).getString(«embeddable»));
    if(isEmbeddable) previewBtn.setEnabled(true);
    else previewBtn.setEnabled(false);
}
catch(JSONException jse){
    previewBtn.setEnabled(false);
    jse.printStackTrace();
}

Мы будем иметь дело с тем, что происходит при нажатии кнопки предварительного просмотра позже. Затем получите URL-адрес страницы книги в Google Книгах и установите его в качестве тега для кнопки ссылки.

1
2
3
4
5
6
7
8
try{
    linkBtn.setTag(volumeObject.getString(«infoLink»));
    linkBtn.setVisibility(View.VISIBLE);
}
catch(JSONException jse){
    linkBtn.setVisibility(View.GONE);
    jse.printStackTrace();
}

Мы осуществим клики по этому позже. Единственный оставшийся фрагмент информации, который мы все еще хотим обработать, — это миниатюрное изображение, которое немного сложнее. Мы хотим использовать дополнительный класс AsyncTask для извлечения его в фоновом режиме.


После класса « GetBookInfo » добавьте еще один для получения эскиза книги.

1
2
3
private class GetBookThumb extends AsyncTask<String, Void, String> {
//get thumbnail
}

Мы передадим миниатюрную строку URL-адреса из другой AsyncTask, когда получим ее из JSON . Внутри этого нового класса AsyncTask добавьте метод doInBackground .

1
2
3
4
@Override
protected String doInBackground(String… thumbURLs) {
//attempt to download image
}

Внутри doInBackground добавьте блоки try и catch в случае исключений ввода-вывода.

1
2
3
4
5
6
try{
//try to download
}
catch(Exception e) {
    e.printStackTrace();
}

В блоке try попытайтесь установить соединение, используя переданный миниатюрный URL.

1
2
3
URL thumbURL = new URL(thumbURLs[0]);
URLConnection thumbConn = thumbURL.openConnection();
thumbConn.connect();

Теперь получите входные и буферизованные потоки.

1
2
InputStream thumbIn = thumbConn.getInputStream();
BufferedInputStream thumbBuff = new BufferedInputStream(thumbIn);

Мы хотим перенести изображение в приложение в виде растрового изображения, поэтому добавьте новую переменную экземпляра вверху объявления класса активности.

1
private Bitmap thumbImg;

Вернувшись в doInBackground для класса « GetBookThumb », после создания буферизованного входного потока считайте изображение в это растровое изображение.

1
thumbImg = BitmapFactory.decodeStream(thumbBuff);

Закройте потоки.

1
2
thumbBuff.close();
thumbIn.close();

После блока catch верните пустую строку для завершения метода.

1
return «»;

После doInBackground добавьте метод onPostExecute, чтобы показать загруженное изображение.

1
2
3
protected void onPostExecute(String result) {
    thumbView.setImageBitmap(thumbImg);
}

Теперь давайте соберем вместе эти нити. В вашем методе onActivityResult после строки, в которой вы создали строку поискового запроса, создайте и выполните экземпляр класса AsyncTask для извлечения результатов поиска.

1
new GetBookInfo().execute(bookSearchString);

В методе onPostExecute класса « GetBookInfo » после блока catch для кнопки ссылки добавьте последний раздел для извлечения эскиза с использованием данных JSON и другого класса AsyncTask .

1
try{ JSONObject imageInfo = volumeObject.getJSONObject(«imageLinks»);

Давайте разберемся с кнопками предварительного просмотра и ссылки. Начните с кнопки ссылки. Мы уже установили класс в качестве прослушивателя щелчков для кнопок, поэтому добавьте новый раздел в метод onClick после условия для кнопки сканирования.

1
2
3
4
5
6
7
8
else if(v.getId()==R.id.link_btn){
    //get the url tag
    String tag = (String)v.getTag();
    //launch the url
    Intent webIntent = new Intent(Intent.ACTION_VIEW);
    webIntent.setData(Uri.parse(tag));
    startActivity(webIntent);
}

Этот код извлекает URL из тега кнопки, который мы установили при обработке JSON . Затем код запускает ссылку в браузере.

Для запуска предварительного просмотра нам нужен номер ISBN книги. Вернитесь к методу onActivityResult на мгновение. Добавьте строку перед строкой, в которой вы создали строку поискового запроса. Установите отсканированный ISBN в качестве тега для кнопки предварительного просмотра.

1
previewBtn.setTag(scanContent);

Теперь вернитесь в onClick и добавьте условие для кнопки предварительного просмотра, в которой вы получите тег.

1
2
3
4
else if(v.getId()==R.id.preview_btn){
    String tag = (String)v.getTag();
    //launch preview
}

Мы собираемся создать новый вид деятельности для предварительного просмотра книги. Добавьте новый класс в свой проект в том же пакете, что и основной вид деятельности, назвав его « EmbeddedBook ». Дайте вашему классу следующую схему.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
 
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
 
public class EmbeddedBook extends Activity {
 
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

Создайте новый файл макета в папке « res / layout » вашего приложения, присвоив ему имя « embedded_book.xml ». Введите следующий макет.

1
2
3
4
<WebView xmlns:android=»http://schemas.android.com/apk/res/android»
    android:id=»@+id/embedded_book_view»
    android:layout_width=»fill_parent»
    android:layout_height=»fill_parent» />

Мы представим предварительный просмотр книги в WebView . Вернувшись в новый класс, установите его как представление содержимого в onCreate после существующего кода.

1
setContentView(R.layout.embedded_book);

Добавьте переменную экземпляра вверху класса.

1
private WebView embedView;

В onCreate получите ссылку на WebView, который мы включили в макет.

1
embedView = (WebView)findViewById(R.id.embedded_book_view);

Установите JavaScript для включения.

1
embedView.getSettings().setJavaScriptEnabled(true);

Когда мы запустим действие, мы передадим ISBN. Получите эти переданные данные далее в onCreate .

1
Bundle extras = getIntent().getExtras();

Теперь проверьте, есть ли у нас какие-либо дополнения.

1
2
3
if(extras !=null) {
//get isbn
}

Внутри условно попытайтесь получить ISBN.

1
String isbn = extras.getString(«isbn»);

Мы собираемся включить содержимое WebView в файл ресурсов в формате HTML. Добавить попробуйте и поймать блоки для загрузки.

1
2
3
4
5
6
7
8
9
try{
//load asset
}
catch(IOException ioe){
    embedView.loadData
    («<html><head></head><body>Whoops! Something went wrong.</body></html>»,
        «text/html», «utf-8»);
    ioe.printStackTrace();
}

Если файл не может быть загружен, мы выведем сообщение об ошибке. Мы скоро вернемся к блоку try . Сначала создайте новый файл в папке « assets » вашего приложения, назвав его « embedded_book_page.html ». Найдите новый файл в проводнике пакетов, щелкните правой кнопкой мыши и выберите « Открыть с помощью », а затем « Текстовый редактор ». Введите следующий код.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<html xmlns=»http://www.w3.org/1999/xhtml»>
    <head>
        <meta http-equiv=»content-type» content=»text/html; charset=utf-8″/>
        <title>Google Books Embedded Viewer API Example</title>
        <script type=»text/javascript» src=»https://www.google.com/jsapi»></script>
        <script type=»text/javascript»>
        google.load(«books», «0»);
 
        function initialize() {
            var viewer = new google.books.DefaultViewer(document.getElementById(‘viewerCanvas’));
            viewer.load(‘ISBN:$ISBN$’);
        }
 
        google.setOnLoadCallback(initialize);
        </script>
    </head>
    <body>
        <div id=»viewerCanvas» style=»width: 600px; height: 500px»></div>
    </body>
</html>

Это стандартный код разметки, используемый для отображения предварительного просмотра Google Книг с помощью API Embedded Viewer . Обратите внимание, что страница содержит « $ ISBN $ ». Мы будем использовать это для передачи номера ISBN из действия.

Вернитесь к блоку try в своем классе » EmbeddedBook «. Попробуйте загрузить страницу, используя следующий алгоритм.

1
2
3
4
5
6
7
8
InputStream pageIn = getAssets().open(«embedded_book_page.html»);
BufferedReader htmlIn = new BufferedReader(new InputStreamReader(pageIn));
StringBuilder htmlBuild = new StringBuilder(«»);
String lineIn;
while ((lineIn = htmlIn.readLine()) != null) {
    htmlBuild.append(lineIn);
}
htmlIn.close();

Затем передайте ISBN, заменив раздел, который мы включили для него в HTML.

1
String embeddedPage = htmlBuild.toString().replace(«$ISBN$», isbn);

Завершите новое действие, загрузив страницу.

1
embedView.loadData(embeddedPage, «text/html», «utf-8»);

Вернувшись в метод onClick основного класса действий, в условный блок, который вы добавили для кнопки предварительного просмотра, после строки, в которой вы получили тег, запустите новое действие, передав ISBN.

1
2
3
Intent intent = new Intent(this, EmbeddedBook.class);
intent.putExtra(«isbn», tag);
startActivity(intent);

Добавьте новый класс в файл манифеста приложения после основного элемента активности.

1
<activity android:name=»com.example.barcodescanningapp.EmbeddedBook» />

При необходимости измените имя пакета, чтобы оно соответствовало вашему.


Вы можете запустить приложение на этом этапе, и оно должно функционировать. Однако давайте сначала позаботимся об изменении состояния приложения. В своем основном классе деятельности добавьте следующий метод.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
protected void onSaveInstanceState(Bundle savedBundle) {
    savedBundle.putString(«title», «»+titleText.getText());
    savedBundle.putString(«author», «»+authorText.getText());
    savedBundle.putString(«description», «»+descriptionText.getText());
    savedBundle.putString(«date», «»+dateText.getText());
    savedBundle.putString(«ratings», «»+ratingCountText.getText());
    savedBundle.putParcelable(«thumbPic», thumbImg);
    if(starLayout.getTag()!=null)
        savedBundle.putInt(«stars», Integer.parseInt(starLayout.getTag().toString()));
    savedBundle.putBoolean(«isEmbed», previewBtn.isEnabled());
    savedBundle.putInt(«isLink», linkBtn.getVisibility());
    if(previewBtn.getTag()!=null)
        savedBundle.putString(«isbn», previewBtn.getTag().toString());
}

Мы не будем вдаваться в подробности о сохранении состояния, потому что это не главное в этом уроке. Посмотрите код, чтобы убедиться, что вы его понимаете. Мы просто сохраняем отображаемые данные, чтобы сохранить их при изменении состояния приложения. В onCreate после существующего кода получите эту информацию.

1
if (savedInstanceState != null){ authorText.setText(savedInstanceState.getString(«author»));

Опять же, мы не будем вдаваться в подробности по этому вопросу. Код просто выполняет то, что код делает сразу после сканирования. В этом случае он следует за изменением состояния. Расширьте открывающий тег вашего основного элемента активности в манифесте проекта для обработки изменений конфигурации.

1
android:configChanges=»orientation|keyboardHidden|screenSize»

Это завершает приложение! Вы должны быть в состоянии запустить его в этот момент. Когда он запустится, нажмите кнопку и отсканируйте книгу. Если книга указана в Google Книгах, соответствующая информация должна появиться в интерфейсе. Если некоторые элементы данных отсутствуют, другие приложения должны по-прежнему отображать. Если доступен предварительный просмотр, кнопка предварительного просмотра должна быть включена и должна запускать действие встроенной книги.

Book Scanner App Предварительный просмотр

В этой серии мы использовали Android SDK: создание сканера штрих-кода для создания приложения для сканирования книг в сочетании с библиотекой сканирования ZXing и API Google Книг. Поэкспериментируйте с приложением, пытаясь извлекать и отображать различные элементы данных из возвращенного канала JSON или реагируя на сканирование различных типов штрих-кодов, а не только EAN . Использование внешних ресурсов через API-интерфейсы является ключевым навыком в любой дисциплине разработки, так как позволяет максимально эффективно использовать существующие данные и функциональные возможности, чтобы сосредоточиться на уникальных деталях ваших собственных приложений.