Hizmetten Android güncelleme etkinliği kullanıcı arayüzü


102

Her zaman yeni görevleri kontrol eden bir servisim var. Yeni görev varsa, bu bilgiyi göstermek için etkinlik arayüzünü yenilemek istiyorum. Bu örneği https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/ buldum . Bu iyi bir yaklaşım mı? Başka örnek var mı?

Teşekkürler.


Cevabımı burada görün. Dinleyicileri kullanarak sınıflar arasında iletişim kurmak için bir Arayüz tanımlamak kolaydır. stackoverflow.com/questions/14660671/…
Simon

Yanıtlar:


228

Orijinal cevabım için aşağıya bakın - bu model iyi çalıştı, ancak son zamanlarda Hizmet / Faaliyet iletişimine farklı bir yaklaşım kullanmaya başladım:

  • Etkinliğin Hizmete doğrudan bir referans almasını sağlayan, böylece Amaçları kullanmak yerine doğrudan aramalara izin veren bağlı bir hizmet kullanın .
  • Eşzamansız işlemleri yürütmek için RxJava kullanın.

  • Hizmetin, hiçbir Etkinlik çalışmadığında bile arka planda işlemlere devam etmesi gerekiyorsa, hizmeti Uygulama sınıfından da başlatın, böylece bağlantı kesildiğinde durdurulmaz.

StartService () / LocalBroadcast tekniğine kıyasla bu yaklaşımda bulduğum avantajlar şunlardır:

  • Parcelable'ı uygulamak için veri nesnelerine gerek yok - şu anda Android ve iOS arasında (RoboVM kullanarak) kod paylaştığım için bu benim için özellikle önemli.
  • RxJava, hazır (ve çapraz platform) zamanlama ve sıralı eşzamansız işlemlerin kolay bileşimini sağlar.
  • Bu, LocalBroadcast kullanmaktan daha verimli olmalı, ancak RxJava kullanmanın ek yükü bundan daha ağır basabilir.

Bazı örnek kod. Önce hizmet:

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

Ve hizmete bağlanan ve basınç irtifa güncellemeleri alan bir Etkinlik:

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

Bu Faaliyetin düzeni:

<?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>

Hizmet ihtiyaçları ciltli Aktivite olmadan arka planda çalışmasına Eğer hem uygulama sınıfından başlatılabilir OnCreate()kullanarak Context#startService().


Orijinal Cevabım (2013'ten itibaren):

Hizmetinizde: (aşağıdaki örnekte hizmet olarak COPA kullanarak).

LocalBroadCastManager kullanın. Hizmetinizin onCreate bölümünde yayıncıyı kurun:

broadcaster = LocalBroadcastManager.getInstance(this);

Kullanıcı arayüzüne bir şey bildirmek istediğinizde:

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

Etkinliğinizde:

OnCreate'te bir dinleyici oluşturun:

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

ve onStart'a kaydedin:

@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 Evet, onStart () ve onStop (), Activity yaşam döngüsünün bir parçasıdır - bkz. Activity Lifecycle
Clyde

8
Küçük yorum - COPA_MESSAGE tanımını kaçırıyorsunuz.
Lior

1
Teşekkürler :) COPA_RESULT hakkında soru soranlar, kullanıcı tarafından oluşturulan statik bir değişkenden başka bir şey değil. "COPA" onun hizmet adıdır, böylece onu tamamen kendinizinkiyle değiştirebilirsiniz. Benim durumumda, statik nihai public String MP_Result = "com.widefide.musicplayer.MusicService.REQUEST_PROCESSED";
TheOnlyAnil

1
Kullanıcı Etkinlikten ayrılırsa ve hizmet bittikten sonra ona geri dönerse kullanıcı arayüzü güncellenmez. Bunu halletmenin en iyi yolu nedir?
SavageKing

1
@SavageKing Sizin hizmetinize onStart()veya onResume()yönteminize uygun olan her türlü talebi göndermeniz gerekir . Genel olarak, Etkinlik Hizmetten bir şey yapmasını isterse ancak sonucu almadan çıkarsa, sonucun artık gerekli olmadığını varsaymak mantıklıdır. Benzer şekilde, bir Aktivite başlatılırken, Hizmet tarafından işlenmekte olan bekleyen taleplerin olmadığını varsaymalıdır.
Clyde

33

benim için en basit çözüm, oncreate i etkinliğinde bir yayın göndermekti ve yayını şu şekilde tanımladı (updateUIReciver, bir sınıf örneği olarak tanımlanır):

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

Ve hizmetten şu şekilde niyet gönderiyorsunuz:

Intent local = new Intent();

local.setAction("com.hello.action");

this.sendBroadcast(local);

yok etme etkinliğindeki kurtarmanın kaydını silmeyi unutmayın:

unregisterReceiver(updateUIReciver);

1
Bu daha iyi bir çözümdür, ancak uygulama içinde kullanılıyorsa LocalBroadcastManager'ı kullanmak daha verimli olacaktır.
Psypher

