来自服务的Android更新活动UI
我有一个服务,一直在检查新的任务。 如果有新的任务,我想刷新活动用户界面来显示该信息。 我find了https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/这个例子。 这是一个很好的接近? 任何其他的例子?
谢谢。
请参阅下面的我的原始答案 – 该模式运行良好,但最近我开始使用不同的方法来服务/活动通信:
- 使用一个绑定的服务 ,使活动获得对服务的直接引用,从而允许直接调用它,而不是使用Intents。
-
使用RxJava执行asynchronous操作。
-
如果服务需要在没有活动运行的情况下继续后台操作,那么也从Application类启动服务,以便在未绑定时不会停止。
与startService()/ LocalBroadcast技术相比,我在这种方法中find的优点是
- 不需要数据对象来实现Parcelable – 这对我来说尤其重要,因为我现在在Android和iOS之间共享代码(使用RoboVM)
- RxJava提供了jar头(和跨平台)的调度,以及顺序asynchronous操作的组合。
- 这应该比使用LocalBroadcast更有效,尽pipe使用RxJava的开销可能超过了这个。
一些示例代码。 首先服务:
public class AndroidBmService extends Service implements BmService { private static final int PRESSURE_RATE = 500000; // microseconds between pressure updates private SensorManager sensorManager; private SensorEventListener pressureListener; private ObservableEmitter<Float> pressureObserver; private Observable<Float> pressureObservable; public class LocalBinder extends Binder { public AndroidBmService getService() { return AndroidBmService.this; } } private IBinder binder = new LocalBinder(); @Nullable @Override public IBinder onBind(Intent intent) { logMsg("Service bound"); return binder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } @Override public void onCreate() { super.onCreate(); sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE); if(pressureSensor != null) sensorManager.registerListener(pressureListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if(pressureObserver != null) { float lastPressure = event.values[0]; float lastPressureAltitude = (float)((1 - Math.pow(lastPressure / 1013.25, 0.190284)) * 145366.45); pressureObserver.onNext(lastPressureAltitude); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } }, pressureSensor, PRESSURE_RATE); } @Override public Observable<Float> observePressure() { if(pressureObservable == null) { pressureObservable = Observable.create(emitter -> pressureObserver = emitter); pressureObservable = pressureObservable.share(); } return pressureObservable; } @Override public void onDestroy() { if(pressureListener != null) sensorManager.unregisterListener(pressureListener); } }
以及与服务绑定并接收压力高度更新的活动:
public class TestActivity extends AppCompatActivity { private ContentTestBinding binding; private ServiceConnection serviceConnection; private AndroidBmService service; private Disposable disposable; @Override protected void onDestroy() { if(disposable != null) disposable.dispose(); unbindService(serviceConnection); super.onDestroy(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.content_test); serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { logMsg("BlueMAX service bound"); service = ((AndroidBmService.LocalBinder)iBinder).getService(); disposable = service.observePressure() .observeOn(AndroidSchedulers.mainThread()) .subscribe(altitude -> binding.altitude.setText( String.format(Locale.US, "Pressure Altitude %d feet", altitude.intValue()))); } @Override public void onServiceDisconnected(ComponentName componentName) { logMsg("Service disconnected"); } }; bindService(new Intent( this, AndroidBmService.class), serviceConnection, BIND_AUTO_CREATE); } }
此活动的布局是:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.controlj.mfgtest.TestActivity"> <TextView tools:text="Pressure" android:id="@+id/altitude" android:gravity="center_horizontal" android:layout_gravity="center_vertical" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> </layout>
如果服务需要在没有绑定Activity的情况下在后台运行,则可以使用Context#startService()
从Application类以及OnCreate()
Context#startService()
。
我的原始答案(从2013年起):
在您的服务:(在下面的例子中使用COPA作为服务)。
使用LocalBroadCastManager。 在您的服务onCreate,设置广播公司:
broadcaster = LocalBroadcastManager.getInstance(this);
当你想通知用户界面的东西:
static final public String COPA_RESULT = "com.controlj.copame.backend.COPAService.REQUEST_PROCESSED"; static final public String COPA_MESSAGE = "com.controlj.copame.backend.COPAService.COPA_MSG"; public void sendResult(String message) { Intent intent = new Intent(COPA_RESULT); if(message != null) intent.putExtra(COPA_MESSAGE, message); broadcaster.sendBroadcast(intent); }
在您的活动中:
在onCreate上创build一个监听器:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.copa); receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String s = intent.getStringExtra(COPAService.COPA_MESSAGE); // do something here. } }; }
并注册在onStart:
@Override protected void onStart() { super.onStart(); LocalBroadcastManager.getInstance(this).registerReceiver((receiver), new IntentFilter(COPAService.COPA_RESULT) ); } @Override protected void onStop() { LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver); super.onStop(); }
对我来说,最简单的解决scheme是发送一个广播,在我注册的活动oncreate和定义这样的广播(updateUIReciver被定义为一个类实例):
IntentFilter filter = new IntentFilter(); filter.addAction("com.hello.action"); updateUIReciver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //UI update here } }; registerReceiver(updateUIReciver,filter);
从服务你发送这样的意图:
Intent local = new Intent(); local.setAction("com.hello.action"); this.sendBroadcast(local);
不要忘记注销摧毁活动中的恢复:
unregisterReceiver(updateUIReciver);
我build议查看Otto ,一个专门为Android量身打造的EventBus。 您的活动/用户界面可以监听从服务发布到总线上的事件,并将其自身从后端解耦。
我会使用绑定的服务来实现这一点,并通过在我的活动中实现一个监听器来与之通信。 因此,如果您的应用程序实现了myServiceListener,那么可以在绑定它之后将其注册为服务器中的侦听器,然后从绑定的服务调用listener.onUpdateUI并更新您的UI!
克莱德的解决scheme工作,但它是一个广播,我敢肯定,会比直接调用方法效率低。 我可能会误解,但是我认为广播对于应用程序间通信来说意味着更多。
我假设你已经知道如何绑定一个活动的服务。 我做了一些类似于下面的代码来处理这种问题:
class MyService extends Service { MyFragment mMyFragment = null; MyFragment mMyOtherFragment = null; private void networkLoop() { ... //received new data for list. if(myFragment != null) myFragment.updateList(); } ... //received new data for textView if(myFragment !=null) myFragment.updateText(); ... //received new data for textView if(myOtherFragment !=null) myOtherFragment.updateSomething(); ... } } class MyFragment extends Fragment { public void onResume() { super.onResume() //Assuming your activity bound to your service getActivity().mMyService.mMyFragment=this; } public void onPause() { super.onPause() //Assuming your activity bound to your service getActivity().mMyService.mMyFragment=null; } public void updateList() { runOnUiThread(new Runnable() { public void run() { //Update the list. } }); } public void updateText() { //as above } } class MyOtherFragment extends Fragment { public void onResume() { super.onResume() //Assuming your activity bound to your service getActivity().mMyService.mMyOtherFragment=this; } public void onPause() { super.onPause() //Assuming your activity bound to your service getActivity().mMyService.mMyOtherFragment=null; } public void updateSomething() {//etc... } }
为了线程安全,我省略了一些,这是必不可less的。 确保在检查和使用或更改服务上的片段引用时使用锁或类似的东西。
Callback from service to activity to update UI. ResultReceiver receiver = new ResultReceiver(new Handler()) { protected void onReceiveResult(int resultCode, Bundle resultData) { //process results or update UI } } Intent instructionServiceIntent = new Intent(context, InstructionService.class); instructionServiceIntent.putExtra("receiver", receiver); context.startService(instructionServiceIntent);
我的解决scheme可能不是最干净的,但它应该没有问题。 逻辑就是创build一个静态variables来将数据存储在Service
上,并在Activity
上每秒更新你的视图。
假设您的Service
上有一个String
,您可以将其发送到您的Activity
上的TextView
。 它应该看起来像这样
您的服务:
public class TestService extends Service { public static String myString = ""; // Do some stuff with myString
你的行为:
public class TestActivity extends Activity { TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); tv = new TextView(this); setContentView(tv); update(); Thread t = new Thread() { @Override public void run() { try { while (!isInterrupted()) { Thread.sleep(1000); runOnUiThread(new Runnable() { @Override public void run() { update(); } }); } } catch (InterruptedException ignored) {} } }; t.start(); startService(new Intent(this, TestService.class)); } private void update() { // update your interface here tv.setText(TestService.myString); } }