서비스의 Android 업데이트 활동 UI


102

나는 항상 새로운 작업을 확인하는 서비스가 있습니다. 새 작업이있는 경우 해당 정보를 표시하도록 활동 UI를 새로 고치고 싶습니다. 이 예제 는 https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/를 찾았습니다 . 그게 좋은 접근입니까? 다른 예가 있습니까?

감사.


여기 내 대답을 참조하십시오. 리스너를 사용하여 클래스간에 통신 할 인터페이스를 쉽게 정의 할 수 있습니다. stackoverflow.com/questions/14660671/…
— Simon

답변:


228

내 원래 대답은 아래를 참조하십시오. 그 패턴은 잘 작동했지만 최근에는 서비스 / 활동 커뮤니케이션에 대해 다른 접근 방식을 사용하기 시작했습니다.

  • 활동이 서비스에 대한 직접 참조를 가져 오도록하여 인 텐트를 사용하는 대신 직접 호출을 허용 하는 바인딩 된 서비스 를 사용하십시오.
  • RxJava를 사용하여 비동기 작업을 실행합니다.

  • 서비스가 실행중인 활동이없는 경우에도 백그라운드 작업을 계속해야하는 경우 바인딩 해제시 중지되지 않도록 Application 클래스에서 서비스를 시작합니다.

startService () / LocalBroadcast 기술과 비교하여이 접근 방식에서 찾은 이점은 다음과 같습니다.

  • Parcelable을 구현하기 위해 데이터 개체가 필요하지 않습니다. 현재 Android와 iOS (RoboVM 사용)간에 코드를 공유하고 있으므로 이것은 특히 중요합니다.
  • RxJava는 미리 준비된 (및 크로스 플랫폼) 스케줄링과 순차 비동기 작업의 손쉬운 구성을 제공합니다.
  • RxJava를 사용하는 오버 헤드가 그보다 클 수 있지만 이것은 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();
}

4
@ user200658 예, onStart () 및 onStop ()은 활동 라이프 사이클의 일부입니다. 활동 라이프 사이클
— Clyde

8
작은 댓글-COPA_MESSAGE의 정의가 누락되었습니다.
— Lior

1
감사합니다 :) COPA_RESULT에 대해 문의하시는 분들은 사용자가 생성 한 정적 변수 일뿐입니다. "COPA"는 그의 서비스 이름이므로 완전히 대체 할 수 있습니다. 제 경우에는 정적 최종 public String MP_Result = "com.widefide.musicplayer.MusicService.REQUEST_PROCESSED";
— TheOnlyAnil 2015 년

1
사용자가 활동에서 벗어나 서비스가 완료된 후 다시 돌아 오면 UI가 업데이트되지 않습니다. 그것을 처리하는 가장 좋은 방법은 무엇입니까?
— SavageKing

1
@SavageKing 당신은 당신의 onStart()또는 onResume()방법에 적합한 서비스에 어떤 요청을 보내야합니다 . 일반적으로 활동이 서비스에 작업을 요청했지만 결과를 받기 전에 종료하는 경우 결과가 더 이상 필요하지 않다고 가정하는 것이 합리적입니다. 마찬가지로 활동을 시작할 때 서비스에서 처리중인 미해결 요청이 없다고 가정해야합니다.
— Clyde

32

나를 위해 가장 간단한 해결책은 방송을 보내는 것이 었습니다. 활동 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);

1
이것은 더 나은 솔루션이지만 더 효율적인 응용 프로그램 내에서 사용되는 경우 LocalBroadcastManager를 사용하는 것이 좋습니다.
— Psypher

1
this.sendBroadcast (local); 이후의 다음 단계 서비스에서?
— angel

의도의 엑스트라 그냥 UI를 원하는 업데이트 및 먹으 렴를 추가 내부 @angel에는 다음 단계는 그것이 없다
— 에 란 카 차브 대통령

12

바인딩 된 서비스 를 사용하여 이를 수행하고 내 활동에 리스너를 구현하여 통신합니다. 따라서 앱이 myServiceListener를 구현하는 경우 바인딩 한 후 서비스에서 리스너로 등록하고 바인딩 된 서비스에서 listener.onUpdateUI를 호출하고 UI를 업데이트 할 수 있습니다!


이것은 내가 찾고있는 것과 정확히 같습니다. 시도해 보겠습니다. 감사.
— user200658 2013-02-04

활동에 대한 참조를 유출하지 않도록주의하세요. 활동이 파괴되고 순환하면서 다시 생성 될 수 있기 때문입니다.
— 에릭

안녕하세요,이 링크를 확인하십시오. 이에 대한 샘플 코드를 공유했습니다. 누군가가 미래에 도움이 될 것이라고 가정하여 여기에 링크를 넣으십시오. myownandroid.blogspot.in/2012/08/…
— jrhamza

+1 이것은 실행 가능한 솔루션이지만 제 경우에는 모든 활동이 바인딩 해제 되어도 계속 실행되는 서비스가 필요합니다 (예 : 사용자가 응용 프로그램을 닫고, OS 조기 종료), 브로드 캐스트를 사용할 수밖에 없습니다. 수신기.
— Neon Warge

9

특히 Android에 맞춤화 된 EventBus 인 Otto를 확인하는 것이 좋습니다 . 활동 / UI는 서비스에서 버스에 게시 된 이벤트를 수신하고 백엔드에서 분리 할 수 ​​있습니다.


5

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

스레드 안전을 위해 비트를 생략했습니다. 서비스에서 조각 참조를 확인하고 사용하거나 변경할 때 잠금 또는 이와 유사한 것을 사용해야합니다.


8
LocalBroadcastManager 는 응용 프로그램 내에서 통신하도록 설계되었으므로 매우 효율적입니다. 바인딩 된 서비스 접근 방식은 서비스에 대해 제한된 수의 클라이언트가 있고 서비스를 독립적으로 실행할 필요가없는 경우 괜찮습니다. 로컬 브로드 캐스트 접근 방식을 사용하면 서비스를 클라이언트에서보다 효과적으로 분리 할 수 ​​있으며 스레드 안전성이 문제가되지 않습니다.
— Clyde

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);

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);
    }
}

2
서비스로도 어떤 활동도 정적 변수의 일을 절대로
— Murtaza Khursheed 후세인

@MurtazaKhursheedHussain-그것에 대해 더 자세히 설명해 주시겠습니까?
— SolidSnake

2
정적 멤버는 활동에서 메모리 누수의 원인이며 (많은 기사가 있음) 서비스 상태로 유지하면 상황이 악화됩니다. 방송 수신기는 OP의 상황에서 훨씬 더 적절하거나 영구 저장 장치에 보관하는 것도 해결책입니다.
— Murtaza Khursheed Hussain 2011

@MurtazaKhursheedHussain-매분마다 일부 데이터 (API에서 오는)로 다시 채워지는 정적 Hashmap이있는 클래스 (서비스 클래스가 아니라 데이터를 채우는 데 사용하는 일부 모델 클래스가 아님)가 있다고 가정 해 보겠습니다. 메모리 누수?
— SolidSnake

androidyes 의 컨텍스트 에서 목록이면 어댑터를 만들거나 sqllite / realm db를 사용하여 데이터를 유지합니다.
— Murtaza Khursheed Hussain
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.