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

Обмен - Обмен с другими системами

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

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

Во-первых, с выходом 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()

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

См. также

Комментарии
1. Денис Мельников (Mi11er) 73 03.11.17 09:33 Сейчас в теме
Буквально месяц назад, загорелся такой идеей, сделать для андройда что то похожее, но потом бысро понял, что пока что не хватит навыков по Java, спасибо за статью =)

Будем учить =) и разбираться. Как раз впереди для этого 3 выходных.
2. Дамир Закиров (Dzenn) 230 03.11.17 10:47 Сейчас в теме
В этой публикации прекрасно всё. За исключением меня, не шарящего в andriod.
AlexKo84; Muzik92; dj_serega; user597616_i.d.kravchenko; ZUL_MTFKA; DrAku1a; KroVladS; гыук320; Сурикат; +9 Ответить
3. ylyas v (ylyas) 25 03.11.17 12:21 Сейчас в теме
Вопрос автору.
А чем Java оказался плох?
Или в чем, в данном случае(для данной задачи) профит Kotlin??
4. Алмаз Шарипов (cdiamond) 146 03.11.17 12:42 Сейчас в теме
(3) В моем реальном проекте количество написанного кода сократилось примерно в 2 раза, он стал хорошо читабельным, потому что издалека напоминает мне Object Pascal (Delphi), которым я увлекался в юности. Кто-то видит в нём похожесть на C# и даже на Swift.
Ну и как я сказал в преамбуле - Гугл официально взял курс на Kotlin, а это значит что в скором времени официальная документация и курсы по программированию на Android будут переписаны на этом языке, а Java останется в роли поддержки старого кода.
5. ylyas v (ylyas) 25 03.11.17 12:59 Сейчас в теме
(4)
Это с точки зрения удобства разработчика. А с точки зрения эксплуатации самого решения, реализованного на kotlin? есть ли преимущества перед JAVA? Производительность, устойчивость, отсутствие проблемы утечки памяти? или что то еще...
6. Алмаз Шарипов (cdiamond) 146 03.11.17 13:06 Сейчас в теме
(5) Kotlin транслируется в байт-код виртуальной машины ART, так что никаких принципиальных отличий от Java пока нет, кроме небольшого увеличения размера рантайм-библиотек + подключаемые расширения языка. В случае изготовления интерфейса без XML-разметки с помощью Anko значительно возрастатет скорость генерации интерфейса. Так что с утечками бороться точно так же как в Java. Но больше всего доставляет null safety.
7. Павел Толкачев (ltfriend) 272 04.11.17 10:16 Сейчас в теме
(4) То, что kotlin теперь официально включен в новую версии android studio не означает, что Гугл сделало его основным. Да и общего с java у него только то, что он генерирует тот же байт код для jvm. Соответственно ни на java, ни на c# (который создавался под влиянием java и также имеет С подобный синтаксис) котлин не особо похож. А вот со swift'ом действительно очень схожий синтаксис.
8. Виктор Ким (victorkim64) 77 04.11.17 18:19 Сейчас в теме
Слабинько, да и похоже муторно, что то стоящее сделать...
9. Игорь Фелькер (Brawler) 380 05.11.17 10:38 Сейчас в теме
Точно так же как и для Web-сервисов, я не рекомендую вам писать бизнес-логику в модуле HTTP-сервиса, так как в нём отсутствует проверка кода на ошибки. Я вам привел плохой пример исключительно для простоты изложения. Вместо этого старайтесь максимально переносить свой код в общие модули.


Сталкивались с этой бородой уже.
Это фича такая или ошибка в платформе?
Если фича, то по каким вразумениям она сделана?
Если ошибка, в 1С кто-то писал?
10. Александр (smit1c) 98 08.11.17 13:05 Сейчас в теме
Если с 0 учить программирование под Андроид, то лучше начинать с JAVA или всё таки Kotlin ?
11. Денис Мельников (Mi11er) 73 08.11.17 14:12 Сейчас в теме
(10)
Думаю что то вроде того
JAVA + ООП -> Kotlin

Хотя Kotlin может быть полностью сольным языком в проекте.
12. Дмитрий Кинаш (Dementor) 303 08.11.17 15:14 Сейчас в теме
(10) Начинать проще с языка, на котором есть учебники для начинающих. Сейчас это Java. В будущем, возможно, появятся учебники на Kotlin. А вы уже сами решайте когда хотите учить программирование для Android - сейчас или в будущем. Рекомендую startandroid.ru
Stan; smit1c; +2 Ответить
13. ylyas v (ylyas) 25 20.11.17 10:14 Сейчас в теме
кстати вопрос.
а ошибки у вас не выдает вот в этом месте?? : .baseUrl(mUrl)
у меня ,например,ретрофит "хочет" чтобы базовый URL заканчивался слешем.
у вас, выше по тексту если смотреть - окончание без слеша...
14. ylyas v (ylyas) 25 20.11.17 18:48 Сейчас в теме
И да, реализовал таки тоже самое на JAVA в андроид студио 2.3...
Особого увеличения кода не увидел. Разницу только в нотации увидел.
Для меня,например, менее очевидны объявления переменных в Kotlin.
Не знаю. мое мнение, что Kotlin - прослойка между Java и разработчиком.. не особо нужная..
Пока вот так
15. Plague Fox (A1ice1990) 94 14.03.18 14:44 Сейчас в теме
(0) > Google взял курс на Kotlin

Не совсем так, многое (включая и интерфейс будующей ос фуксия и реклама, которая и делает им основной доход) делается на flutter + dart.
Флаттер, кстати, позволяет просто делать очень шустрые приложения сразу под ведро и огрызок. Так что извращенцы любители JS могут не грустить, а помаленьку переучиваться.

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