Usługa API Restful


226

Szukam usługi, której mogę używać do nawiązywania połączeń z internetowym interfejsem API REST.

Zasadniczo chcę uruchomić usługę w aplikacji init, a następnie chcę móc poprosić tę usługę o podanie adresu URL i zwrócenie wyników. W międzyczasie chcę być w stanie wyświetlić okno postępu lub coś podobnego.

Stworzyłem usługę, która obecnie korzysta z IDL, przeczytałem gdzieś, że naprawdę potrzebujesz jej tylko do komunikacji między aplikacjami, więc pomyśl, że trzeba się rozebrać, ale nie wiesz, jak wykonać wywołania zwrotne bez tego. Również po uderzeniu post(Config.getURL("login"), values)aplikacja wydaje się na chwilę wstrzymywać (wydaje się dziwna - pomyślałam, że za usługą działa inny wątek!)

Obecnie mam usługę z pocztą i metodami HTTP, kilka plików AIDL (do komunikacji dwukierunkowej), ServiceManager, który zajmuje się uruchamianiem, zatrzymywaniem, wiązaniem itp. Z usługą i dynamicznie tworzę moduł obsługi z określonym kodem w razie potrzeby oddzwaniania.

Nie chcę, aby ktokolwiek dał mi pełną bazę kodu do pracy, ale niektóre wskazówki byłyby bardzo mile widziane.

Kod w (przeważnie) pełny:

public class RestfulAPIService extends Service  {

final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();

public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
}
public IBinder onBind(Intent intent) {
    return binder;
}
public void onCreate() {
    super.onCreate();
}
public void onDestroy() {
    super.onDestroy();
    mCallbacks.kill();
}
private final IRestfulService.Stub binder = new IRestfulService.Stub() {
    public void doLogin(String username, String password) {

        Message msg = new Message();
        Bundle data = new Bundle();
        HashMap<String, String> values = new HashMap<String, String>();
        values.put("username", username);
        values.put("password", password);
        String result = post(Config.getURL("login"), values);
        data.putString("response", result);
        msg.setData(data);
        msg.what = Config.ACTION_LOGIN;
        mHandler.sendMessage(msg);
    }

    public void registerCallback(IRemoteServiceCallback cb) {
        if (cb != null)
            mCallbacks.register(cb);
    }
};

private final Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        // Broadcast to all clients the new value.
        final int N = mCallbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                switch (msg.what) {
                case Config.ACTION_LOGIN:
                    mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response"));
                    break;
                default:
                    super.handleMessage(msg);
                    return;

                }
            } catch (RemoteException e) {
            }
        }
        mCallbacks.finishBroadcast();
    }
    public String post(String url, HashMap<String, String> namePairs) {...}
    public String get(String url) {...}
};

Kilka plików AIDL:

package com.something.android

oneway interface IRemoteServiceCallback {
    void userLogIn(String result);
}

i

package com.something.android
import com.something.android.IRemoteServiceCallback;

interface IRestfulService {
    void doLogin(in String username, in String password);
    void registerCallback(IRemoteServiceCallback cb);
}

i kierownik serwisu:

public class ServiceManager {

    final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
    public IRestfulService restfulService;
    private RestfulServiceConnection conn;
    private boolean started = false;
    private Context context;

    public ServiceManager(Context context) {
        this.context = context;
    }

    public void startService() {
        if (started) {
            Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.startService(i);
            started = true;
        }
    }

    public void stopService() {
        if (!started) {
            Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.stopService(i);
            started = false;
        }
    }

    public void bindService() {
        if (conn == null) {
            conn = new RestfulServiceConnection();
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.bindService(i, conn, Context.BIND_AUTO_CREATE);
        } else {
            Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
        }
    }

    protected void destroy() {
        releaseService();
    }

    private void releaseService() {
        if (conn != null) {
            context.unbindService(conn);
            conn = null;
            Log.d(LOG_TAG, "unbindService()");
        } else {
            Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
        }
    }

    class RestfulServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName className, IBinder boundService) {
            restfulService = IRestfulService.Stub.asInterface((IBinder) boundService);
            try {
            restfulService.registerCallback(mCallback);
            } catch (RemoteException e) {}
        }

        public void onServiceDisconnected(ComponentName className) {
            restfulService = null;
        }
    };

    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        public void userLogIn(String result) throws RemoteException {
            mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result));

        }
    };

    private Handler mHandler;

    public void setHandler(Handler handler) {
        mHandler = handler;
    }
}

