Связь между службами Android и действиями

см. архитектуру

Я хочу разработать приложение для Android с тремя действиями и двумя службами.

Первая служба с именем WebClientService вызывает REST API каждые 30 секунд, используя обработчик, и должна уведомить активное действие о результате. Он также должен уведомить вторую службу с именем DatabaseService, чтобы обновить локальную БД.

Служба базы данных будет вызываться только один раз onCreate действия (в случае сбоя и перезапуска приложения) и только один раз при onRestart (таким образом у нас есть данные для отображения в если были проблемы с подключением). Затем действия будут обновляться благодаря службе WebClientService, которая уведомляет об «активных» действиях каждые 30 секунд.

Вопросы:

  • Каков наилучший способ уведомить об обновлении как активной активности, так и фоновой службы базы данных? Моя идея состоит в том, чтобы использовать sendBroadcast() в WebClientService и BroadcastReceiver в каждом действии и в пределах DatabaseService, это правильный подход?

  • Должен ли я использовать тот же подход для связи между AllMeetingRoomActivity и DatabaseService или должен использовать Bound Служба?

Спасибо

ОБНОВЛЕНИЕ: DatabaseService больше не будет фоновой службой, а будет просто общим экземпляром уровня базы данных между WebClientService и действиями.

Итак, теперь возникает вопрос: является ли хорошим подходом просто записывать мои 30-секундные обновления в локальную базу данных и разрешать действиям обновлять себя каждые несколько секунд, просто читая из локальной базы данных? Не слишком ли это повлияет на производительность?

Контекст:

Следует тому, что я реализовал до сих пор, но с использованием SettableFutures, и поэтому его необходимо повторно реализовать с помощью Services и Broadcasts, как только я пойму, как заставить их эффективно взаимодействовать:

public class MainActivity extends AppCompatActivity {               

    private TextView meetingsTextView;
    private EditText mEdit, editSubject;

    private final ConnectorInitializer clientInitializer = new ConnectorInitializer();
    private AppConnector genericClient; // can use OutlookClient or a test client to talk with a mock server

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // initializes client based on the settings in "config.json"
        genericClient = clientInitializer.create(this);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        meetingsTextView = (TextView) findViewById(R.id.NowMeeting);
        mEdit   = (EditText)findViewById(R.id.editText);
        editSubject   = (EditText)findViewById(R.id.editSubject);

        Futures.addCallback(genericClient.logon(this, scopes), new FutureCallback<Boolean>() {
            @Override
            public void onSuccess(Boolean result) {
                Log.d("APP", "-- Logged in. --");

                databaseConnector.synchronouslyGetBackupFromLocalDatabase() // FUTURE
                // callback here
                // onSuccess, onFailure

            }

            @Override
            public void onFailure(@NonNull Throwable t) {
                Log.e("\n ~~~~>> logon \n", t.getMessage());
                meetingsTextView.setText(R.string.Login_Failed);
            }
        });

    }

    /** At the moment the UI is not updated automatically every 30 seconds
    *   but manually using a refresh button
    */
    public void getBookings(@SuppressWarnings("UnusedParameters") View view){

        Log.d("APP", "Retrieve button clicked: "+(DateTime.now())+". Calling async getCalendar.");
        meetingsTextView.setText(R.string.retrieving_events);

        try{
            Futures.addCallback( genericClient.getCalendarEvents(), new FutureCallback<String>(){
                @Override
                public void onSuccess(final String resultCalendars) {

                    Log.d("APP", "Success. Result: "+resultCalendars);

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {

                            Log.d("APP", "Calendars SUCCESSFULLY retrieved.");
                            String meetingsRetrieved = getString(R.string.calendar)+resultCalendars;
                            meetingsTextView.setText(meetingsRetrieved);

                            Toast.makeText(getApplicationContext(), "Success!", Toast.LENGTH_LONG).show();
                        }
                    });

                    databaseConnector.asyncUpdateLocalDbWithResults(); // FUTURE
                    // callback here
                    // onSuccess, onFailure

               }

                @Override
                public void onFailure(@NonNull Throwable t) {
                    Log.e( "APP", "Calendar error. Cause: "+t.getLocalizedMessage() );
                    String retrieveError = "Retrieve error. \n\n\n"+t.getLocalizedMessage();
                    meetingsTextView.setText(retrieveError);
                    Toast.makeText(getApplicationContext(), "Fail!", Toast.LENGTH_LONG).show();
                }
            });

        }catch(Exception ex){    
            Log.e("APP","Something went wrong in your code. Cause:"+ex);
        }
    }

