Решил вот написать статью. Так как писать статьи я не умею, прошу сильно не пинать за возможные ошибки.
Написать статью меня сподвигла вот эта вот публикация
//infostart.ru/public/463387/
В свое время для одного своего проекта на Андроид на базе этой публикации я делал обмен с 1С через WEB-Сервисы. Так как я был начинающим программистом на Андроид, алгоритм получился немного громоздким, т.к. в результате обмена я получал xml файл, который, в итоге, надо было еще парсить средствами Андроид. Что-то типа вроде этого.
try {
XmlPullParser xpp = prepareXpp();
while ((xpp.getEventType() != XmlPullParser.END_DOCUMENT)) {
switch (xpp.getEventType()) {
case XmlPullParser.START_DOCUMENT:
// Log.d(LOG_TAG, "XmlPullParser.START_DOCUMENT Start document");
break;
case XmlPullParser.START_TAG:
// Log.d(LOG_TAG, "XmlPullParser.START_TAG " + xpp.getName() + " " + xpp.getDepth());
switch (xpp.getDepth()) {
case 2:
data = new Bundle();
break;
case 3:
tagName = xpp.getName();
}
// if (xpp.getName().equals(XML_NODE_NAME_CONTACT)) {
// data = new Bundle();
// } else {
// tagName = xpp.getName();
// }
break;
case XmlPullParser.END_TAG:
// Log.d(LOG_TAG, "XmlPullParser.END_TAG " + xpp.getName());
tagName = "";
if (xpp.getDepth() == 2) {
addDataContact(data);
// Log.d(LOG_TAG,data.get("FullName").toString());
// Log.d(LOG_TAG,"ADD DATA");
//breakProgram = true;
}
break;
case XmlPullParser.TEXT:
// Log.d(LOG_TAG, "XmlPullParser.TEXT " + xpp.getText());
if (xpp.getDepth() == 3) {
String dataText = xpp.getText();
if (dataText == null) {
dataText = "";
};
data.putString(tagName, dataText);
}
// if (!tagName.isEmpty()) {
// data.putString(tagName, xpp.getText());
// }
break;
default:
break;
}
//if (breakProgram) break;
xpp.next();
}
}catch (XmlPullParserException e) {
//e.printStackTrace();
DataXML.append(e.toString());
sendMessage(MainActivity.STATUS_ERR);
return "false";
// Log.d(LOG_TAG, e.toString());
}catch (Exception e) {
//e.printStackTrace();
DataXML.append(e.toString());
sendMessage(MainActivity.STATUS_ERR);
// Log.d(LOG_TAG, e.toString());
return "false";
}
return "true";
}
Да и тестирование WEB-сервиса тоже требовало определенных усилий и дополнительного софта. Библиотека ksoap, с помощью которой можно работать с WEB-Сервисами, не внушала у меня особого доверия, может я в ней не разобрался тогда до конца, может еще были причины. В общем, для меня все это было громоздким и устаревшим.
К тому же злые языки в 1С утверждали, что WEB-Сервисы - это вчерашний день и настоятельно рекомендовали переходить на HTTP-сервисы. И я не мог с ними не согласиться. И начал искать альтернативу. В поисках альтернативы наткнулся на библиотеку под названием Retrofit. Это HTTP клиент реализующий REST архитектуру. Выражаясь просто, ее можно использовать для обмена с 1С с помощью HTTP-сервисов. В дополнении к этому, эта библиотека умеет работать с json, но самое важное, что при работе с ней, программист оперирует классами (моделью). Т.е. получая результат от сервера, я работаю не с сырыми данными в виде xml или json, а уже готовыми классами.
Как это работает?
Например, у нас есть некоторые данные, которые надо получить из 1С. Средствами 1С легко получить данные в формате json. Создаем на стороне 1С HTTP-сервис, который принимает запрос и возвращает результат в виде json.
Функция ПолучитьПередатьДанные(Запрос)
СтруктураРезультата = ОбменДанными.ПолучитьОбработатьДанные(Запрос);
Ответ = Новый HTTPСервисОтвет(СтруктураРезультата.КодСостояния);
Ответ.УстановитьТелоИзСтроки(СтруктураРезультата.РезультатВыполнения);
Возврат Ответ;
КонецФункции
Подключаем к своему проекту Retrofit.
// retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
Создаем интерфейс, где будут наши методы для запроса или отправки данных
public interface ApiService {
@GET("{secondURL}/{id}/products")
Call<List<RetrofitProduct>> getListProducts(@Path(value = "secondURL", encoded = true) String secondURL, @Path("id") String id_client);
}
RetrofitProduct - это класс, список объектов которого будут созданы библиотекой автоматически, при успешном получении данных с сервера.
public class RetrofitProduct {
@SerializedName("ID")
@Expose
private String iD;
@SerializedName("name")
@Expose
private String name;
@SerializedName("name")
// далее геттеры и сеттеры
}
Кстати, его можно автоматически сгенерировать на этом вот сайте https://www.jsonschema2pojo.org/
если у вас есть данные в формате json.
Создаем класс синглтон для настройки клиента и авторизации на стороне 1С
public class RetrofitClient {
private static Retrofit retrofit = null;
// временно, логин и пароль для подключения будем хранить в настройках?
private static String user = "admin";
private static String pwd = "admin";
public static Retrofit getClient(String baseUrl) {
if (retrofit == null) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(300, TimeUnit.SECONDS)
.addInterceptor(new BasicAuthInterceptor(user, pwd))
.build();
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
Для авторизации на стороне 1С используем класс BasicAuthInterceptor
class BasicAuthInterceptor implements Interceptor{
private String credentials;
public BasicAuthInterceptor(String user, String password) {
credentials = Credentials.basic(user, password);
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request authenticatedRequest = request.newBuilder()
.addHeader("Authorization", credentials)
.build();
return chain.proceed(authenticatedRequest);
}
}
определим класс для инициализации библиотеки
public class ApiUtils {
private static String BASE_URL;
private ApiUtils(){}
public static ApiService getAPIService() {
BASE_URL = App.getBaseURL();
try {
return RetrofitClient.getClient(BASE_URL).create(ApiService.class);
}catch (Exception e) {
MyLog.e(e.toString());
return null;
}
}
}
Инициализация клиента в коде
mApiService = ApiUtils.getAPIService();
if (mApiService == null) {
return;
}
И собственно дергаем нужный нам метод
try {
response = mApiService.getListProducts(
App.getDefaultSecondURL(), App.CONSTANT_ID_PARTNER)
.execute();
if (response.isSuccessful()) {
somethingToDo(response.body()); // тут у нас List<RetrofitProduct>
}else {
String errorMessage = response.raw().toString() + "\n" //response.raw() - сырые данные, можно получить ответ сервера
+ response.errorBody().string();
}
}catch (IOException e) {
// обработка исключения;
}
Есть два варианта вызова, синхронный и асинхронный. Синхронный вызов execute() не допускается в UI-потоке. В моем случае я использую WorkManager, который выполняет задачи в своем потоке.
Асинхронный вызов выглядит вот так
mApiServiceRetrofit.getProduct(App.getDefaultSecondURL(), App.CONSTANT_ID_PARTNER).enqueue(new Callback<RetrofitProduct>() {
@Override
public void onResponse(Call<PostCoordinates> call, Response<PostCoordinates> response) {
if (response.isSuccessful()) {
}else {
}
}
@Override
public void onFailure(Call<PostCoordinates> call, Throwable t) {
}
});
Можно вызывать из Activity или из Service. Но, как я уже писал выше, я использую WorkManager (https://developer.android.com/reference/androidx/work/WorkManager.html)
Он позволяет запускать фоновые задачи последовательно или параллельно, передавать в них данные, получать из них результат, отслеживать статус выполнения и запускать только при соблюдении заданных условий. И еще, на получения результата работы можно на него подписаться, что очень удобно.
LiveData<WorkInfo> ld = WorkManager.getInstance().getWorkInfoByIdLiveData(workID);
ld.observe(context, new Observer<WorkInfo>() {
@Override
public void onChanged(WorkInfo workInfo) {
if (workInfo.getState().isFinished()) {
// to do
}
}
});
В результате, программист получает отличный и простой инструмент для обмена данными с 1С. При этом можно не только получать данные с помощью HTTP-запроса GET, но также и передавать данные с помощью HTTP-запроса POST.
Полное описание библиотеки на сайте разработчика: https://square.github.io/retrofit/
Для ленивых написал простой проект с одним activity, который реализует работу с Retrofit, а также с WorkManager. Проект полностью рабочий, подключаете к Android Studio и можете сразу же пробовать. Без настроек на свой сервер будете получать ответ в виде Toast, об ошибке подключения :)
Надеюсь, что статья сэкономит кому то время (и деньги) :)