Restful API сервис

226

Я ищу услугу, которую я могу использовать для вызова веб-интерфейса REST API.

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

Я создал сервис, который в настоящее время использует IDL, я где-то читал, что вам это действительно нужно только для взаимодействия между приложениями, поэтому подумайте, что это нужно, но не знаете, как делать обратные вызовы без него. Кроме того, когда я нажимаю на post(Config.getURL("login"), values)приложение, приложение на некоторое время приостанавливается (кажется странным - мысль о том, что служба основана на том, что она работает в другом потоке!)

В настоящее время у меня есть сервис с методами post и get внутри, пара файлов AIDL (для двусторонней связи), ServiceManager, который занимается запуском, остановкой, привязкой и т. Д. К сервису, и я динамически создаю обработчик с определенным кодом для обратных вызовов по мере необходимости.

Я не хочу, чтобы кто-нибудь давал мне полную кодовую базу для работы, но некоторые указатели будут с благодарностью.

Код в основном (в основном):

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) {...}
};

Пара файлов AIDL:

package com.something.android

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

и

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

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

и менеджер сервиса:

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;
    }
}

Служба init и bind:

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

вызов сервисной функции:

// 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);
        }

    }

};
Martyn
источник
5
Это может быть очень полезно для людей, изучающих реализацию клиента Android REST. Презентация Добьянского транскрибирована в PDF: drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/…
Кей Зед
Так как многие люди рекомендовали презентацию Вирджила Добжански и ссылка на IO 2010 теперь не работает, вот прямая ссылка на видео YT: youtube.com/watch?v=xHXn3Kg2IQE
Jose_GD

Ответы:

283

Если ваш сервис станет частью вашего приложения, то вы сделаете его более сложным, чем нужно. Поскольку у вас есть простой вариант использования получения данных из веб-службы RESTful, вам следует изучить ResultReceiver и IntentService .

Этот шаблон Service + ResultReceiver работает путем запуска или привязки к службе с помощью startService (), когда вы хотите выполнить какое-либо действие. Вы можете указать операцию, которую нужно выполнить, и передать ее в ResultReceiver (действие) через дополнительные функции в Intent.

В сервисе вы реализуете onHandleIntent для выполнения операции, указанной в Intent. Когда операция завершена, вы используете переданный в ResultReceiver, чтобы отправить сообщение обратно в Activity, после чего будет вызван onReceiveResult .

Так, например, вы хотите получить некоторые данные из вашего веб-сервиса.

  1. Вы создаете намерение и вызываете startService.
  2. Операция в сервисе запускается и отправляет активности сообщение о том, что она запущена
  3. Операция обрабатывает сообщение и показывает прогресс.
  4. Служба завершает операцию и отправляет некоторые данные в вашу деятельность.
  5. Ваша деятельность обрабатывает данные и помещает их в список
  6. Служба отправляет вам сообщение о том, что это сделано, и убивает себя.
  7. Действие получает сообщение о завершении и скрывает диалог прогресса.

Я знаю, что вы упомянули, что вам не нужна база кода, но приложение Google I / O 2010 с открытым исходным кодом использует службу, как я описываю.

Обновлено, чтобы добавить пример кода:

Активность.

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;
    }
}

Сервис:

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);
            }    
        }
    }
}

Расширение ResultReceiver - отредактировано для реализации 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
Отлично - спасибо! Я закончил тем, что просматривал приложение Google iosched и ничего себе ... это довольно сложно, но у меня есть кое-что, что работает, теперь мне просто нужно понять, почему это работает! Но да, это основная модель, с которой я работаю. Большое спасибо.
Мартын
29
Небольшое дополнение к ответу: как и mReceiver.setReceiver (null); в методе onPause вы должны выполнить mReceiver.setReceiver (this); в методе onResume. В противном случае вы можете не получать события, если ваша деятельность возобновляется без воссоздания
Винсент Мимун-Прат
7
Разве документы не говорят, что вам не нужно вызывать stopSelf, так как IntentService делает это для вас?
Микаэль Олсон
2
@MikaelOhlson Правильно, вам не следует звонить, stopSelfесли вы подкласс, IntentServiceпотому что если вы это сделаете, вы потеряете все ожидающие запросы к тому же IntentService.
тишина
1
IntentServiceубьет себя, когда задача будет завершена, так this.stopSelf()что нет необходимости.
Euporie
17

Разработка клиентских приложений REST для Android стала для меня отличным ресурсом. Спикер не показывает никакого кода, он просто перебирает конструктивные соображения и приемы создания идеального Rest Api в Android. Если вы любите подкаст или нет, я бы порекомендовал дать этому хотя бы одно прослушивание, но лично я слушал его как 4 или 5 раз и, вероятно, я буду слушать его снова.

Разработка клиентских приложений REST для Android
Автор: Virgil Dobjanschi
Описание:

На этом занятии будут представлены архитектурные аспекты разработки приложений RESTful на платформе Android. Основное внимание уделяется шаблонам проектирования, интеграции платформы и проблемам производительности, характерным для платформы Android.

И есть так много соображений, которые я действительно не сделал в первой версии моего API, что мне пришлось рефакторинг