Usługa inicjowania i wiązania:

// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);

wywołanie funkcji serwisowej:

// this lot i'm calling as required - in this example for login
progressDialog = new ProgressDialog(Login.this);
progressDialog.setMessage("Logging you in...");
progressDialog.show();

application = (ApplicationState) getApplication();
servicemanager = application.getServiceManager();
servicemanager.setHandler(mHandler);

try {
    servicemanager.restfulService.doLogin(args[0], args[1]);
} catch (RemoteException e) {
    e.printStackTrace();
}

...later in the same file...

Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        switch (msg.what) {
        case Config.ACTION_LOGIN:

            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }

            try {
                ...process login results...
                }
            } catch (JSONException e) {
                Log.e("JSON", "There was an error parsing the JSON", e);
            }
            break;
        default:
            super.handleMessage(msg);
        }

    }

};

5
Może to być bardzo pomocne dla osób uczących się implementacji klienta Android REST. Prezentacja Dobjanschi przepisana na plik PDF: drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/...
Kay Zed

Ponieważ wiele osób poleciło prezentację Virgila Dobjanschi, a link do IO 2010 jest teraz zepsuty, oto bezpośredni link do filmu na YT: youtube.com/watch?v=xHXn3Kg2IQE
Jose_GD

Odpowiedzi:


283

Jeśli twoja usługa będzie częścią twojej aplikacji, czynisz ją znacznie bardziej złożoną, niż powinna. Ponieważ masz prosty przypadek użycia niektórych danych z usługi sieci Web RESTful, powinieneś zajrzeć do ResultReceiver i IntentService .

Ten wzorzec Service + ResultReceiver działa poprzez uruchomienie lub powiązanie z usługą za pomocą startService (), gdy chcesz wykonać jakąś akcję. Możesz określić operację do wykonania i przekazać w ResultReceiver (działanie) poprzez dodatki w celu.

W usłudze implementujesz onHandleIntent, aby wykonać operację określoną w parametrze Intent. Po zakończeniu operacji używasz przekazanego w ResultReceiver, aby wysłać wiadomość z powrotem do działania, w którym to momencie zostanie wywołany onReceiveResult .

Na przykład chcesz pobrać niektóre dane z usługi sieci Web.

  1. Tworzysz zamiar i wywołujesz startService.
  2. Operacja w usłudze rozpoczyna się i wysyła do działania komunikat informujący o rozpoczęciu
  3. Działanie przetwarza wiadomość i pokazuje postęp.
  4. Usługa kończy operację i przesyła niektóre dane z powrotem do Twojej aktywności.
  5. Twoja aktywność przetwarza dane i umieszcza je w widoku listy
  6. Usługa wysyła ci wiadomość, że jest zrobione i zabija się.
  7. Działanie otrzymuje komunikat o zakończeniu i ukrywa okno dialogowe postępu.

Wiem, że wspomniałeś, że nie chcesz bazy kodu, ale aplikacja Google I / O 2010 typu open source korzysta z usługi w taki sposób, który opisuję.

Zaktualizowano, aby dodać przykładowy kod:

Aktywność.

public class HomeActivity extends Activity implements MyResultReceiver.Receiver {

    public MyResultReceiver mReceiver;

    public void onCreate(Bundle savedInstanceState) {
        mReceiver = new MyResultReceiver(new Handler());
        mReceiver.setReceiver(this);
        ...
        final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class);
        intent.putExtra("receiver", mReceiver);
        intent.putExtra("command", "query");
        startService(intent);
    }

    public void onPause() {
        mReceiver.setReceiver(null); // clear receiver so no leaks.
    }

    public void onReceiveResult(int resultCode, Bundle resultData) {
        switch (resultCode) {
        case RUNNING:
            //show progress
            break;
        case FINISHED:
            List results = resultData.getParcelableList("results");
            // do something interesting
            // hide progress
            break;
        case ERROR:
            // handle the error;
            break;
    }
}

Obsługa:

