115

常に新しいタスクをチェックしているサービスがあります。新しいタスクがある場合は、アクティビティUIを更新してその情報を表示したいと思います。https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/この例を見つけました。それは良いアプローチですか?他の例はありますか?

ありがとう。

4

8 に答える 8

239

私の元の答えについては以下を参照してください-そのパターンはうまくいきましたが、最近、サービス/アクティビティ通信に別のアプローチを使い始めました:

  • アクティビティがサービスへの直接参照を取得できるようにするバインドされたサービスを使用して、インテントを使用するのではなく、それを直接呼び出すことができるようにします。
  • RxJava を使用して非同期操作を実行します。

  • アクティビティが実行されていない場合でもサービスがバックグラウンド操作を続行する必要がある場合は、アンバインド時にサービスが停止しないように、Application クラスからもサービスを開始します。

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>

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();
}
于 2013-02-04T21:25:57.200 に答える
37

私にとって最も簡単な解決策は、ブロードキャストを送信することでした。アクティビティ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);
于 2014-08-15T13:23:12.123 に答える
12

バインドされたサービスを使用してそれを実行し、アクティビティにリスナーを実装することで通信します。したがって、アプリがmyServiceListenerを実装している場合は、バインド後にサービスのリスナーとして登録し、バインドされたサービスからlistener.onUpdateUIを呼び出して、そこでUIを更新できます。

于 2013-02-04T21:05:19.120 に答える
9

Android専用のEventBusであるOttoをチェックすることをお勧めします。アクティビティ/UIは、サービスからバスに投稿されたイベントをリッスンし、バックエンドから切り離すことができます。

于 2013-02-04T21:03:02.570 に答える
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);
于 2016-05-05T07:17:04.213 に答える
5

クライドのソリューションは機能しますが、これはブロードキャストであり、メソッドを直接呼び出すよりも効率が悪いと確信しています。私は間違っているかもしれませんが、ブロードキャストはアプリケーション間の通信を目的としていると思います。

サービスをアクティビティにバインドする方法はすでに知っていると思います。この種の問題を処理するために、以下のコードのようなことを行います。

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

不可欠なスレッドセーフのためのビットを省略しました。サービスでフラグメント参照を確認して使用または変更するときは、必ずロックなどを使用してください。

于 2013-11-06T23:37:33.680 に答える
0

Android JetpackのLiveDataを使用できます

ドキュメントに従って:

シングルトン パターンを使用してオブジェクトを拡張し、LiveDataシステム サービスをラップして、アプリで共有できるようにすることができます。LiveData オブジェクトがシステム サービスに一度接続すると、リソースを必要とするオブザーバーは LiveData オブジェクトを監視するだけで済みます。詳細については、LiveData の拡張を参照してください。

以下は、サービスとアクティビティ、およびサービスとフラグメントの間の通信のために私が行ったことです。

この例では、次のものがあります。

  • を含むSyncLogLiveData拡張クラスLiveDataSpannableStringBuilder
  • サービス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 について言及されていません。

于 2021-08-20T19:31:28.973 に答える