【问题标题】:Communication between Android Services and ActivitiesAndroid 服务和活动之间的通信
【发布时间】:2016-04-10 03:42:47
【问题描述】:

我想开发一个包含三个活动和两个服务的 Android 应用程序。

第一个名为 WebClientService 的服务使用 Handler 每 30 秒调用一次 REST API,并且必须将结果通知活动的 Activity。 它还必须通知名为 DatabaseService 的第二个服务,以便更新本地数据库。

数据库服务将仅在活动的一次onCreate 调用(以防应用崩溃和重启)并且仅在onRestart 调用一次(这样我们就有了数据以防万一出现连接问题)。借助每 30 秒通知“活动”活动的 WebClientService,活动将保持自身更新。

问题是:

  • 通知活动活动和后台 DatabaseService 更新的最佳方式是什么? 我的想法是在 WebClientService 中使用 sendBroadcast() 并在每个活动和 DatabaseService 中使用 BroadcastReceiver,这是正确的方法吗?

  • 我应该使用相同的方法在 AllMeetingRoomActivity 和 DatabaseService 之间进行通信还是应该使用Bound Service?

谢谢

更新: DatabaseService 将不再是后台服务,而只是 WebClientService 和活动之间的 db 层的共享实例。

所以现在的问题是:将我的 30 秒更新写入本地数据库并允许活动每隔几秒钟更新一次,只需从本地数据库读取,这是一个好方法吗? 这会对性能造成太大影响吗?

上下文:

遵循我迄今为止实现的内容,但使用 SettableFutures,因此一旦我清楚如何使它们有效通信,就需要使用服务和广播重新实现:

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

【问题讨论】:

  • 你能用一些伪代码解释一下吗?
  • 我很困惑,因为我不知道你真正想做什么
  • @Slim_user71169 我重新表述了这个问题,并附上了一张简图来说明所需的架构。谢谢
  • 从描述看来,您的 DatabaseService 应该改为 ContentProvider - 然后您可以使用 ContentObservers 和其他现有 API,而不是尝试重新发明轮子
  • 广播接收器或 ResultReceiver 将是实现这一目标的最佳选择

标签: android android-activity android-service localbroadcastmanager


【解决方案1】:

有史以来最好的选择:

使用LocalBroadcastManager。更多参考here.

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

在服务中添加下面的方法,当你想从服务更新数据到活动时,通过传递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();
}

希望这会对你有所帮助。

【讨论】:

  • SERVICE_MESSAGE 也可以是我传递活动的 DTO 对象,对吧?谢谢
  • 出于安全考虑.. 其他应用程序将无法使用 LocalBroadcasManager 获取这些广播意图,对吧?再次感谢
  • @GaSacchi,是的,您可以使用这个传递任何对象,并且为了安全起见,它仅依赖于应用程序,因此其他应用程序无法获取它。
  • 感谢您的帮助。我选择 Kingfisher Phuoc 的答案是因为它的完整性。
【解决方案2】:

我认为BroadCastReceiver 可以使用您的方法。但是,BroadCastReceiver 应该用于全局目的(例如在 2 个应用程序之间进行通信)。如果您打算仅将BroadCastReceiver 用于您的应用程序,我更喜欢使用LocalBroadcastManager。当LocalBroadcastManager 只能被您的应用捕获时,使用LocalBroadcastManager 会更快、更安全。

您的activitys 和您的services 之间还有另一种通信方式是使用EventBus。这将比使用BroadCastReceiver 容易得多(尤其是在它们之间传递数据时)。

更新:关于您的更新问题:

  1. 仅将我的 30 秒更新写入本地数据库并允许活动每隔几秒钟更新一次,只需从本地数据库读取,这是一个好方法吗? --> 当然不是。您应该让您的活动在需要时自行更新。当您更新本地数据库时,您应该知道是否有任何更改。如果有任何变化,请使用LocalBroadcastmanager 通知您的活动进行更新。
  2. 这会对性能造成太大影响吗? --> 对,就是这样。 db 连接需要时间来执行,并且在某些情况下会阻塞你的 UI。在这种情况下,您应该为每个execute 使用一个带有ExecutorService 的线程(插入、更新...)。要考虑的另一件事是,频繁更新会非常非常快地耗尽手机电池。

【讨论】:

  • 我已更新问题并添加了赏金。谢谢:)
  • 谢谢老哥,很有用!
  • 此时我想知道我是否应该只发送我需要在 LocalBrodcastManager 通知中更新活动并刮擦数据库层的数据。我不需要这些数据是“抗崩溃的”,因为我的事实来源是 Outlook 后端。无论如何,我需要 WebClientService 作为后台服务,因为它还将当前用户数据发送到内部服务,以便与使用不同用户的其他设备共享它,如果用户正在使用,我不希望共享数据过时其他一些活动(例如设置等)。你认为这是一个好方法吗?助教
  • @GaSacchi 您可以使用LocalBroadCastReceiver 更新您的活动。但是,我认为每 30 秒调用一次restful 服务并不是一个好主意。它会很快耗尽你的电池,甚至比你想象的还要快。我认为,在多个设备之间同步数据的一种好方法是:选项 1. 使用会话(一次用于 1 个在线设备)选项 2. 使用谷歌云消息通知设备在需要时更新,而不是每 30 秒更新一次.
  • 那么设备将在墙上并插入电源,因此电池消耗不是问题..
【解决方案3】:

您可以将服务绑定到活动并更新您的 UI。 或者,您可以使用 Otto 或 EventBus 等库来创建发布者/订阅者依赖项,并在您的服务每次发布信息更新时通知您的活动。

【讨论】:

    【解决方案4】:

    使用事件总线进行此通信。 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);
            }
          });
        }
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多