person Gabe    schedule 10.04.2016    source источник
comment
Можете ли вы объяснить с каким-то псевдокодом?   -  person Slim_user71169    schedule 10.04.2016
comment
Я так смущен, потому что я не знаю, что ты действительно хочешь сделать   -  person Slim_user71169    schedule 10.04.2016
comment
@ Slim_user71169 Slim_user71169 Я перефразировал вопрос и приложил тонкую диаграмму, чтобы проиллюстрировать желаемую архитектуру. Спасибо   -  person Gabe    schedule 10.04.2016
comment
stackoverflow.com/a/27652660/4317945   -  person Gabe    schedule 22.04.2016
comment
Из описания кажется, что ваш DatabaseService вместо этого должен быть ContentProvider - тогда вы можете использовать ContentObservers и другие существующие API вместо того, чтобы пытаться изобретать колесо   -  person RocketRandom    schedule 22.04.2016
comment
попробуйте использовать библиотеку OttoBus, которая является облегченной системой шины событий для Android от Squre.. square.github.io/ отто   -  person Moinkhan    schedule 25.04.2016
comment
Широковещательные приемники или ResultReceiver будут лучшими вариантами для достижения этой цели.   -  person Ankit Aggarwal    schedule 25.04.2016
comment
@ankitaggarwal О ResultReceiver я читал, что соединение разорвется, если ваш процесс пойдет далеко по любой причине. Подходит ли тогда BrodcastReceiver лучше всего? Моя служба должна будет всегда работать в рабочее время. Спасибо   -  person Gabe    schedule 25.04.2016


Ответы (4)


Я думаю, что ваш подход подходит к BroadCastReceiver. Однако BroadCastReceiver следует использовать для глобальных целей (например, для связи между двумя приложениями). Если вы собираетесь использовать BroadCastReceiver только для своего приложения, я предпочитаю использовать LocalBroadcastManager. вместо этого. Использование LocalBroadcastManager быстрее и безопаснее, когда оно может быть перехвачено только вашим приложением.

Есть еще один способ связи между вашими activity и service с помощью EventBus. Это будет намного проще, чем использовать BroadCastReceiver (особенно при передаче данных между ними).

Обновление: о вашем вопросе об обновлении:

  1. это хороший подход, чтобы просто записать мои 30-секундные обновления в локальную базу данных и разрешить действиям обновлять себя каждые несколько секунд, просто читая из локальной базы данных? --> Конечно НЕТ. Вы должны позволить своим действиям обновляться, когда они в этом нуждаются. Когда вы обновляете свою локальную базу данных, вы должны знать, есть ли какие-либо изменения или нет. Если есть какие-либо изменения, используйте LocalBroadcastmanager, чтобы сообщить о своей активности для обновления.
  2. Не сильно ли это повлияет на производительность? --> Да, что делать. Для выполнения соединения с базой данных потребуется время, и в некоторых случаях оно будет блокировать ваш пользовательский интерфейс. в этом случае вы должны использовать поток с ExecutorService для каждого execute (вставка, обновление...). Еще одна вещь, которую следует учитывать, — это частое обновление, которое очень и очень быстро разряжает аккумулятор вашего телефона.
