Как вернуть ответ на вызывающую функцию? (Android-приложение вызывает Java REST-сервер через JSON) - PullRequest
0 голосов
/ 29 апреля 2019

Я переписываю некоторые части приложения Android для вызова REST-сервера, написанного на Java, используя Spring-Boot Framework.

Подводя итог, скажем, в приложении, функции Aвызывает функцию B в моем главном контроллере (который должен управлять связью клиент-сервер).Вызов функции B (с несколькими дополнительными шагами) вызывает мой Сервер.Когда функция B выполняет вызов, она использует анонимный внутренний класс, переопределяющий метод, чтобы определить, как обрабатывать ответ.Я изо всех сил пытаюсь получить этот ответ от анонимного внутреннего класса к функции B, чтобы его можно было вернуть к функции A. К сожалению, некоторая асинхронность задействована, просто чтобы сделать вещи, которые немного более интересными.использовать карту, которая хранит результат вызова с уникальным идентификатором, чтобы сделать его доступным за пределами внутреннего класса.Теоретически работает довольно хорошо, но рассыпается под асинхронностью

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

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

См. Следующеекод

Клиент: MainController

class MainController
{
    private final AtomicLong counter;
    private final HashMap<Long, Object> callResults;

    public MainController(){
        counter = new AtomicLong();
        callResults  = new HashMap<>();
    }

    public Login getLoginByUsername(String username)
    {
        long callID = counter.incrementAndGet();
        System.out.println("Start call");
        ServerConnection.get("myURL",null, new JsonHttpResponseHandler(){
            @Override
            public void onSuccess(int statusCode, Header[] headers, JSONObject response){
                try {
                    System.out.println("Call success, enter result:");
                    callResults.put(callID, new CallResult(Login.parseFromJson(response)));
                    System.out.println(callResults.get(callID));

                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });

        System.out.println("start waiting");
       while(callResults.get(callID) == null){
            System.out.print(".");
        }
        System.out.println("waiting over, return result:");
        System.out.println(callResults.get(callID));
        //return (Login) callResults.remove(callID).content;
        return null;
    }
}

Клиент: ServerConnection

import com.loopj.android.http.*;
import cz.msebera.android.httpclient.HttpEntity;

public class ServerConnection {

    private static final String BASE_URL = "http://1.3.3.7:8080/";

    private static AsyncHttpClient client = new AsyncHttpClient();

    public static void get(String url, RequestParams params, JsonHttpResponseHandler responseHandler) {
        client.get(BASE_URL+url, params, responseHandler);
    }
}

Вот что я знаю:

  1. Намеченный ответ придет в конце концов.Когда я не использую цикл ожидания и просто возвращаю ноль, на консоли будут отображаться сообщения «Успешный вызов, введите результат:» и идентификатор объекта

  2. При использовании whileк концу, он никогда не печатает "."- это просто ничего не делает.Я знаю, что приложение все еще работает, потому что AndroidStudio продолжает информировать меня о распределении пространства

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

Это, как я полагаю, проблема: цикл предотвращает выполнение асинхронного REST-вызова,таким образом делая невозможным завершение.

Мой вопрос: A) Как я могу вернуть объект Login, полученный мной от сервера, в функцию, которая сначала вызывала функцию MainController.getLoginByUsername-function?

B) Что происходит с циклом while, который я пытался использовать для ожидания?Почему тело (print(".");) не будет выполнено и, что более важно, почему оно останавливает выполнение / завершение формы вызова REST?

(также не стесняйтесь оставлять отзыв о моем стиле публикации,я впервые задаю вопрос здесь)

Редактировать: добавлен вопрос B, который может предоставить более простой способ найти быстрое решение

Изменить на ОБНОВЛЕНИЕ: Извините, что не прояснил это достаточно:любое предложение, которое также требует от меня адаптации вызывающей функции (например, шаблоны наблюдателя, такие как Livedata или Rxjava или другие действительно хорошие предложения, предоставленные Лео Пелозо), неосуществимо.Я проверил, по крайней мере, предложение Лео Пелозоса, и основной код просто не позволяет этого.Я теряю доступ к параметрам и больше не могу использовать функцию возврата.Я знаю, что переделка базовой системы была бы лучшим вариантом действий, но сейчас это просто выходит за рамки ...

Ответы [ 4 ]

0 голосов
/ 01 мая 2019

Спасибо за вашу помощь! Я нашел решение, которое работает для моего случая.

Во-первых, поскольку это всего лишь прототип, мне не нужно, чтобы мои запросы были асинхронными, поэтому я изменил ServerConnection, чтобы вместо него использовать SyncHttpClient:

public class ServerConnection {

    private static final String BASE_URL = "http://10.203.58.11:8080/";

    private static SyncHttpClient client = new SyncHttpClient();

    public static void get(String url, RequestParams params, JsonHttpResponseHandler responseHandler) {
        client.get(getAbsoluteUrl(url), params, responseHandler);
    }
}

