Создание мобильного клиента 1С на Android с использованием HTTP-сервисов

Краткий курс по созданию мобильного приложения на Android, который связывается с сервером 1С через HTTP-сервис. Публикация рассчитана на тех, кто хорошо знаком с программированием на платформе 1С и владеет основами программирования на платформе Android (умеет создавать Activity и знает, как устроена структура проекта).

На Инфостарте есть несколько публикаций на тему создания приложений для Android и его связке с 1С через Web-сервис. Но на дворе сейчас конец 2024 года, и пришла пора освежить свои навыки.

Во-первых, с выходом Android Studio 3.0.0 в корпорации Google уже окончательно определились с будущим основным языком программирования, и это будет Kotlin. В данной публикации будем использовать именно этот язык. Он совместим с Java и с переходом у вас никаких проблем не будет, получите только позитивные эмоции.

Во-вторых, не стоит вкладывать свои силы в разработку Web-сервисов. Этому есть несколько причин:

  • На платформе 1С 8.3 им на смену пришли HTTP-сервисы
  • Протокол SOAP, используемый веб-сервисами дорого обходится и для сервера, и для клиента
  • Для платформы Android нет хорошей библиотеки для работы с SOAP (по крайней мере бесплатных). Библиотека ksoap2 хороша ровно до того момента, когда вы от простых примеров перейдёте к реальным задачам и на сложных структурах данных поймаете Double ID Exception, для лечения которого надо обладать исключительными знаниями протокола, схемы XML-разметок, рыться в исходниках библиотеки. Оно вам надо? Парсинг полученных данных в ksoap — это отдельный ад программиста
  • Для создания клиента HTTP-сервисов существуют замечательные библиотеки, в том числе для платформы Android
  • В конце концов, при помощи HTTP-сервиса в 1С вы можете сами написать собственную реализацию протокола SOAP

В данной публикации я не буду подробно рассматривать создание HTTP-сервиса, т.к. на эту тему уже есть публикации, например эти: HTTP-сервисы для тех, кто ничего не понимает в WEB и HTTP-сервисы в 1С Предприятие 8.3 . В качестве упражнения, руководствуясь этими публикациями, создайте самостоятельно HTTP-сервис с базовым URL "wms" и шаблоном /tables/{ИмяТаблицы}, возвращающий список складов в формате JSON. Формат JSON лучше XML хотя бы тем, что в нём меньше букв. Код модуля сервиса должен у вас получиться примерно такой:

Функция ТаблицыПолучить(Запрос)

ИмяТаблицы = Запрос.ПараметрыURL["ИмяТаблицы"];
Данные = Новый ЗаписьJSON;
Данные.ПроверятьСтруктуру = Ложь;
Данные.УстановитьСтроку(Новый ПараметрыЗаписиJSON(,Символы.Таб));
Данные.ЗаписатьНачалоОбъекта();
Данные.ЗаписатьИмяСвойства(ИмяТаблицы);
Данные.ЗаписатьНачалоМассива();

Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Склады.Код,
| Склады.Наименование КАК Наименование
|ИЗ
| Справочник.Склады КАК Склады
|ГДЕ
| Склады.ПометкаУдаления = ЛОЖЬ
| И Склады.ЭтоГруппа = ЛОЖЬ";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Данные.ЗаписатьНачалоОбъекта();
Данные.ЗаписатьИмяСвойства("id");
Данные.ЗаписатьЗначение(Выборка.Код);
Данные.ЗаписатьИмяСвойства("name");
Данные.ЗаписатьЗначение(Выборка.Наименование);
Данные.ЗаписатьИмяСвойства("isFifo");
Данные.ЗаписатьЗначение(Ложь);
Данные.ЗаписатьКонецОбъекта();
КонецЦикла;

Данные.ЗаписатьКонецМассива();
Данные.ЗаписатьКонецОбъекта();
Тело = Данные.Закрыть();
Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзСтроки(Тело, КодировкаТекста.UTF8);

Возврат Ответ;

КонецФункции

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

