我有一项服务,一直在检查新任务。如果有新任务,我想刷新活动UI以显示该信息。 我确实找到了https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/这个例子。这是一个很好的approch?还有其他任何例子吗?
感谢。
答案 0 :(得分:216)
请参阅下面的原始答案 - 该模式运作良好,但最近我开始使用不同的服务/活动沟通方法:
使用RxJava执行异步操作。
如果服务需要继续后台操作,即使没有 Activity正在运行,也从Application启动服务 class,以便在解除绑定时不会停止。
与startService()/ LocalBroadcast技术相比,我在此方法中发现的优势是
一些示例代码。首先是服务:
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()
在OnCreate()
中从Application类启动它。
我的原始答案(自2013年起):
在您的服务中(在下面的示例中使用COPA作为服务)。
使用LocalBroadCastManager。在您的服务onCreate中,设置广播公司:
broadcaster = LocalBroadcastManager.getInstance(this);
当您想要通知UI时:
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();
}
答案 1 :(得分:29)
对我来说最简单的解决方案就是发送一个广播,在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);
不要忘记在destroy上取消注册活动中的恢复:
unregisterReceiver(updateUIReciver);
答案 2 :(得分:12)
我会使用bound service来做到这一点,并通过在我的活动中实现一个监听器来与之通信。因此,如果您的应用程序实现myServiceListener,您可以在绑定它之后将其注册为服务中的侦听器,从绑定服务调用listener.onUpdateUI并在那里更新您的UI!
答案 3 :(得分:9)
我建议您查看专为Android量身定制的EventBus Otto。您的活动/ UI可以从服务中侦听在总线上发布的事件,并将其自身与后端分离。
答案 4 :(得分:5)
克莱德的解决方案有效,但它是一个广播,我相信它不会比直接调用方法效率低。我可能会弄错,但我认为广播对于应用程序间的通信更有意义。
我假设您已经知道如何使用Activity绑定服务。 我做了类似下面的代码来处理这类问题:
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... }
}
我省略了用于线程安全的位,这是必不可少的。在检查和使用或更改服务上的片段引用时,请确保使用锁或类似的东西。
答案 5 :(得分:5)
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);
答案 6 :(得分:1)
我的解决方案可能不是最干净的,但它应该没有任何问题。
逻辑只是创建一个静态变量来将您的数据存储在Service
上,并在Activity
上每秒更新您的视图。
假设您String
上有一个Service
,要将TextView
发送给Activity
上的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);
}
}
你的活动:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test Site</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="stackdemo.css">
<link href='https://fonts.googleapis.com/css?family=Lato:400,400italic,700' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Roboto:400,700,400italic' rel='stylesheet' type='text/css'>
</head>
<body>
<div class="main-nav">
<ul class="nav">
<li class="name">Test Site</li>
<li class="subname">
sub-heading should be right next to 'Test Site' whenever page is resized
</li>
<li><a href="#">Sign up</a></li>
<li><a href="#">Log in</a></li>
</ul>
</div>
<header>
<img src="https://placeimg.com/400/400/people" alt="picture" class="profile-image">
<ul class="direct">
<li><a href="#">Try</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</header>
</body>
</html>