Для работы многих программ нужно обращаться к данным. Однако далеко не все данные могут храниться на устройстве, на котором запускается программа. Зачастую необходимо получать информацию из интернета. Например, запросить погоду по населённому пункту, чтобы отобразить её в программе. Причём большинство используемых приложений – мобильные. Как получить удалённую информацию с телефона Android? С помощью обращения к специальным API.
Содержание:
API
API (Application Programming Interface) – это то, как взаимодействуют друг с другом программы. Например, вы пишете программу, которая должна создавать Word-документ. Вы обращаетесь к библиотеке, которая вызывает API Word-а. Таким образом, ваша программа взаимодействует с его API.
Или, что ближе к теме статьи, вы пишете программу, которая будет отображать ближайшие матчи в Английской Премьер-Лиге. Для того, чтобы получать информацию о матчах, вы делаете запросы к API в интернете. Или вы хотите в рамках вашей программы делать запросы к GPT, парсить их и отображать пользователю вашей программы. Как это? Говоря просто, сделать запрос к API означает открыть ссылку, на которой расположен ресурс с этим API, и при необходимости передать параметры запроса – например, за какие сезоны нужно получить футбольные матчи или на какой вопрос должна ответить GPT. Как правило, это get-запросы.
Если вы нажмёте сюда, то попадёте на страничку API, который выдаёт случайные цитаты.
JSON
JSON расшифровывается как JavaScript Object Notation. Это текстовый формат, в котором передаются данные. JSON состоит из ключей (имён полей) и значений. Выглядит это так – “ключ”: “значение”.
Нагляднее всего вам покажет пример. Если вы перешли по ссылке на API, то увидели в окне браузера некоторый текст. Это и есть JSON, то есть объект с ответом на ваш запрос. Он содержит информацию о случайной цитате.
Вот так будет выглядеть ответ в формате JSON, если мы самостоятельно, не в рамках программы, вставим ссылку для доступа к API в адресную строку браузера.
Программа будет делать то же самое – обращаться к этой ссылке в надежде получить JSON, чтобы затем отобразить цитату и её автора в программе. Для расшифровки ответа в формате JSON мы будем использовать библиотеку Gson.
Gson
Библиотека Gson позволяет распарсить ответ, который пришёл в JSON, в удобный и понятный java-объект. Для сборщика проектов gradle нужно вставить следующую строку в блок dependencies в build.gradle:
1 |
implementation 'com.google.code.gson:gson:2.8.9' |
Хорошо. Теперь необходимо написать тот самый java-объект, в который будет расшифровываться ответ. Это несложно – просто выделим данные, которые нас интересуют, и определим их как поля класса. А также добавим геттеры и конструктор. В JSON нас интересуют текст и автор цитаты, поэтому названия полей мы берём такие же, какими они прописаны в самом JSON: quoteText и quoteAuthor. Также добавим метод getFormattedAuthor(), чтобы возвращать автора цитаты заранее с приставкой (с). А если автор неизвестен – такое тоже бывает в этом API – то возвращать пустую строку.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class QuoteResponse { private final String quoteText; private final String quoteAuthor; public QuoteResponse(String quoteText, String quoteAuthor) { this.quoteText = quoteText; this.quoteAuthor = quoteAuthor; } public String getQuoteText() { return quoteText; } public String getQuoteAuthor() { return quoteAuthor; } public String getFormattedAuthor() { if (quoteAuthor.isEmpty()) { return ""; } else { return "(c) " + quoteAuthor; } } } |
Как видите, самый обычный java-объект.
Разрешение на интернет
На Android всё устроено очень строго. Если хотите воспользоваться интернетом – то есть сделать запрос к API, находящемуся в интернете – то необходимо прописать соответствующее разрешение. Дополнить будет нужно 2 файла.
Во-первых, нужно добавить следующую строчку в файл AndroidManifest.xml:
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.androidapp.example"> <uses-permission android:name="android.permission.INTERNET"/> <application ... </application> </manifest> |
Отлично. А теперь вам нужно добавить ещё много строк в MainActivity.java. Это связано с тем, что разрешение, прописанное в AndroidManifest.xml, не всегда корректно может быть получено. Иногда сам пользователь его сразу не разрешил. Иногда – что-то другое. Поэтому, чтобы приложение не село в лужу после первой неудачи, превращаем MainActivity.java в сборник просьб выдать разрешение на интернет:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
public class MainActivity extends AppCompatActivity { ... private static final int PERMISSION_REQUEST_CODE = 1; private static final String INTERNET_PERMISSION = Manifest.permission.INTERNET; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... if (isInternetPermissionGranted()) { ... } else { requestInternetPermission(); } } /** * проверка наличия разрешения на интернет */ private boolean isInternetPermissionGranted() { int permissionResult = ActivityCompat.checkSelfPermission(this, INTERNET_PERMISSION); return permissionResult == PackageManager.PERMISSION_GRANTED; } /** * запросить разрешение на интернет */ private void requestInternetPermission() { String[] PERMISSIONS = {INTERNET_PERMISSION}; ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_REQUEST_CODE); } /** * метод-обработчик события, когда пользователь нажимает * в диалоговом окне выдачи прав "разрешить" или "запретить" */ @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (isPermissionForInternetGranted(requestCode, permissions, grantResults)) { startInternetThread(); } else { Toast.makeText(this, "Вы не выдали разрешение", Toast.LENGTH_SHORT).show(); } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } /** * проверить, выдано ли было разрешение на интернет */ private boolean isPermissionForInternetGranted(int requestCode, String[] permissions, int[] grantResults) { if (requestCode != PERMISSION_REQUEST_CODE) { return false; } if (permissions.length != 1 || grantResults.length != 1) { return false; } if (!permissions[0].equals(INTERNET_PERMISSION)) { return false; } return grantResults[0] == PackageManager.PERMISSION_GRANTED; } ... } |
Отлично. Разрешения прописаны. Что делаем дальше?
Разметка интерфейса
Что самое важное в мобильном приложении? Функциональность? Скорость работы? Ха! Чтобы симпатично было.
Давайте настроим графические компоненты в файле разметки интерфейса main_activity.xml.
Разметка интерфейса включает в себя 2 элемента TextView. Они позволяют отобразить сам текст цитаты и её автора под цитатой.
Чтобы центрировать не один, а оба эти элемента в совокупности, они обёрнуты в LinearLayout, который размещается в центре в ConstraintLayout.
Шрифт для цитаты я подобрала самый обычный serif. А вот шрифт для подписи автора решила взять рукописный caveat – его нужно установить в проект с помощью среды разработки. В случае с IntelliJ IDEA это сделать весьма просто.
Выделив нужный TextView, во вкладке Design при редактировании файла activity_main.xml необходимо выбрать параметр fontFamily, раскрыть выпадающий список и кликнуть More Fonts.
Затем ищете подходящий шрифт, выделяете его и нажимаете OK (переключатель должен стоять на Create downloadable font). Всё, шрифт будет установлен, он будет доступен в проекте и для других текстов. Для выбранного TextView он даже пропишется самостоятельно в параметры в xml.
Вот получившийся код в файле activity_main.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/mainLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingVertical="20dp" android:paddingHorizontal="30dp"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"> <TextView android:id="@+id/textViewQuote" android:text="Цитата цитата цитата цитата цитата цитата цитата" android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="serif" android:textColor="@color/black" android:textSize="26sp"/> <TextView android:id="@+id/textViewAuthor" android:text="(с) Автор" android:lineSpacingMultiplier="0.8" android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="@font/caveat" android:textColor="@color/black" android:layout_marginTop="16dp" android:textSize="24sp"/> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout> |
Интерфейс готов! Можно приступать к основной части – к отправке запросов и обработке ответов.
OkHttp3
Чтобы воспользоваться библиотекой для отправки http-запросов, необходимо её добавить в проект.
Давайте подключим эту библиотеку. Код для gradle:
1 |
implementation 'com.squareup.okhttp3:okhttp:4.9.1' |
Превосходно. Теперь будем заниматься запросами. Соорудим класс QuoteApi, который позволит получить цитату get-запросом к API с помощью библиотеки OkHttp3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package com.androidapp.example; import com.google.gson.Gson; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; /** * класс для отправки запроса к внешнему API * для получения цитаты */ public class QuoteApi { private OkHttpClient client; public QuoteApi() { this.client = new OkHttpClient(); } public QuoteResponse sendForQuote() throws ApiException { Request request = createRequest(); String responseBody = sendRequestForResponseBody(request); return convertResponseBodyToQuote(responseBody); } private Request createRequest() { return new Request.Builder() .url("https://api.forismatic.com/api/1.0/?method=getQuote&lang=ru&format=json") .build(); } private String sendRequestForResponseBody(Request request) throws ApiException { try (Response response = client.newCall(request).execute()) { if (response.isSuccessful() && response.body() != null) { return response.body().string(); } else { throw new ApiException("Цитаты не будет"); } } catch (IOException ex) { throw new ApiException("Цитаты не будет"); } } private QuoteResponse convertResponseBodyToQuote(String body) throws ApiException { Gson gson = new Gson(); return gson.fromJson(body, QuoteResponse.class); } } |
Класс, отвечающий за отправку запросов и конвертацию ответов, готов. Осталось внести пару строк в MainActivity, который будет использовать внутри себя этот самый класс QuoteApi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
public class MainActivity extends AppCompatActivity { private QuoteApi api = new QuoteApi(); private TextView textViewQuote; private TextView textViewAuthor; private static final int PERMISSION_REQUEST_CODE = 1; private static final String INTERNET_PERMISSION = Manifest.permission.INTERNET; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); defineComponents(); defineFunctionality(); if (isInternetPermissionGranted()) { startInternetThread(); } else { requestInternetPermission(); } } /** * определить функциональность графических компонентов */ private void defineFunctionality() { } /** * запуск самой функциональности приложения * (создать поток для выполнения запросов и расчётов) */ private void startInternetThread() { Thread thread = new Thread(() -> showQuoteOnScreen()); thread.start(); } /** * отобразить цитату на экране */ private void showQuoteOnScreen() { try { QuoteResponse quote = api.sendForQuote(); showTextOnTextViewFromOtherThread(quote.getQuoteText(), textViewQuote); showTextOnTextViewFromOtherThread(quote.getFormattedAuthor(), textViewAuthor); } catch (ApiException e) { throw new RuntimeException(e); } } /** * отобразить текст из другого потока в главном GUI-потоке */ private void showTextOnTextViewFromOtherThread(String text, TextView textView) { textView.post(() -> textView.setText(text)); } /** * проверка наличия разрешения на интернет */ private boolean isInternetPermissionGranted() { int permissionResult = ActivityCompat.checkSelfPermission(this, INTERNET_PERMISSION); return permissionResult == PackageManager.PERMISSION_GRANTED; } /** * запросить разрешение на инетрнет */ private void requestInternetPermission() { String[] PERMISSIONS = {INTERNET_PERMISSION}; ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_REQUEST_CODE); } /** * метод-обработчик события, когда пользователь нажимает * в диалоговом окне выдачи прав "разрешить" или "запретить" */ @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (isPermissionForInternetGranted(requestCode, permissions, grantResults)) { startInternetThread(); } else { Toast.makeText(this, "Вы не выдали разрешение", Toast.LENGTH_SHORT).show(); } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } /** * проверить, выдано ли было разрешение на интернет */ private boolean isPermissionForInternetGranted(int requestCode, String[] permissions, int[] grantResults) { if (requestCode != PERMISSION_REQUEST_CODE) { return false; } if (permissions.length != 1 || grantResults.length != 1) { return false; } if (!permissions[0].equals(INTERNET_PERMISSION)) { return false; } return grantResults[0] == PackageManager.PERMISSION_GRANTED; } /** * определить графические компоненты */ private void defineComponents() { textViewQuote = findViewById(R.id.textViewQuote); textViewAuthor = findViewById(R.id.textViewAuthor); } } |
Самый долгожданный момент. Когда уже можно будет посмотреть на результат? Прямо сейчас! Давайте запустим приложение и посмотрим, как оно работает.
Великолепно! Мы создали ШЕДЕВР. Спасибо большое создателям API за такой прекрасный источник цитат. Теперь-то, мои дорогие, вы сможете открывать это приложение каждый день и получать свою цитату на сегодня.
Причём работа с API никак не изменяется в зависимости от того, какое приложение их применяет. Это могут быть и запросы к API из консольного приложения.
API упрощает взаимодействие между различными программными компонентами. Используйте API других программ и ресурсов, которые уже существуют, вместо того, чтобы изобретать велосипед!