В браузере вы должны получить от сервиса такую структуру данных в формате JSON, где "stores" — это параметр, передаваемый в адресной строке вместо {ИмяТаблицы}, сохраните её в блокноте, пригодится нам в дальнейшем:

{
"stores": [
{
"id": "000000008",
"name": "Изолятор брака",
"isFifo": false
},
{
"id": "000000007",
"name": "Производственный склад",
"isFifo": false
},
{
"id": "000000002",
"name": "Склад готовой продукции",
"isFifo": false
} ]
}

А теперь переходим к клиенту: в Android Studio 3 создайте новый проект, включите поддержку языка Kotlin. Укажите минимальный SDK 25 уровня. В файле build.gradle вашего проекта добавьте следующие зависимости:

dependencies {
// эти зависимости уже могут быть в вашем проекте, их не трогайте:
implementation fileTree(include: ["*.jar"], dir: "libs")
implementation "com.android.support:appcompat-v7:26.+"
implementation "com.android.support:design:26.+"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

// а эти зависимости вы добавляете сами:
implementation "com.squareup.retrofit2:retrofit:2.+"
implementation "com.squareup.retrofit2:converter-gson:2.+"
implementation "org.jetbrains.anko:anko-sdk25:$anko_version"
implementation "org.jetbrains.anko:anko-sdk25-listeners:$anko_version"
implementation "org.jetbrains.anko:anko-commons:$anko_version"
}

Последними строками в файле конфигурации вы добавляете библиотеку Retrofit и парсер GSON, который нам понадобиться в проекте, а также очень полезные расширения языка Kotlin для Android под названием Anko — с ним ваш код будет еще более кратким и понятным.

Google рекомендует бизнес-логику приложения выносить в класс, наследуемый от Service, поэтому создайте простейший сервис примерно такого содержания:

package test.App

import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class BLService : Service() {

private val mUrl = "http://server/Database1c/hs/wms"  // Базовый URL в 1С указан как wms
lateinit private var rf: Retrofit   // Объект, который содержит все настройки нашего
// соединения с сервером и выполняет всю работу
lateinit private var wms: WmsApi    // Это API нашего HTTP-сервиса, напишем его позже

private var binder = BLBinder()     // Нужен для доступа к сервису из любой Activity

inner class BLBinder : Binder() {
fun getService(): BLService? {
return this@BLService
}
}

override fun onCreate() {

super.onCreate()

val okHttpClient = OkHttpClient.Builder()
.addInterceptor(BasicAuthInterceptor("Иванов", "СуперПароль"))
// напишем этот Interceptor позже, нужен для авторизации в 1С
.build()
rf = Retrofit.Builder()
.baseUrl(mUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
wms = rf.create(WmsApi::class.java)

}

// обязательные переопределяемые методы:
override fun onBind(intent: Intent?): IBinder {
return binder
}

override fun onUnbind(intent: Intent?): Boolean {
return true
}

}

Не могу удержаться и покажу, как теперь стало просто запускать сервис из главной Activity при помощи расширения языка Anko:

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)
// здесь может быть еще код

// запуск сервиса с помощью расширения языка Anko:
startService<BLService>()
}

По умолчанию Retrofit не использует авторизацию на HTTP-сервере, но к счастью его легко добавить. Для этого нам необходимо написать класс для авторизации на сервере 1С методом BASIC, так называемый интерцептор, вот его полный код:

package test.App

import okhttp3.Credentials
import okhttp3.Interceptor
import okhttp3.Response

class BasicAuthInterceptor(user: String, password: String) : Interceptor {

private val credentials : String = Credentials.basic(user, password)

override fun intercept(p0: Interceptor.Chain?): Response {
val request = p0!!.request()
val authenticatedRequest = request
.newBuilder()
.header("Authorization", credentials)
.build()
return p0.proceed(authenticatedRequest)
}

}

Не забывайте, что методом BASIC пароли пользователей 1С передаются через сеть открытым текстом, поэтому при боевом развертывании приложения всегда настраивайте веб-сервер на использование только шифрованного протокола HTTPS.