Тогда Android не позволит мне выполнять запросы в основном потоке. Это не проблема с AsyncHttpClient, так как асинхронная часть обрабатывается в отдельном потоке. Поэтому мне пришлось добавлять темы в мои запросы. Таким образом, мой MainController теперь выглядит примерно так:

public class MainController
{
    private final AtomicLong counter;
    private final HashMap<Long, CallResult> callResults;

    public MainController(){
        counter = new AtomicLong();
        callResults  = new HashMap<>();
    }

public Login getLoginByUsername(String username)
    {
        long callID = counter.incrementAndGet();
        new Thread(new Runnable() {
            @Override
            public void run() {
                ServerConnection.get("person-login-relations?byUsername="+username,null, new JsonHttpResponseHandler(){
                    @Override
                    public void onSuccess(int statusCode, Header[] headers, JSONObject response){
                        try {
                            callResults.put(callID, new CallResult(Login.parseFromJson(response)));
                            System.out.println(callResults.get(callID));

                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                });


            }
        })   .start();

        while(callResults.get(callID) == null){
            //wait
        }
        return (Login) callResults.remove(callID).content;
    }
}

Я думаю, что цикл while, который я использую для ожидания, теперь работает, потому что он не блокирует поток, который отвечает за выполнение и обработку запроса. Хотя не совсем уверен в этом.

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

Через несколько месяцев мне будет поручено переработать приложение, которое я сейчас расширяю для подключения к клиенту. На этом этапе я обязательно выполню большинство ваших предложений! Так что для любого, кто читает это решение: это прямо здесь - обходной путь, потому что мне не требуются асинхронные запросы, и я не должен изменять функции, вызывающие методы, которые вы видите здесь! Я предлагаю вам все еще прочитать другие ответы, поскольку они качественно превосходят!

0 голосов
/ 29 апреля 2019

Если вы не хотите использовать Livedata или Rxjava, вы можете реорганизовать этот метод для принятия JsonHttpResponseHandler или пользовательского обратного вызова, это не красиво, но работает:

1.

public void getLoginByUsername(String username, JsonHttpResponseHandler callback)
{
    long callID = counter.incrementAndGet();
    System.out.println("Start call");
    ServerConnection.get("myURL",null, callback);

}

, а затем вызывать его так:

getLoginByUsername("myUsername", new JsonHttpResponseHandler(){
        @Override
        public void onSuccess(int statusCode, Header[] headers, JSONObject response){
            try {
                Login myLogin = Login.parseFromJson(response);
                //do stuff

            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    });

или

2.

class MainController {
    private final AtomicLong counter;
    private final HashMap<Long, Object> callResults;

    interface OnLogin{
        void onSuccessLogin(Login login);
    }

    public MainController() {
        counter = new AtomicLong();
        callResults = new HashMap<>();
    }

    public void getLoginByUsername(String username, OnLogin callback)
    {

        ServerConnection.get("myURL",null, new JsonHttpResponseHandler(){
            @Override
            public void onSuccess(int statusCode, Header[] headers, JSONObject response){
                try {
                    Login loginObject = Login.parseFromJson(response);
                    callback.onSuccessLogin(loginObject);

                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });

    }


}

и вы называете это так:

    MainController controller = new MainController();
    controller.getLoginByUsername("myUsername", new MainController.OnLogin() {
        @Override
        public void onSuccessLogin(Login login) {
            //do whatever here
        }
    });
0 голосов
/ 01 мая 2019

Вы можете легко достичь этого, используя CompletableFuture<?>.Он работает аналогично Promise в JavaScript.Если вам требуется совместимость до API уровня 24, проверьте этот ответ здесь .

Так что ваш getLoginByUsername будет выглядеть примерно так:

public CompletableFuture<Login> loginByUsername(String username) {
    CompletableFuture<Login> promise = new CompletableFuture<Login>();

    System.out.println("Start call");
    ServerConnection.get("myURL",null, new JsonHttpResponseHandler(){
        @Override
        public void onSuccess(int statusCode, Header[] headers, JSONObject response){
            try {
                System.out.println("Call success, enter result:");
                promise.complete(Login.parseFromJson(response));

            } catch (JSONException e) {
                promise.completeExceptionally(e);
            }
        }
    });

    return promise;
}

И ваш сайт вызовачто-то вроде этого:

Login login = loginByUsername("user").get();
// Or wait a maximum time
Login login = loginByUsername("user").get(30, TimeUnit.SECONDS);

Никогда не звоните get() на UIThread.Это заблокирует пользовательский интерфейс и предоставит плохой опыт для пользователя

0 голосов
/ 29 апреля 2019

Использовали ли вы GSON от google, он конвертирует объект JSON в объект java, его синтаксис прост, и он вам понравится, я использую его все время. И я использую залп для ответа HTTP-запроса. Просто используйте GSON как

GSON gson = new GSON();
LoginObject login = new LoginObject();
login=gson.fromJson(responce,LoginObject.class);

и вуаля, вы сделали.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...