Android 服务和活动之间的通信
Communication between Android Services and Activities
我想开发一个包含三个活动和两个服务的 Android 应用程序。
第一个服务名为 WebClientService,每 30 秒调用一次 REST API,使用处理程序,并且必须通知活动 Activity结果。
它还必须通知名为 DatabaseService 的第二个服务,以便更新本地数据库。
数据库服务将仅在 activity 的 onCreate 中调用一次(以防应用程序崩溃并重新启动),并且仅在 onRestart 中调用一次(通过这种方式,我们可以在出现连接问题时显示数据)。由于每 30 秒通知 "alive" activity 的 WebClientService,活动将自行更新。
问题是:
通知活动 activity 和后台 DatabaseService 更新的最佳方式是什么?
我的想法是在 WebClientService 中使用 sendBroadcast(),在每个 activity 和 DatabaseService 中使用 BroadcastReceiver,是这是正确的做法吗?
我应该使用相同的方法在 AllMeetingRoomActivity 和 DatabaseService 之间进行通信还是应该使用 Bound Service?
谢谢
更新:
DatabaseService 将不再是后台服务,而只是 WebClientService 和活动之间的数据库层的共享实例。
所以现在的问题是:将我的 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);
}
}
我认为您的方法适合 BroadCastReceiver
。但是,BroadCastReceiver
应该用于全局目的(比如在 2 个应用程序之间进行通信)。如果您打算只为您的应用使用 BroadCastReceiver
,我更喜欢使用 LocalBroadcastManager。如果只能由您的应用程序捕获,则使用 LocalBroadcastManager
更快、更安全。
还有另一种在 activity
和 service
之间进行通信的方法是使用 EventBus。这将比使用 BroadCastReceiver
容易得多(尤其是在它们之间传递数据时)。
更新:关于您的更新问题:
- 将我的 30 秒更新写入本地数据库并允许活动每隔几秒更新一次,只需从本地数据库读取,这是一个好方法吗? --> 当然没有。您应该让您的活动在需要时自行更新。当您更新本地数据库时,您应该知道是否有任何更改。如有变动,请使用
LocalBroadcastmanager
通知您的activity进行更新。
- 那会不会对性能影响太大? --> 是的,就是这样。数据库连接将需要时间来执行,并且在某些情况下它会阻止您的 UI。在这种情况下,您应该为每个
execute
(插入、更新...)使用带有 ExecutorService
的线程。需要考虑的另一件事是频繁更新会非常非常快地耗尽您的 phone 电池。
最佳选择:
使用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);
}
在服务中添加以下方法,每当您想将数据从服务更新到 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();
}
希望对您有所帮助。
您可以将服务绑定到活动并更新您的 UI。
或者,您可以使用像 Otto 或 EventBus 这样的库来创建 publisher/subscriber 依赖项,并在您的服务每次发布信息更新时通知您的活动。
使用事件总线进行此通信。 EventBus 允许组件之间进行发布-订阅式的通信,而不需要组件显式地相互注册(从而相互了解)。它专为使用显式注册取代传统的 Java 进程内事件分发而设计。
有很多:
https://github.com/greenrobot/EventBus
这是 Otto 用法的示例:
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.
从任何线程(主线程或后台线程)到 post,如果是服务并在主线程上接收事件:
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);
}
});
}
}
我想开发一个包含三个活动和两个服务的 Android 应用程序。
第一个服务名为 WebClientService,每 30 秒调用一次 REST API,使用处理程序,并且必须通知活动 Activity结果。 它还必须通知名为 DatabaseService 的第二个服务,以便更新本地数据库。
数据库服务将仅在 activity 的 onCreate 中调用一次(以防应用程序崩溃并重新启动),并且仅在 onRestart 中调用一次(通过这种方式,我们可以在出现连接问题时显示数据)。由于每 30 秒通知 "alive" activity 的 WebClientService,活动将自行更新。
问题是:
通知活动 activity 和后台 DatabaseService 更新的最佳方式是什么? 我的想法是在 WebClientService 中使用 sendBroadcast(),在每个 activity 和 DatabaseService 中使用 BroadcastReceiver,是这是正确的做法吗?
我应该使用相同的方法在 AllMeetingRoomActivity 和 DatabaseService 之间进行通信还是应该使用 Bound Service?
谢谢
更新: DatabaseService 将不再是后台服务,而只是 WebClientService 和活动之间的数据库层的共享实例。
所以现在的问题是:将我的 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);
}
}
我认为您的方法适合 BroadCastReceiver
。但是,BroadCastReceiver
应该用于全局目的(比如在 2 个应用程序之间进行通信)。如果您打算只为您的应用使用 BroadCastReceiver
,我更喜欢使用 LocalBroadcastManager。如果只能由您的应用程序捕获,则使用 LocalBroadcastManager
更快、更安全。
还有另一种在 activity
和 service
之间进行通信的方法是使用 EventBus。这将比使用 BroadCastReceiver
容易得多(尤其是在它们之间传递数据时)。
更新:关于您的更新问题:
- 将我的 30 秒更新写入本地数据库并允许活动每隔几秒更新一次,只需从本地数据库读取,这是一个好方法吗? --> 当然没有。您应该让您的活动在需要时自行更新。当您更新本地数据库时,您应该知道是否有任何更改。如有变动,请使用
LocalBroadcastmanager
通知您的activity进行更新。 - 那会不会对性能影响太大? --> 是的,就是这样。数据库连接将需要时间来执行,并且在某些情况下它会阻止您的 UI。在这种情况下,您应该为每个
execute
(插入、更新...)使用带有ExecutorService
的线程。需要考虑的另一件事是频繁更新会非常非常快地耗尽您的 phone 电池。
最佳选择:
使用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);
}
在服务中添加以下方法,每当您想将数据从服务更新到 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();
}
希望对您有所帮助。
您可以将服务绑定到活动并更新您的 UI。 或者,您可以使用像 Otto 或 EventBus 这样的库来创建 publisher/subscriber 依赖项,并在您的服务每次发布信息更新时通知您的活动。
使用事件总线进行此通信。 EventBus 允许组件之间进行发布-订阅式的通信,而不需要组件显式地相互注册(从而相互了解)。它专为使用显式注册取代传统的 Java 进程内事件分发而设计。
有很多:
https://github.com/greenrobot/EventBus
这是 Otto 用法的示例:
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.
从任何线程(主线程或后台线程)到 post,如果是服务并在主线程上接收事件:
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);
}
});
}
}