Теперь необходимо написать интерфейс API нашего HTTP-сервиса. Для начала возьмем блокнот и посмотрим на структуру данных, полученную ранее. Этот файл поможет нам создать классы Java, в которые будут завёрнуты наши данные. В случае использования ksoap2 вы бы на этом пункте хорошенько вспотели. Но ничего не бойтесь, с нами Retrofit, поэтому идем на сайт www.jsonschema2pojo.org/ и в левой его части вставляем содержание вашего JSON-пакета. В правой части заполняем как на рисунке:

На основе введеных данных этот сайт бесплатно сгенерирует нам два класса на языке Java в 2 файлах:

-----------------------------------test.App.Store.java-----------------------------------

package test.App;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Store {

@SerializedName("id")
@Expose
private String id;
@SerializedName("name")
@Expose
private String name;
@SerializedName("isFifo")
@Expose
private Boolean isFifo;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Boolean getIsFifo() {
return isFifo;
}

public void setIsFifo(Boolean isFifo) {
this.isFifo = isFifo;
}

}
-----------------------------------test.App.Stores.java-----------------------------------

package test.App;

import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Stores {

@SerializedName("stores")
@Expose
private List<Store> stores = null;

public List<Store> getStores() {
return stores;
}

public void setStores(List<Store> stores) {
this.stores = stores;
}

}

Добавьте эти 2 файла в свой проект, и через меню Code -> Convert Java file to Kotlin file переведите на язык Kotlin, в результате получатся совсем простые классы, избавленные от геттеров и сеттеров, которые в языке Kotlin вдобавок еще можно объединить в одном файле:

package test.App

import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName

class Store {

@SerializedName("id")
@Expose
var id: String? = null

@SerializedName("name")
@Expose
var name: String? = null

@SerializedName("isFifo")
@Expose
var isFifo: Boolean? = null

}

class Stores {

@SerializedName("stores")
@Expose
var stores: List<Store>? = null

}

Принцип построения этих классов-обёрток думаю теперь вам стал понятен и немного попрактиковавшись вы сможете писать их на языке Kotlin без всяких помощников типа сайта, приведенного выше.

Добавим в наш сервисный класс BLService переменную для хранения массива полученных из 1С данных:

var dbStores : Stores? = null

А теперь переходим непосредственно к написанию интерфейса API. Звучит угрожающе, но на самом деле для нашего HTTP-сервиса это будет такой простой код;

package test.App

import retrofit2.Call
import retrofit2.http.GET

interface WmsApi {

@GET("table/stores")
fun getStores() : Call<Stores>

}

Полный URL в браузере, соответствующий функции getStore() выглядел бы с учётом вышенаписанного кода так: http://server/Database1c/hs/wms/table/stores. Функции в интерфейсе всегда должны возвращать тип Call с указанием получаемого от HTTP-сервиса класса-обертки в угловых скобках. Параметры в строке @GET можно точно так же как в 1С заключать в фигурные скобки и указывать их в параметре функции, например вот так:

interface GitHubService {
@GET("users/{user}/repos")
fun listRepos(@Path("user") user: String) : Call<List<Repo>>
}

Интерфейс API инициализируется в коде нашего класса BLService такой строкой:

wms = rf.create(WmsApi::class.java)

Вы наверное удивитесь, но на этом всё. Теперь вы можете дергать 1С при помощи HTTP-сервиса. Для простоты уберу обработчики исключений и асинхронные штучки, оставив только самую суть:

dbStores = wms.getStores().execute().body()

До скорых встреч!