person Kingfisher Phuoc    schedule 10.04.2016
comment
Я обновил вопрос и добавил награду. Спасибо :) - person Gabe; 20.04.2016
comment
Спасибо дружище, очень полезно! - person Gabe; 21.04.2016
comment
На данный момент мне интересно, должен ли я просто отправить данные, которые мне нужны, чтобы обновить действия внутри уведомлений LocalBrodcastManager и поцарапать слой базы данных. Мне не нужно, чтобы эти данные были устойчивыми к сбоям, поскольку моим источником правды является серверная часть Outlook. Во всяком случае, мне нужно, чтобы WebClientService был фоновым сервисом, потому что он также будет отправлять текущие пользовательские данные во внутреннюю службу, чтобы делиться ими с другими устройствами, используя разных пользователей, и я не хочу, чтобы эти общие данные устарели, если пользователь использует некоторые другие действия (например, настройки и т. д.). Думаете, это хороший подход? ТА - person Gabe; 21.04.2016
comment
@GaSacchi Вы можете обновить свою активность с помощью LocalBroadCastReceiver. Однако я не думаю, что вызывать службу restful каждые 30 секунд — хорошая идея. Это разрядит вашу батарею очень быстро, даже быстрее, чем вы думаете. По моему мнению, 1 хороший подход для синхронизации данных между несколькими устройствами: вариант 1. Использование сеансов (для 1 онлайн-устройства за раз) вариант 2. Использование облачных сообщений Google для уведомления устройства об обновлении, когда оно необходимо, вместо обновления каждые 30 секунд. . - person Kingfisher Phuoc; 21.04.2016
comment
Ну, устройство будет в стене и подключено к источнику питания, поэтому потребление батареи не является проблемой. - person Gabe; 21.04.2016
comment
Я выбрал ваш ответ за его полноту. - person Gabe; 27.04.2016

Лучший вариант когда-либо:

Используйте 1_. Дополнительная информация здесь.

MyService.java:

private LocalBroadcastManager localBroadcastManager;
private final String SERVICE_RESULT = "com.service.result";
private final String SERVICE_MESSAGE = "com.service.message";

@Override
public void onCreate() {
    super.onCreate();

   // Other stuff

   localBroadcastManager = LocalBroadcastManager.getInstance(this);
}

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

private void sendResult(String message) {
    Intent intent = new Intent(SERVICE_RESULT);
    if(message != null)
        intent.putExtra(SERVICE_MESSAGE, message);
    localBroadcastManager.sendBroadcast(intent);
}

HomeActivity.java:

private BroadcastReceiver broadcastReceiver;

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.setContentView(R.layout.activity_home);
    broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String s = intent.getStringExtra(MyService.SERVICE_MESSAGE);
            // do something here.
        }
    };
}

@Override
protected void onStart() {
    super.onStart();
    LocalBroadcastManager.getInstance(this).registerReceiver((broadcastReceiver), 
        new IntentFilter(MyService.SERVICE_RESULT));
}

@Override
protected void onStop() {
    LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
    super.onStop();
}

Надеюсь, что это поможет вам.

person Hiren Patel    schedule 21.04.2016
comment
SERVICE_MESSAGE также может быть объектом DTO, который я передаю через действия, верно? Спасибо - person Gabe; 21.04.2016
comment
И из соображений безопасности... другие приложения не смогут получить эти широковещательные намерения, отправленные с помощью LocalBroadcasManager, верно? Еще раз спасибо - person Gabe; 21.04.2016
comment
@GaSacchi, да, вы можете передать любой объект с помощью этого, и в целях безопасности он зависит только от приложения, поэтому другие приложения не могут его получить. - person Hiren Patel; 21.04.2016
comment
Спасибо за вашу помощь. Я выбрал ответ Kingfisher Phuoc за его полноту. - person Gabe; 27.04.2016

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

person Isaac Urbina    schedule 26.04.2016

Используйте шину событий для этой связи. EventBus позволяет осуществлять связь между компонентами в стиле публикации-подписки, не требуя, чтобы компоненты явно регистрировались друг с другом (и, таким образом, знали друг о друге). Он предназначен исключительно для замены традиционного внутрипроцессного распределения событий Java с использованием явной регистрации.

Их очень много:

http://square.github.io/otto/

https://github.com/greenrobot/EventBus

Это пример использования Отто:

Bus bus = new Bus();

bus.post(new AnswerAvailableEvent(42));

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
    // TODO: React to the event somehow!
}

bus.register(this); // In order to receive events, a class instance needs to register with the bus.

Чтобы публиковать сообщения из любого потока (основного или фонового), в вашем случае это Служба и получать события в основном потоке:

public class MainThreadBus extends Bus {
  private final Handler mHandler = new Handler(Looper.getMainLooper());

  @Override
  public void post(final Object event) {
    if (Looper.myLooper() == Looper.getMainLooper()) {
      super.post(event);
    } else {
      mHandler.post(new Runnable() {
        @Override
        public void run() {
          MainThreadBus.super.post(event);
        }
      });
    }
  }
person Bogdan Ustyak    schedule 28.04.2016