Терренс
источник
4
+1 Здесь содержатся такие соображения, о которых вы никогда не подумаете, когда начинаете.
Томас Але
Да, моя первая попытка разработки клиента Rest была почти точно его описанием того, что именно не нужно делать (к счастью, я понял, что многое из этого было неправильно, прежде чем я это увидел). Я немного расслабился.
Терренс
Я видел это видео более одного раза, и я реализую второй шаблон. Моя проблема заключается в том, что мне нужно использовать транзакции в моей сложной модели базы данных для обновления локальных данных из свежих данных, поступающих с сервера, а интерфейс ContentProvider не позволяет мне это сделать. У тебя есть предложения, Терранс?
Флавио Фариа
2
NB: комментарии Добянского о HttpClient больше не действуют. См. Stackoverflow.com/a/15524143/939250
Донал Лафферти
Да, HttpURLConnection теперь предпочтительнее. Кроме того, с выпуском Android 6.0 поддержка HTTP-клиента Apache была официально удалена .
RonR
16

Кроме того, когда я нажимаю на сообщение (Config.getURL («login»), значения), приложение, кажется, приостанавливается на некоторое время (кажется странным - мысль о том, что служба основана на том, что она работает в другом потоке!)

Нет, вам нужно создавать поток самостоятельно, по умолчанию локальный сервис запускается в потоке пользовательского интерфейса.

Соумия Симанта
источник
11

Я знаю, что @Martyn не хочет полный код, но я думаю, что эта аннотация хороша для этого вопроса:

10 Android-приложений с открытым исходным кодом, которые должен изучить каждый разработчик Android

Foursquared для Android имеет открытый код и имеет интересный шаблон кода, взаимодействующий с REST API foursquare.

panchicore
источник
6

Я очень рекомендую Retrofit клиента REST .

Я нашел этот хорошо написанный пост в блоге чрезвычайно полезным, он также содержит простой пример кода. Автор использует Retrofit для выполнения сетевых вызовов и Otto для реализации шаблона шины данных:

http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html

Пит
источник
5

Я просто хотел показать вам все в направлении отдельного класса, который я прокатил, который включает в себя все функции.

http://github.com/StlTenny/RestService

Он выполняет запрос как неблокирующий и возвращает результаты в простой для реализации обработчик. Даже поставляется с примером реализации.

StlTenny
источник
4

Допустим, я хочу запустить службу для события - onItemClicked () кнопки. Механизм Receiver не будет работать в этом случае, потому что:
a) я передал Receiver службе (как в Intent extra) из onItemClicked ()
b) действие переходит в фоновый режим. В onPause () я установил для ссылки получателя в ResultReceiver значение null, чтобы избежать утечки Activity.
в) Деятельность разрушается.
г) Активность создается снова. Однако в этот момент Служба не сможет выполнить обратный вызов для Действия, так как эта ссылка на получателя потеряна.
Механизм ограниченной широковещательной рассылки или PendingIntent кажется более полезным в таких сценариях - см. Уведомление об активности от службы.

Nikhil_Katre
источник
1
есть проблема с тем, что вы говорите. то есть когда действие переходит в фоновый режим, оно не уничтожается ... поэтому получатель все еще существует, а также контекст действия.
DArkO
@DArkO Когда действие приостановлено или остановлено, оно может быть уничтожено системой Android в ситуациях нехватки памяти. См. Жизненный цикл деятельности .
jk7
4

Обратите внимание, что решение от Robby Pond почему-то отсутствует: таким образом, вы позволяете выполнять только один вызов API за один раз, поскольку IntentService обрабатывает только одно намерение за раз. Часто вы хотите выполнять параллельные вызовы API. Если вы хотите сделать это, вы должны расширить Service вместо IntentService и создать свой собственный поток.

TjerkW
источник
1
Вы все еще можете сделать несколько вызовов на IntentService путем делегирования вебсервис API вызовы к исполнителю-токарно-службы, присутствует в качестве переменной - члена вашей IntentService-производного класса
Viren
2

Кроме того, когда я нажимаю на сообщение (Config.getURL («login»), значения), приложение, кажется, приостанавливается на некоторое время (кажется странным - мысль о том, что служба основана на том, что она работает в другом потоке!)

В этом случае лучше использовать asynctask, который выполняется в другом потоке и возвращает результат обратно в поток пользовательского интерфейса после завершения.

Aakash
источник
2

Здесь есть другой подход, который в основном помогает вам забыть обо всем управлении запросами. Он основан на методе асинхронной очереди и ответе на основе вызываемого / обратного вызова. Основное преимущество заключается в том, что с помощью этого метода вы сможете сделать весь процесс (запрос, получить и проанализировать ответ, sabe to db) полностью прозрачным для вас. Как только вы получите код ответа, работа уже сделана. После этого вам просто нужно позвонить на ваш БД и все готово. Это также помогает с проблемой того, что происходит, когда ваша активность неактивна. Здесь произойдет то, что все ваши данные будут сохранены в вашей локальной базе данных, но ответ не будет обработан вашей деятельностью, это идеальный способ.

Я писал об общем подходе здесь http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

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

Хосе Л Угия
источник
Мертвая ссылка. Срок действия домена истек.
jk7
1

Робби дает отличный ответ, хотя я вижу, что вы все еще ищете дополнительную информацию. Я реализовал вызовы REST API легко, но неправильно. Только когда я посмотрел это видео ввода / вывода Google , я понял, где я ошибся. Это не так просто, как собрать AsyncTask с вызовом get / put HttpUrlConnection.

Эндрю Халлоран
источник
Мертвая ссылка. Вот обновленный - Google I / O 2010 - клиентские REST-приложения для Android
zim