1
bundan sonraki adımlar.sendBroadcast (yerel); serviste?
melek

@angel Bir sonraki adım yok, niyetin ekstralarının içine sadece istediğiniz kullanıcı arabirimi güncellemelerini ekleyin ve işte bu
Eran Katsav

12

Bunu yapmak ve etkinliğime bir dinleyici uygulayarak onunla iletişim kurmak için bağlı bir hizmet kullanırdım. Dolayısıyla, uygulamanız myServiceListener'ı uygularsa, onu bağladıktan sonra hizmetinize bir dinleyici olarak kaydedebilir, bağlı hizmetinizden listener.onUpdateUI'yi çağırabilir ve oradaki kullanıcı arayüzünüzü güncelleyebilirsiniz!


Bu tam olarak aradığıma benziyor. Bir deneyeyim. Teşekkürler.
user200658

Faaliyetinize bir referans sızdırmamaya dikkat edin. Çünkü faaliyetiniz yok edilebilir ve dönüşümlü olarak yeniden yaratılabilir.
Eric

Merhaba, lütfen bu bağlantıya göz atın. Bunun için bir örnek kod paylaşmıştım. Bağlantıyı buraya koymak, gelecekte birisinin kendini yararlı hissedeceğini varsayarak. myownandroid.blogspot.in/2012/08/…
jrhamza

+1 Bu uygulanabilir bir çözüm, ancak benim durumumda, hizmetin tüm faaliyetin bağlantısını kesmesine rağmen çalışmaya devam etmesine gerçekten ihtiyacım var (örn. Kullanıcı uygulamayı kapat, Erken İşletim Sistemini sonlandırma), yayını kullanmaktan başka seçeneğim yok alıcılar.
Neon Warge

9

Android için özel olarak tasarlanmış bir EventBus olan Otto'ya göz atmanızı tavsiye ederim . Aktiviteniz / Kullanıcı Arayüzünüz, Otobüste yayınlanan olayları Hizmetten dinleyebilir ve kendisini arka uçtan ayırabilir.


5

Clyde'ın çözümü işe yarıyor, ancak bu bir yayın, ki bunun bir yöntemi doğrudan çağırmaktan daha az verimli olacağından oldukça eminim. Yanılıyor olabilirim, ancak yayınların daha çok uygulamalar arası iletişim anlamına geldiğini düşünüyorum.

Bir hizmeti bir Aktivite ile nasıl bağlayacağınızı zaten bildiğinizi varsayıyorum. Bu tür bir sorunu çözmek için aşağıdaki kod gibi bir şey yapıyorum:

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

İş parçacığı güvenliği için gerekli olan bitleri dışarıda bıraktım. Hizmetteki parça referanslarını kontrol ederken ve kullanırken veya değiştirirken kilitler veya buna benzer bir şey kullandığınızdan emin olun.


8
LocalBroadcastManager bir uygulama içinde iletişim için tasarlanmıştır, bu nedenle oldukça verimlidir. Hizmet için sınırlı sayıda müşteriniz olduğunda ve hizmetin bağımsız olarak çalışması gerekmediğinde, bağlı hizmet yaklaşımı uygundur. Yerel yayın yaklaşımı, hizmetin müşterilerinden daha etkili bir şekilde ayrıştırılmasına olanak tanır ve iş parçacığı güvenliğini sorun dışı hale getirir.
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

Benim çözümüm en temiz olmayabilir ama sorunsuz çalışmalı. Mantık, basitçe, verilerinizi üzerinde depolamak için statik bir değişken oluşturmak Serviceve Activity.

Diyelim ki, bir String' Servicee göndermek istediğiniz bir TextViewüzerinde var Activity. Böyle görünmeli

Servisiniz:

public class TestService extends Service {
    public static String myString = "";
    // Do some stuff with myString

Aktiviteniz:

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
Ne Hizmette ne de herhangi bir aktivitede statik değişken şeyi asla yapmayın
Murtaza Khursheed Hussain

@MurtazaKhursheedHussain - bu konuda daha fazla detay verebilir misin?
SolidSnake

2
Statik üyeler, etkinliklerdeki bellek sızıntılarının kaynağıdır (etrafta çok sayıda makale vardır) ve bunları hizmette tutmak durumu daha da kötüleştirir. Bir yayın alıcısı, OP'nin durumunda çok daha uygundur veya kalıcı depolamada tutmak da bir çözümdür.
Murtaza Khursheed Hussain

@MurtazaKhursheedHussain - diyelim ki, her dakika bazı verilerle (bir API'den gelen) yeniden doldurulan statik bir Hashmap'e sahip bir sınıfım (bir hizmet sınıfı değil, sadece verileri doldurmak için kullandığım bir model sınıfı) varsa, bellek sızıntısı?
SolidSnake

androidYes bağlamında , eğer bir liste ise, sqllite / realm db kullanarak bir bağdaştırıcı veya kalıcı veri oluşturmaya çalışın.
Murtaza Khursheed Hussain
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.