15 Comments

  1. Mi11er

    Буквально месяц назад, загорелся такой идеей, сделать для андройда что то похожее, но потом бысро понял, что пока что не хватит навыков по Java, спасибо за статью =)

    Будем учить =) и разбираться. Как раз впереди для этого 3 выходных.

    Reply
  2. Dzenn

    В этой публикации прекрасно всё. За исключением меня, не шарящего в andriod.

    Reply
  3. ylyas

    Вопрос автору.

    А чем Java оказался плох?

    Или в чем, в данном случае(для данной задачи) профит Kotlin??

    Reply
  4. cdiamond

    (3) В моем реальном проекте количество написанного кода сократилось примерно в 2 раза, он стал хорошо читабельным, потому что издалека напоминает мне Object Pascal (Delphi), которым я увлекался в юности. Кто-то видит в нём похожесть на C# и даже на Swift.

    Ну и как я сказал в преамбуле — Гугл официально взял курс на Kotlin, а это значит что в скором времени официальная документация и курсы по программированию на Android будут переписаны на этом языке, а Java останется в роли поддержки старого кода.

    Reply
  5. ylyas

    (4)

    Это с точки зрения удобства разработчика. А с точки зрения эксплуатации самого решения, реализованного на kotlin? есть ли преимущества перед JAVA? Производительность, устойчивость, отсутствие проблемы утечки памяти? или что то еще…

    Reply
  6. cdiamond

    (5) Kotlin транслируется в байт-код виртуальной машины ART, так что никаких принципиальных отличий от Java пока нет, кроме небольшого увеличения размера рантайм-библиотек + подключаемые расширения языка. В случае изготовления интерфейса без XML-разметки с помощью Anko значительно возрастатет скорость генерации интерфейса. Так что с утечками бороться точно так же как в Java. Но больше всего доставляет null safety.

    Reply
  7. ltfriend

    (4) То, что kotlin теперь официально включен в новую версии android studio не означает, что Гугл сделало его основным. Да и общего с java у него только то, что он генерирует тот же байт код для jvm. Соответственно ни на java, ни на c# (который создавался под влиянием java и также имеет С подобный синтаксис) котлин не особо похож. А вот со swift’ом действительно очень схожий синтаксис.

    Reply
  8. victor_k

    Слабинько, да и похоже муторно, что то стоящее сделать…

    Reply
  9. Brawler
    Точно так же как и для Web-сервисов, я не рекомендую вам писать бизнес-логику в модуле HTTP-сервиса, так как в нём отсутствует проверка кода на ошибки. Я вам привел плохой пример исключительно для простоты изложения. Вместо этого старайтесь максимально переносить свой код в общие модули.

    Сталкивались с этой бородой уже.

    Это фича такая или ошибка в платформе?

    Если фича, то по каким вразумениям она сделана?

    Если ошибка, в 1С кто-то писал?

    Reply
  10. smit1c

    Если с 0 учить программирование под Андроид, то лучше начинать с JAVA или всё таки Kotlin ?

    Reply
  11. Mi11er

    (10)

    Думаю что то вроде того

    JAVA + ООП -> Kotlin

    Хотя Kotlin может быть полностью сольным языком в проекте.

    Reply
  12. Dementor

    (10) Начинать проще с языка, на котором есть учебники для начинающих. Сейчас это Java. В будущем, возможно, появятся учебники на Kotlin. А вы уже сами решайте когда хотите учить программирование для Android — сейчас или в будущем. Рекомендую startandroid.ru

    Reply
  13. ylyas

    кстати вопрос.

    а ошибки у вас не выдает вот в этом месте?? : .baseUrl(mUrl)

    у меня ,например,ретрофит «хочет» чтобы базовый URL заканчивался слешем.

    у вас, выше по тексту если смотреть — окончание без слеша…

    Reply
  14. ylyas

    И да, реализовал таки тоже самое на JAVA в андроид студио 2.3…

    Особого увеличения кода не увидел. Разницу только в нотации увидел.

    Для меня,например, менее очевидны объявления переменных в Kotlin.

    Не знаю. мое мнение, что Kotlin — прослойка между Java и разработчиком.. не особо нужная..

    Пока вот так

    Reply
  15. 🅵🅾️🆇

    (0) > Google взял курс на Kotlin

    Не совсем так, многое (включая и интерфейс будующей ос фуксия и реклама, которая и делает им основной доход) делается на flutter + dart.

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

    Так что немного покривили душой. А так статью в закладки, тк дай боже в ближайшем будующем снова вернусь к дружбе мобильников и 1с’ки)))

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *