Служба відпочинку API


226

Я хочу зробити послугу, яку можна використовувати для здійснення дзвінків до веб-інтерфейсу API REST.

В основному я хочу запустити послугу в додатку init, тоді я хочу мати можливість запитати цю службу, щоб запитувати URL і повертати результати. Тим часом я хочу мати змогу відобразити вікно прогресу чи щось подібне.

Я створив сервіс, який зараз використовує IDL, я десь читав, що вам потрібне це лише для перехресного спілкування додатків, тому подумайте, що ці потреби знімаються, але не знаєте, як робити зворотні дзвінки без нього. Також, коли я натискаю post(Config.getURL("login"), values)додаток, схоже, на деякий час призупиняється (здається дивним - думка, що стоїть під сервісом, полягала в тому, що він працює на іншій нитці!)

В даний час у мене є служба з методами публікації та отримання http всередині, пара файлів 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);
        }

    }

};

5
Це може бути дуже корисно для людей, які навчаються впровадженню клієнта Android REST. Презентація Dobjanschi перетворена на PDF: drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/…
Kay Zed

Оскільки багато людей рекомендували презентацію Вергілія Доб'янського, а посилання на IO 2010 порушено, ось пряме посилання на відео YT: youtube.com/watch?v=xHXn3Kg2IQE
Jose_GD

Відповіді:


283

Якщо ваша послуга буде частиною вашої програми, то ви робите це складніше, ніж це повинно бути. Оскільки у вас є простий випадок використання даних щодо отримання RESTful веб-служби, вам слід заглянути в ResultReceiver та IntentService .

Цей шаблон Service + ResultReceiver працює, запускаючи або прив’язуючи до служби за допомогою startService (), коли ви хочете виконати якусь дію. Ви можете вказати операцію, яку слід виконати і пропустити у своєму ResultReceiver (активність) через додаткові елементи в Намірі.

У сервісі, який ви реалізуєте onHandleIntent, щоб виконати операцію, вказану в Намір. Коли операція завершена, ви використовуєте передане в ResultReceiver повідомлення для того, щоб відправити повідомлення в Діяльність, в який момент буде викликано 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 (це); у методі onResume. Інакше ви не зможете отримувати події, якщо ваша діяльність буде відновлена ​​без відновлення
Vincent Mimoun-Prat

7
Чи не кажуть документи, що вам не доведеться телефонувати stopSelf, оскільки IntentService робить це за вас?
Мікаел Олсон

2
@MikaelOhlson Правильно, ви не повинні дзвонити, stopSelfякщо ви підклас, IntentServiceтому що, якщо ви це зробите, ви втратите будь-які очікувані запити до того ж IntentService.
тихий хвірт

1
IntentServiceбуде вбивати себе, коли завдання буде виконано, тому this.stopSelf()зайве.
Euporie

17

Розробка клієнтських програм Android REST була для мене приголомшливим ресурсом. Доповідач не показує жодного коду, він просто переживає дизайнерські міркування та методики зібрання міцного скелі Rest Api в android. Якщо ваша людина з подкастом чи ні, я рекомендую дати цьому принаймні один прослуховування, але особисто я до цього часу його прослуховував, як 4 або п’ять разів, і, мабуть, буду слухати його ще раз.

Розробка клієнтських програм Android REST
Автор: Virgil Dobjanschi
Опис:

На цьому сеансі будуть представлені архітектурні міркування щодо розробки програм RESTful на платформі Android. Він фокусується на моделях дизайну, інтеграції платформи та питаннях продуктивності, характерних для платформи Android.

І є так багато міркувань, які я насправді не робив у першій версії мого api, що мені довелося переробляти


4
+1 Тут містяться міркування, про які ви ніколи не замислювались, починаючи.
Thomas Ahle

Так, моє перше натхнення на розробку клієнта «Відпочинок» було майже точно його описом того, що саме не потрібно робити (на щастя, я зрозумів, що багато цього було неправильним, перш ніж я це спостерігав). Я трохи потримався на цьому.
Терранс

Я бачив це відео не раз і реалізую другий зразок. Моя проблема полягає в тому, що мені потрібно використовувати транзакції в моїй складній моделі бази даних для оновлення локальних даних зі свіжих даних, що надходять із сервера, а інтерфейс ContentProvider не дає мені способу це зробити. Чи є у вас якісь пропозиції, Терренсе?
Flávio Faria

