115

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

谢谢。

4

8 回答 8

239

请参阅下面的原始答案-该模式运行良好,但最近我开始使用不同的方法进行服务/活动通信:

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

  • 如果 Service 需要在没有 Activity 运行的情况下继续后台操作,也可以从 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);
    }
} 

还有一个绑定到服务并接收压力高度更新的 Activity:

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 的情况下在后台运行,它可以从 Application 类启动,也可以OnCreate()使用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);

不要忘记在销毁活动中取消注册恢复:

unregisterReceiver(updateUIReciver);
于 2014-08-15T13:23:12.123 回答
12

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

于 2013-02-04T21:05:19.120 回答
9

我建议查看Otto,这是一个专为 Android 量身定制的 EventBus。您的 Activity/UI 可以从 Service 监听 Bus 上发布的事件,并将自身与后端分离。

于 2013-02-04T21:03:02.570 回答
5

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

我假设您已经知道如何将服务与 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... }
}

为了线程安全,我省略了一些位,这是必不可少的。在检查和使用或更改服务上的片段引用时,请确保使用锁或类似的东西。

于 2013-11-06T23:37:33.680 回答
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 回答
1

我的解决方案可能不是最干净的,但它应该没有问题。逻辑只是创建一个静态变量来存储您的数据Service并在您的Activity.

假设您有一个StringService想要将其发送到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);
    }
}
于 2016-07-05T23:18:29.100 回答
0

你可以使用 android Jetpack 的LiveData

根据文档:

您可以LiveData使用单例模式扩展对象以包装系统服务,以便它们可以在您的应用程序中共享。LiveData 对象连接到系统服务一次,然后任何需要资源的观察者都可以观看 LiveData 对象。有关详细信息,请参阅扩展 LiveData

下面是我为 Service 和 Activity 以及 Service 和 Fragment 之间的通信所做的。

在这个例子中,我有:

  • 一个类SyncLogLiveData扩展LiveData,包含一个SpannableStringBuilder
  • 一项服务SyncService
  • 一个片段SyncFragment

Fragment “观察” LiveData(即 SyncLogLiveData)并在 LiveData 更改时执行操作。
LiveData 由服务更新。
我也可以以相同的方式从 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 回答