Android 更新活动 UI 来自服务

我有一个服务,是检查新的任务所有的时间。如果有新任务,我想刷新活动 UI 以显示该信息。 我确实找到了 https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/这个例子。这是一个好的方法吗? 还有其他的例子吗?

谢谢。

127514 次浏览

我建议您查看一下 奥托,这是一款专门为 Android 定制的 EventBus。您的 Activity/UI 可以从 Service 侦听发布在总线上的事件,并从后端解耦自身。

我将使用一个 绑定服务来实现这一点,并通过在我的活动中实现一个侦听器来与它进行通信。因此,如果您的应用程序实现了 myServiceListener,那么您可以在绑定后将其注册为服务中的侦听器,从绑定的服务中调用 listener.onUpdateUI 并在其中更新您的 UI!

下面是我最初的回答——这种模式很有效,但是最近我开始使用一种不同的方法来进行服务/活动交流:

  • 使用 绑定服务使活动能够获得直接的 引用服务,从而允许对其直接调用,而不是 而不是使用意图。
  • 使用 RxJava 执行异步操作。

  • 如果服务需要继续后台操作,即使 Activity is running, also start the service from the Application class so that it does not get stopped when unbound.

与 startService ()/LocalBroadcast 技术相比,我在这种方法中发现的优势是

  • 不需要数据对象来实现 Parcelable ——这对我来说特别重要,因为我现在正在 Android 和 iOS 之间共享代码(使用 RoboVM)
  • RxJava 提供罐装(和跨平台)调度,以及顺序异步操作的简单组合。
  • 这应该比使用 LocalBroadcast 更有效率,尽管使用 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()中启动该服务。


我的原始答案(自2013年起) :

In your service: (using COPA as service in example below).

使用 LocalBroadCastManager 在你的服务的 onCreate 中设置播放器:

broadcaster = LocalBroadcastManager.getInstance(this);

When you want to notify the UI of something:

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 上创建一个监听器:

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

Clyde 的解决方案是有效的,但是它是一个广播,我很确定这比直接调用一个方法效率要低。我可能搞错了,但我认为广播更多的是为了应用程序之间的通信。

我假设您已经知道如何将服务与活动绑定。 我做了一些类似于下面的代码来处理这类问题:

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... }
}

为了线程安全,我省略了一些必要的部分。确保在检查和使用或更改服务上的片段引用时使用锁或类似的东西。

对我来说,最简单的解决方案是发送一个广播,在 oncreate i 注册的活动中定义广播,如下所示(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);
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);

我的解决方案可能不是最干净的,但应该没有问题。 这个逻辑只是创建一个静态变量,将数据存储在 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);
}
}

你可以使用机器人喷气背包的 LiveData

As per the documentation:

你可以用这个单例模式来扩展一个 LiveData对象 系统服务,以便它们可以在您的应用程序中共享 对象连接到系统服务一次,然后连接到系统服务的任何观察器 needs the resource can just watch the LiveData object. For more 信息,请参见 < a href = “ https://developer.android.com/subject/library/Architecture/livedata? hl = en # tended _ livedata”rel = “ nofollow noReferrer”> Extended LiveData.

下面是我在服务和活动之间以及服务和片段之间所做的通信。

在这个例子中,我有:

  • 扩展包含 SpannableStringBuilderLiveData的类 SyncLogLiveData
  • 服务 SyncService
  • a fragment SyncFragment

片段“观察”LiveData (即 SyncLogLiveData) ,并在 LiveData 更改时执行一个操作。
LiveData 由服务更新。
我也可以用同样的方式更新碎片中的 LiveData,但是不在这里显示。

类别 SyncLogLiveData

public class SyncLogLiveData extends LiveData<SpannableStringBuilder> {
private static SyncLogLiveData sInstance;
private final static SpannableStringBuilder log = new SpannableStringBuilder("");


@MainThread
public static SyncLogLiveData get() {
if (sInstance == null) {
sInstance = new SyncLogLiveData();
}
return sInstance;
}


private SyncLogLiveData() {
}


public void appendLog(String text) {
log.append(text);
postValue(log);
}


public void appendLog(Spanned text) {
log.append(text);
postValue(log);
}
}

SyncService

这行代码将更新 LiveData 的内容

SyncLogLiveData.get().appendLog(message);

您也可以直接使用 LiveDatasetValue(...)postValue(...)方法

SyncLogLiveData.get().setValue(message);

类同步片段

public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {


//...


// Create the observer which updates the UI.
final Observer<SpannableStringBuilder> ETAObserver = new Observer<SpannableStringBuilder>() {
@Override
public void onChanged(@Nullable final SpannableStringBuilder spannableLog) {
// Update the UI, in this case, a TextView.
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
textViewLog.setText(spannableLog);
}
});
}
};
// Observe the LiveData, passing in this activity/fragment as the LifecycleOwner and the observer.
SyncLogLiveData.get().observe(getViewLifecycleOwner(), ETAObserver);


//...
}

在活动内部,它的工作方式是相同的,但是对于 .observe(...),您可以使用这种方式

SyncLogLiveData.get().observe(this, ETAObserver);

You could also fetch the current value of the LiveData this way at anytime in your code.

SyncLogLiveData.get().getValue();

Hopefully this will help someone. There wasn't any mention of LiveData in this answer yet.