На Инфостарте есть несколько публикаций на тему создания приложений для 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()
До скорых встреч!
Буквально месяц назад, загорелся такой идеей, сделать для андройда что то похожее, но потом бысро понял, что пока что не хватит навыков по Java, спасибо за статью =)
Будем учить =) и разбираться. Как раз впереди для этого 3 выходных.
В этой публикации прекрасно всё. За исключением меня, не шарящего в andriod.
Вопрос автору.
А чем Java оказался плох?
Или в чем, в данном случае(для данной задачи) профит Kotlin??
(3) В моем реальном проекте количество написанного кода сократилось примерно в 2 раза, он стал хорошо читабельным, потому что издалека напоминает мне Object Pascal (Delphi), которым я увлекался в юности. Кто-то видит в нём похожесть на C# и даже на Swift.
Ну и как я сказал в преамбуле — Гугл официально взял курс на Kotlin, а это значит что в скором времени официальная документация и курсы по программированию на Android будут переписаны на этом языке, а Java останется в роли поддержки старого кода.
(4)
Это с точки зрения удобства разработчика. А с точки зрения эксплуатации самого решения, реализованного на kotlin? есть ли преимущества перед JAVA? Производительность, устойчивость, отсутствие проблемы утечки памяти? или что то еще…
(5) Kotlin транслируется в байт-код виртуальной машины ART, так что никаких принципиальных отличий от Java пока нет, кроме небольшого увеличения размера рантайм-библиотек + подключаемые расширения языка. В случае изготовления интерфейса без XML-разметки с помощью Anko значительно возрастатет скорость генерации интерфейса. Так что с утечками бороться точно так же как в Java. Но больше всего доставляет null safety.
(4) То, что kotlin теперь официально включен в новую версии android studio не означает, что Гугл сделало его основным. Да и общего с java у него только то, что он генерирует тот же байт код для jvm. Соответственно ни на java, ни на c# (который создавался под влиянием java и также имеет С подобный синтаксис) котлин не особо похож. А вот со swift’ом действительно очень схожий синтаксис.
Слабинько, да и похоже муторно, что то стоящее сделать…
Сталкивались с этой бородой уже.
Это фича такая или ошибка в платформе?
Если фича, то по каким вразумениям она сделана?
Если ошибка, в 1С кто-то писал?
Если с 0 учить программирование под Андроид, то лучше начинать с JAVA или всё таки Kotlin ?
(10)
Думаю что то вроде того
JAVA + ООП -> Kotlin
Хотя Kotlin может быть полностью сольным языком в проекте.
(10) Начинать проще с языка, на котором есть учебники для начинающих. Сейчас это Java. В будущем, возможно, появятся учебники на Kotlin. А вы уже сами решайте когда хотите учить программирование для Android — сейчас или в будущем. Рекомендую startandroid.ru
кстати вопрос.
а ошибки у вас не выдает вот в этом месте?? : .baseUrl(mUrl)
у меня ,например,ретрофит «хочет» чтобы базовый URL заканчивался слешем.
у вас, выше по тексту если смотреть — окончание без слеша…
И да, реализовал таки тоже самое на JAVA в андроид студио 2.3…
Особого увеличения кода не увидел. Разницу только в нотации увидел.
Для меня,например, менее очевидны объявления переменных в Kotlin.
Не знаю. мое мнение, что Kotlin — прослойка между Java и разработчиком.. не особо нужная..
Пока вот так
(0) > Google взял курс на Kotlin
Не совсем так, многое (включая и интерфейс будующей ос фуксия и реклама, которая и делает им основной доход) делается на flutter + dart.
Флаттер, кстати, позволяет просто делать очень шустрые приложения сразу под ведро и огрызок. Так что
извращенцылюбители JS могут не грустить, а помаленьку переучиваться.Так что немного покривили душой. А так статью в закладки, тк дай боже в ближайшем будующем снова вернусь к дружбе мобильников и 1с’ки)))