public class QueryService extends IntentService {
    protected void onHandleIntent(Intent intent) {
        final ResultReceiver receiver = intent.getParcelableExtra("receiver");
        String command = intent.getStringExtra("command");
        Bundle b = new Bundle();
        if(command.equals("query") {
            receiver.send(STATUS_RUNNING, Bundle.EMPTY);
            try {
                // get some data or something           
                b.putParcelableArrayList("results", results);
                receiver.send(STATUS_FINISHED, b)
            } catch(Exception e) {
                b.putString(Intent.EXTRA_TEXT, e.toString());
                receiver.send(STATUS_ERROR, b);
            }    
        }
    }
}

Rozszerzenie ResultReceiver - edytowane w celu wdrożenia MyResultReceiver.Receiver

public class MyResultReceiver implements ResultReceiver {
    private Receiver mReceiver;

    public MyResultReceiver(Handler handler) {
        super(handler);
    }

    public void setReceiver(Receiver receiver) {
        mReceiver = receiver;
    }

    public interface Receiver {
        public void onReceiveResult(int resultCode, Bundle resultData);
    }

    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        if (mReceiver != null) {
            mReceiver.onReceiveResult(resultCode, resultData);
        }
    }
}

1
Wspaniale, dziękuję! Skończyłem przeglądanie aplikacji Google na iOS i wow ... to dość skomplikowane, ale mam coś, co działa, teraz muszę tylko dowiedzieć się, dlaczego to działa! Ale tak, to jest podstawowy wzorzec, który mam. Dziękuję Ci bardzo.
Martyn

29
Jeden mały dodatek do odpowiedzi: jak robisz mReceiver.setReceiver (null); w metodzie onPause powinieneś zrobić mReceiver.setReceiver (this); w metodzie onResume. W przeciwnym razie możesz nie otrzymywać wydarzeń, jeśli twoja aktywność zostanie wznowiona bez ponownego utworzenia
Vincent Mimoun-Prat

7
Czy dokumenty nie mówią, że nie musisz wywoływać stopSelf, ponieważ IntentService robi to za Ciebie?
Mikael Ohlson,

2
@MikaelOhlson Poprawnie, należy nie zadzwonić stopSelf, jeśli podklasy IntentServicebo jeśli to zrobisz, stracisz żadnych oczekujących żądań do sam IntentService.
quietmint,

1
IntentServicezabije się, gdy zadanie zostanie ukończone, więc nie this.stopSelf()jest konieczne.
Euporie

17

Tworzenie aplikacji klienckich REST dla Androida było dla mnie niesamowitym zasobem. Mówca nie pokazuje żadnego kodu, po prostu omawia rozważania projektowe i techniki w tworzeniu solidnego Rest Api w Androidzie. Jeśli jesteś osobą z podcastu, czy nie, polecam posłuchać tego co najmniej jednego, ale osobiście słuchałem go jak dotąd 4 lub 5 razy i prawdopodobnie będę go ponownie słuchał.

Tworzenie aplikacji klienckich REST dla Androida
Autor: Virgil Dobjanschi
Opis:

W tej sesji zostaną przedstawione rozważania architektoniczne dotyczące tworzenia aplikacji RESTful na platformie Android. Koncentruje się na wzorcach projektowych, integracji platformy i problemach wydajnościowych specyficznych dla platformy Android.

I jest tak wiele uwag, których tak naprawdę nie wziąłem pod uwagę w pierwszej wersji mojego interfejsu API, które musiałem zreformować


4
+1 Obejmuje to kwestie, o których nigdy nie pomyślałbyś, kiedy zaczynasz.
Thomas Ahle,

Tak, moją pierwszą próbą opracowania klienta Rest był prawie dokładnie jego opis tego, czego dokładnie nie robić (na szczęście zdałem sobie sprawę, że wiele z tego było nie tak, zanim to obejrzałem). Trochę na to lamentuję.
Terrance

Widziałem ten film więcej niż raz i wdrażam drugi wzorzec. Mój problem polega na tym, że muszę używać transakcji w złożonym modelu bazy danych, aby aktualizować lokalne dane ze świeżych danych pochodzących z serwera, a interfejs ContentProvider nie zapewnia mi takiej możliwości. Czy masz jakieś sugestie, Terrance?
Flávio Faria,

2
Uwaga: komentarze Dobjanschi na temat HttpClient nie są już aktualne. Zobacz stackoverflow.com/a/15524143/939250
Donal Lafferty

Tak, HttpURLConnection jest teraz preferowany. Ponadto wraz z wydaniem Androida 6.0 oficjalnie usunięto obsługę klienta HTTP Apache .
RonR