2
NB: Коментарі Dobjanschi щодо HttpClient більше не дотримуються. Дивіться stackoverflow.com/a/15524143/939250
Donal Lafferty

Так, тепер кращим є HttpURLConnection . Крім того, з випуском Android 6.0 підтримка HTTP-клієнта Apache офіційно була видалена .
RonR

16

Крім того, коли я потрапляю на публікацію (Config.getURL ("логін"), значення), програма, здається, на деякий час призупиняється (здається дивним - думка, що стоїть під сервісом, полягає в тому, що вона працює на іншому потоці!)

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


11

Я знаю, що @Martyn не хоче повного коду, але я вважаю, що це примітка корисне для цього питання:

10 програм із відкритим кодом для Android, на які повинен звернути увагу кожен розробник Android

Квадратний квадратик для Android є відкритим кодом , і він має цікавий зразок коду, що взаємодіє з четвертим API REST.


6

Я настійно рекомендую клієнту РЕСТОФІТУ .

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

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


5

Я просто хотів спрямувати вас у бік самостійного класу, який я прокатував, який містить усі функції.

http://github.com/StlTenny/RestService

Він виконує запит як не блокуючий, а результати повертає у простому для реалізації інструменті обробки. Навіть йде з прикладом реалізації.


4

Скажімо, я хочу запустити послугу на подію - onItemClicked () кнопки. У такому випадку механізм Receiver не працює, тому що: -
а) я передав приймач в службу (як у намірі додатково) від onItemClicked ()
b) активність переміщується на другий план. У режимі onPause () я встановлюю посилання приймача в ResultReceiver на нульове значення, щоб уникнути витоку активності.
в) Діяльність знищується.
г) Діяльність створюється заново. Однак у цей момент Служба не зможе здійснити зворотний виклик діяльності, оскільки ця посилання приймача втрачена.
Механізм обмеженого мовлення або PendingIntent здається більш корисним у таких сценаріях - зверніться до Повідомити про діяльність від служби


1
є проблема з тим, що ви говорите. тобто, коли активність переходить на другий план, вона не знищується ... тож приймач все ще існує і контекст діяльності також.
DArkO

@DArkO Якщо діяльність призупинена або припинена, система Android може знищити її в ситуаціях з низькою пам’яттю. Зверніться до життєвого циклу діяльності .
jk7

4

Зауважте, що рішення від Robby Pond якимось не вистачає: таким чином ви дозволяєте лише один дзвінок api одночасно, оскільки IntentService обробляє лише один намір за один раз. Часто ви хочете виконувати паралельні дзвінки api. Якщо ви хочете зробити це, вам доведеться розширити Сервіс замість IntentService і створити власну нитку.


1
Ви все ще можете робити кілька дзвінків на IntentService, делегуючи виклики api веб-сервісу до служби виконавця-потоку, представленої як змінну члена вашого похідного класу IntentService
Viren

2

Крім того, коли я потрапляю на публікацію (Config.getURL ("логін"), значення), програма, здається, на деякий час призупиняється (здається дивним - думка, що стоїть під сервісом, полягає в тому, що вона працює на іншому потоці!)

У цьому випадку краще використовувати асинтакт, який працює на іншій нитці і повертає результат назад до потоку ui після завершення.


2

Тут є ще один підхід, який в основному допомагає вам забути про все управління запитами. Він заснований на методі черги асинхронізації та відповіді на основі дзвінка / зворотного дзвінка. Основна перевага полягає в тому, що, використовуючи цей метод, ви зможете зробити весь процес (запит, отримання та аналіз відповіді, перехід на db) повністю прозорим для вас. Після отримання коду відповіді робота вже завершена. Після цього вам просто потрібно зателефонувати на ваш db, і ви закінчите. Це допомагає також з проблематикою того, що відбувається, коли ваша діяльність не активна. Тут відбудеться те, що ви збережете всі свої дані у вашій локальній базі даних, але відповідь не буде оброблена вашою діяльністю, це ідеальний спосіб.

Про загальний підхід я писав тут http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

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


Мертве посилання. Термін дії домену закінчився.
jk7

1

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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.