나는 항상 새로운 작업을 확인하는 서비스가 있습니다. 새 작업이있는 경우 해당 정보를 표시하도록 활동 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>
바인딩 된 활동없이 백그라운드에서 서비스를 실행해야하는 경우 .NET Framework를 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();
}
onStart()또는 onResume()방법에 적합한 서비스에 어떤 요청을 보내야합니다 . 일반적으로 활동이 서비스에 작업을 요청했지만 결과를 받기 전에 종료하는 경우 결과가 더 이상 필요하지 않다고 가정하는 것이 합리적입니다. 마찬가지로 활동을 시작할 때 서비스에서 처리중인 미해결 요청이 없다고 가정해야합니다.
나를 위해 가장 간단한 해결책은 방송을 보내는 것이 었습니다. 활동 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는 서비스에서 버스에 게시 된 이벤트를 수신하고 백엔드에서 분리 할 수 있습니다.
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... }
}
스레드 안전을 위해 비트를 생략했습니다. 서비스에서 조각 참조를 확인하고 사용하거나 변경할 때 잠금 또는 이와 유사한 것을 사용해야합니다.
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.
당신은을 가지고하자 말 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);
}
}
androidyes 의 컨텍스트 에서 목록이면 어댑터를 만들거나 sqllite / realm db를 사용하여 데이터를 유지합니다.