16

Również gdy trafiłem na wpis (Config.getURL („login”), wartości) aplikacja wydaje się na chwilę wstrzymywać (wydaje się dziwna - pomyślałam, że za usługą działa inny wątek!)

Nie, musisz samodzielnie utworzyć wątek, usługa lokalna domyślnie działa w wątku interfejsu użytkownika.




5

Chciałem tylko skierować was wszystkich w stronę niezależnej klasy, którą rzuciłem, która zawiera wszystkie funkcje.

http://github.com/StlTenny/RestService

Wykonuje żądanie jako nieblokujące i zwraca wyniki w łatwej do wdrożenia procedurze obsługi. Nawet pochodzi z przykładową implementacją.


4

Powiedzmy, że chcę uruchomić usługę w przypadku zdarzenia - onItemClicked () przycisku. W takim przypadku mechanizm odbiornika nie działałby, ponieważ:
a) przekazałem odbiornik do usługi (jak w przypadku zamiaru dodatkowego) z funkcji onItemClicked ()
b) aktywność przenosi się w tło. W funkcji onPause () ustawiam wartość referencyjną odbiornika w ResultReceiver na null, aby uniknąć wycieku działania.
c) Aktywność ulega zniszczeniu.
d) Aktywność zostanie utworzona ponownie. Jednak w tym momencie usługa nie będzie mogła oddzwonić do działania, ponieważ utracono referencję odbiorcy.
Mechanizm ograniczonej emisji lub PendingIntent wydaje się bardziej przydatny w takich scenariuszach - patrz Powiadomienie o aktywności z usługi


1
jest problem z tym, co mówisz. oznacza to, że gdy działanie przenosi się na tło, nie jest niszczone ... więc odbiornik nadal istnieje i kontekst działania również.
DArkO

@DArkO Gdy działanie zostanie wstrzymane lub zatrzymane, może zostać zabite przez system Android w sytuacjach braku pamięci. Zobacz Cykl życia aktywności .
jk7

4

Zauważ, że w jakiś sposób brakuje rozwiązania z Robby Pond: w ten sposób zezwalasz tylko na jedno wywołanie API naraz, ponieważ usługa IntentService obsługuje tylko jedną intencję na raz. Często chcesz wykonywać równoległe połączenia API. Jeśli chcesz to zrobić, musisz rozszerzyć usługę zamiast IntentService i utworzyć własny wątek.


1
Nadal możesz wykonywać wiele wywołań w usłudze IntentService, delegując wywołania interfejsu API usługi sieci Web do usługi wątku executora, prezentowanej jako zmienna członkowska klasy pochodzącej z usługi IntentService
Viren

2

Również gdy trafiłem na wpis (Config.getURL („login”), wartości) aplikacja wydaje się na chwilę wstrzymywać (wydaje się dziwna - pomyślałam, że za usługą działa inny wątek!)

W takim przypadku lepiej jest użyć asynctask, który działa na innym wątku i po zakończeniu zwraca wynik z powrotem do wątku interfejsu użytkownika.


2

Jest tutaj inne podejście, które zasadniczo pomaga zapomnieć o całym zarządzaniu żądaniami. Opiera się na metodzie kolejki asynchronicznej i odpowiedzi opartej na wywołaniu zwrotnym / wywołaniu zwrotnym. Główną zaletą jest to, że dzięki tej metodzie będziesz w stanie uczynić cały proces (żądanie, odpowiedź i parsowanie odpowiedzi, sabe do db) całkowicie przejrzysty. Po otrzymaniu kodu odpowiedzi praca jest już zakończona. Następnie wystarczy zadzwonić do bazy danych i gotowe. Pomaga również w problematyce tego, co dzieje się, gdy twoja aktywność nie jest aktywna. To, co się tutaj stanie, to zapisanie wszystkich danych w lokalnej bazie danych, ale odpowiedź nie zostanie przetworzona przez Twoją aktywność, to idealny sposób.

Pisałem o ogólnym podejściu tutaj http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

W nadchodzących postach wstawię konkretny przykładowy kod. Mam nadzieję, że to pomoże, skontaktuj się ze mną w sprawie podzielenia się tym podejściem i rozwiązania potencjalnych wątpliwości lub problemów.


Martwy link. Domena wygasła.
jk7

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.