Привет! Недавно была задача реализовать бронирование сауны через мобильное приложение на Android. Как вам, наверное, известно напрямую из приложения отправлять запросы в базу данных небезопасно. Как минимум разобрав ваше приложение можно получать все данные к вашей базе данных, а возможно и не только к ней. Поэтому я решил реализовать небольшой API (интерфейс прикладного программирования) и отправлять данные из мобильного приложения через POST-запрос.

Использовать стандартные методы Java в приложении не вышло. Поэтому пришлось искать в интернете, что оказалось сложно. Наконец, мне помогли на Stackoverflow. И теперь я расскажу вам.

Для начала нам нужно создать наш API на сервере. Я буду использовать PHP и напишу скрипт, который к примеру, будет принимать сообщение и записывать его в файл. В своих проектах, вы можете реализовывать любую логику, к примеру запись данных в базу данных.

Я создал тестовый файл по пути yupotapov.ru/test/test.php. Теперь давайте напишем простецкий скрипт, который примет запрос и запишет в файл массив, который мы представим как текст.

<?php
	ini_set('display_errors', 1);   //Для отображения ошибок PHP, если они отключены на сервере
	ini_set('display_startup_errors', 1); //Для отображения ошибок PHP, если они отключены на сервере
	error_reporting(E_ALL); //Для отображения ошибок PHP, если они отключены на сервере
	
	$data = $_POST; //Записываем данные переменную
	file_put_contents("txt.log", print_r($data, true)); //Сохраняем полученный массив в файл экранируя его как текст
?>

Отлично, давайте проверим наш скрипт и перейдем в браузере на адрес: {путь до вашего файла}/test.php?message=Сообщение

После того, как мы перешли по данной ссылке следуем по данной директории и открываем файл txt.log. Если файл создан и имеет вот такое содержимое:

Array
(
)

То мы все сделали правильно. Спросите почему, если должно было записаться наше сообщение? С какой-то стороны вы правы. Но через адресную строку посылается GET-запрос, а наш сервер будет посылать POST-запрос. Поэтому в нашем PHP коде в переменную data записывается $_POST и существующий массив даже с нашим запросом будет пуст. И так, мы подготовили наш API и теперь пора приступать к написанию мобильного приложения. Давайте, создадим пустой проект для изучения, после вы сможете внедрить изученное в ваши проекты.

Создание пустого проекта

В качестве минимальной версии Android можно выбрать API 23 Android Marshmallow.

Создание пустого проекта

После того, как закончится индексация проекта нужно создать файл Network Config. Он будет выполняться «сертификат доверия» к нашему домену. Без него никак не получится подключиться. И кстати именно с этим я долго боролся.

Давайте создадим каталог xml в папке res. После этого нам требуется написать собственно сам сертификат. И поместить его в данную папку. Если не получается создать файл именно в этой папке, то открывайте ее через проводник и создавайте файл вручную, Android Studio автоматически подгрузит его в проект. Дадим название файлу network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config usesCleartextTraffic="false">
        <domain includeSubdomains="true">yupotapov.ru</domain>
    </domain-config>
</network-security-config>

Этот xml код говорит приложению о том, что следует доверять домену yupotapov.ru, в вашем случае это может быть любой другой домен.

Отлично, файл мы создали, но этого не достаточно, чтобы приложение его использовало. Требуется добавить запись о нем в манифест нашего приложения. Для этого открываем папку manifests и соответственно AndroidManifest.xml, находим тег application и дописываем атрибут android:networkSecurityConfig=»@xml/network_security_config», чтобы получилось примерно так:

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:networkSecurityConfig="@xml/network_security_config">

Запись о Network Config добавлена, но теперь нужно предоставить доступ к интернету, поэтому в наш документ, после тега application добавляем строки с разрешением:

<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Теперь наш манифест выглядит примерно так:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.potapov.testpostrequest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:networkSecurityConfig="@xml/network_security_config">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

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

Создание кнопки

XML-код нашей кнопки будет выглядеть так:

 <Button
        android:id="@+id/buttonRequests"
        android:layout_width="152dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="Отправить POST-запрос"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

Открываем наш класс управляющий этот формой и инициализируем кнопку. То есть создаем объект и присваиваем ему нашу кнопку. Для этого внутри метода onCreate прописываем:

 Button btn = (Button) findViewById(R.id.buttonRequests);

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

 btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });

Теперь подошло время к написанию самого запроса. Для этого давайте создадим метод sendRequests как-то так:

protected Void sendRequests() {
        try {
            String myURL = "https://yupotapov.ru/test/test.php";
            String parammetrs = "message=Hello, its requests in Android App";
            byte[] data = null;
            InputStream is = null;

            try {
                URL url = new URL(myURL);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setDoOutput(true);
                conn.setDoInput(true);

                conn.setRequestProperty("Content-Length", "" + Integer.toString(parammetrs.getBytes().length));
                OutputStream os = conn.getOutputStream();
                data = parammetrs.getBytes("UTF-8");
                os.write(data);
                data = null;

                conn.connect();
                int responseCode= conn.getResponseCode();

                ByteArrayOutputStream baos = new ByteArrayOutputStream();

                if (responseCode == 200) {
                    is = conn.getInputStream();

                    byte[] buffer = new byte[8192]; // Такого вот размера буфер
                    // Далее, например, вот так читаем ответ
                    int bytesRead;
                    while ((bytesRead = is.read(buffer)) != -1) {
                        baos.write(buffer, 0, bytesRead);
                    }
                    data = baos.toByteArray();
                    String resultString = new String(data, "UTF-8");
                } else {

                }
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("Успех!")
                        .setMessage("Сообщение успешно отправлено на сервер!")
                        .setCancelable(false)
                        .setNegativeButton("Закрыть",
                                new DialogInterface.OnClickListener() {
                                    public void onClick(DialogInterface dialog, int id) {
                                        dialog.cancel();
                                    }
                                });
                AlertDialog alert = builder.create();
                alert.show();


            } catch (MalformedURLException e) {

                //resultString = "MalformedURLException:" + e.getMessage();
            } catch (IOException e) {

                //resultString = "IOException:" + e.getMessage();
            } catch (Exception e) {

            }
        } catch (Exception e) {
            //resultString = "IOException:" + e.getMessage();
        }
        return null;
    }

Теперь нужно вызвать этот метод внутри обработчика события нашей кнопки. Думаю, с этим вы справитесь и без меня.

И знаете, я кажется забыл самое главное! Чтобы все точно прошло успешно, открываем наш главный класс и до того, как мы инициализируем кнопку добавляем создания политики безопасности:

 if (android.os.Build.VERSION.SDK_INT > 9)
        {
            StrictMode.ThreadPolicy policy = new
                    StrictMode.ThreadPolicy.Builder().permitAll().build();
            StrictMode.setThreadPolicy(policy);
        }

По итогу, наша функция onCreate должна выглядеть так:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (android.os.Build.VERSION.SDK_INT > 9)
        {
            StrictMode.ThreadPolicy policy = new
                    StrictMode.ThreadPolicy.Builder().permitAll().build();
            StrictMode.setThreadPolicy(policy);
        }
        Button btn = (Button) findViewById(R.id.buttonRequests);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sendRequests();
            }
        });

    }

Теперь запускаем приложение и нажимаем кнопку.

Уведомление о успешной отправке

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

Array
(
    [message] => Hello
)

Ух, наше сообщение успешно отправилось. Получается, наша задача выполнена! Если потребуется целый проект, напишите в комментарии, я его выложу.