常に新しいタスクをチェックしているサービスがあります。新しいタスクがある場合は、アクティビティUIを更新してその情報を表示したいと思います。https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/この例を見つけました。それは良いアプローチですか?他の例はありますか?
ありがとう。
常に新しいタスクをチェックしているサービスがあります。新しいタスクがある場合は、アクティビティUIを更新してその情報を表示したいと思います。https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/この例を見つけました。それは良いアプローチですか?他の例はありますか?
ありがとう。
私の元の答えについては以下を参照してください-そのパターンはうまくいきましたが、最近、サービス/アクティビティ通信に別のアプローチを使い始めました:
RxJava を使用して非同期操作を実行します。
アクティビティが実行されていない場合でもサービスがバックグラウンド操作を続行する必要がある場合は、アンバインド時にサービスが停止しないように、Application クラスからもサービスを開始します。
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>
OnCreate()
バインドされたアクティビティなしでバックグラウンドでサービスを実行する必要がある場合は、を使用して Application クラスから開始することもできますContext#startService()
。
私の元の回答(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();
}
私にとって最も簡単な解決策は、ブロードキャストを送信することでした。アクティビティ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);
バインドされたサービスを使用してそれを実行し、アクティビティにリスナーを実装することで通信します。したがって、アプリがmyServiceListenerを実装している場合は、バインド後にサービスのリスナーとして登録し、バインドされたサービスからlistener.onUpdateUIを呼び出して、そこでUIを更新できます。
Android専用のEventBusであるOttoをチェックすることをお勧めします。アクティビティ/UIは、サービスからバスに投稿されたイベントをリッスンし、バックエンドから切り離すことができます。
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);
クライドのソリューションは機能しますが、これはブロードキャストであり、メソッドを直接呼び出すよりも効率が悪いと確信しています。私は間違っているかもしれませんが、ブロードキャストはアプリケーション間の通信を目的としていると思います。
サービスをアクティビティにバインドする方法はすでに知っていると思います。この種の問題を処理するために、以下のコードのようなことを行います。
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... }
}
不可欠なスレッドセーフのためのビットを省略しました。サービスでフラグメント参照を確認して使用または変更するときは、必ずロックなどを使用してください。
Android JetpackのLiveDataを使用できます
ドキュメントに従って:
シングルトン パターンを使用してオブジェクトを拡張し、
LiveData
システム サービスをラップして、アプリで共有できるようにすることができます。LiveData オブジェクトがシステム サービスに一度接続すると、リソースを必要とするオブザーバーは LiveData オブジェクトを監視するだけで済みます。詳細については、LiveData の拡張を参照してください。
以下は、サービスとアクティビティ、およびサービスとフラグメントの間の通信のために私が行ったことです。
この例では、次のものがあります。
SyncLogLiveData
拡張クラスLiveData
SpannableStringBuilder
SyncService
SyncFragment
Fragment は LiveData (つまり、SyncLogLiveData) を「監視」し、LiveData が変更されたときにアクションを実行します。
LiveData は Service によって更新されます。
同じ方法で Fragment から 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);
setValue(...)
またはpostValue(...)
メソッドを直接使用することもできますLiveData
SyncLogLiveData.get().setValue(message);
クラス SyncFragment
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);
コード内でいつでも、この方法で LiveData の現在の値を取得することもできます。
SyncLogLiveData.get().getValue();
うまくいけば、これは誰かを助けるでしょう。この回答では、まだ LiveData について言及されていません。