सेवा से Android अद्यतन गतिविधि UI


102

मेरे पास एक सेवा है जो हर समय नए कार्य के लिए जाँच कर रही है। यदि कोई नया कार्य है, तो मैं उस जानकारी को दिखाने के लिए गतिविधि UI को ताज़ा करना चाहता हूं। मुझे इस उदाहरण में https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/ मिला । क्या यह एक अच्छा दृष्टिकोण है? कोई अन्य उदाहरण?

धन्यवाद।


मेरा जवाब यहां देखें। श्रोताओं का उपयोग करके कक्षाओं के बीच संवाद करने के लिए एक इंटरफ़ेस को परिभाषित करना आसान है। stackoverflow.com/questions/14660671/…
साइमन

जवाबों:


228

मेरे मूल उत्तर के लिए नीचे देखें - उस पैटर्न ने अच्छी तरह से काम किया है, लेकिन हाल ही में मैंने सेवा / गतिविधि संचार के लिए एक अलग दृष्टिकोण का उपयोग करना शुरू कर दिया है:

  • एक बाउंड सेवा का उपयोग करें जो गतिविधि को सेवा का सीधा संदर्भ प्राप्त करने में सक्षम बनाती है, इस प्रकार इंटेंट्स का उपयोग करने के बजाय इस पर सीधे कॉल की अनुमति देती है।
  • अतुल्यकालिक कार्यों को निष्पादित करने के लिए RxJava का उपयोग करें।

  • यदि सेवा में कोई गतिविधि नहीं चल रही है, तब भी पृष्ठभूमि के संचालन को जारी रखने की आवश्यकता है, तो अनुप्रयोग वर्ग से भी सेवा शुरू करें, ताकि जब वह आगे न बढ़े।

स्टार्टस् सेवा () / लोकलब्रॉडकास्ट तकनीक की तुलना में मुझे इस दृष्टिकोण में जो फायदे मिले हैं

  • पार्सलबल को लागू करने के लिए डेटा ऑब्जेक्ट की आवश्यकता नहीं है - यह मेरे लिए विशेष रूप से महत्वपूर्ण है क्योंकि मैं अब एंड्रॉइड और आईओएस के बीच कोड साझा कर रहा हूं (रोबो वीवीएम का उपयोग करके)
  • 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()उपयोग में शुरू किया जा सकता है Context#startService()


मेरा मूल उत्तर (2013 से):

आपकी सेवा में: (नीचे उदाहरण में COPA सेवा के रूप में)।

एक LocalBroadCastManager का उपयोग करें। आपकी सेवा के ऑनक्रैट में, ब्रॉडकास्टर सेट करें:

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 के बारे में पूछ रहे हैं, यह कुछ भी नहीं है, लेकिन एक उपयोगकर्ता स्थिर चर उत्पन्न करता है। "कोपा" उसका सेवा नाम है, ताकि आप उसे पूरी तरह से बदल सकें। मेरे मामले में, यह स्थिर अंतिम सार्वजनिक स्ट्रिंग है MP_Result = "com.widefide.musicplayer.MusicService.REQUEST_PROCESSED";
TheOnlyAnil

1
यूआई अपडेट नहीं करेगा यदि उपयोगकर्ता गतिविधि से दूर हो जाता है और सेवा समाप्त होने के बाद फिर से वापस आ जाता है। इसे संभालने का सबसे अच्छा तरीका क्या है?
SavageKing

1
@SavageKing आपको अपने onStart()या onResume()विधि में सेवा के लिए जो भी अनुरोध उचित है, भेजने की आवश्यकता है । सामान्य तौर पर यदि गतिविधि सेवा को कुछ करने के लिए कहती है, लेकिन परिणाम प्राप्त करने से पहले छोड़ देती है, तो यह मान लेना उचित है कि परिणाम की अब आवश्यकता नहीं है। इसी तरह एक गतिविधि शुरू करने पर यह मान लेना चाहिए कि सेवा द्वारा संसाधित किए जा रहे कोई बकाया अनुरोध नहीं हैं।
क्लाईड

32

मेरे लिए सबसे सरल उपाय एक प्रसारण भेजना था, जिस गतिविधि में मैंने पंजीकरण किया था और इस तरह प्रसारण को परिभाषित किया (अपडेट यूरीसेवर को कक्षा उदाहरण के रूप में परिभाषित किया गया है):

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

1
यह बेहतर समाधान है, लेकिन अगर यह अनुप्रयोग के भीतर उपयोग किया जा रहा है जो अधिक कुशल होगा, तो LocalBroadcastManager का उपयोग करना बेहतर होगा।
Psypher

1
इसके बाद अगले चरण। SendBroadcast (स्थानीय); सेवा में?
दूत

@ एंजेल कोई अगला कदम नहीं है, इरादे के एक्स्ट्रा के अंदर सिर्फ आप चाहते हैं कि यूआई अपडेट जोड़ें और इसे
एरन कट्सव

12

मैं अपनी गतिविधि में एक श्रोता को लागू करके ऐसा करने के लिए एक बाध्य सेवा का उपयोग करूंगा और इसके साथ संवाद करूंगा। इसलिए यदि आपका ऐप myServiceListener को लागू करता है, तो आप इसे अपनी बाध्यता के बाद अपनी सेवा में एक श्रोता के रूप में पंजीकृत कर सकते हैं, अपनी बाध्य सेवा से listener.onUpdateUI को कॉल कर सकते हैं और वहां अपना UI अपडेट कर सकते हैं!


यह वैसा ही दिखता है जैसा मैं देख रहा हूं। मुझे इसकी कोशिश करने दीजिए। धन्यवाद।
user200658

अपनी गतिविधि के संदर्भ को लीक न करने के लिए सावधान रहें। क्योंकि आपकी गतिविधि नष्ट हो सकती है और रोटेशन पर फिर से बनाई जा सकती है।
एरिक

नमस्ते, कृपया इस लिंक को देखें। मैंने इसके लिए एक नमूना कोड साझा किया था। लिंक को यहां पर यह मानते हुए कि भविष्य में कोई मददगार लग सकता है। myownandroid.blogspot.in/2012/08/…
jrhamza

+1 यह एक व्यवहार्य समाधान है, लेकिन मेरे मामले में, मुझे वास्तव में इस सेवा को चलाने की आवश्यकता है, भले ही यह सभी गतिविधि के लिए unbinds है (जैसे उपयोगकर्ता एप्लिकेशन को बंद कर देता है, शीघ्र ओएस समाप्ति), मेरे पास प्रसारण का उपयोग करने के अलावा कोई विकल्प नहीं है। रिसीवर।
नियॉन वारगे

9

मैं ओटो की जाँच करने की सलाह दूंगा, विशेष रूप से एंड्रॉइड के अनुरूप एक ईवेंटबस। आपकी गतिविधि / UI सेवा से बस में पोस्ट की गई घटनाओं को सुन सकती है, और बैकएंड से खुद को हटा सकती है।


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

मैंने थ्रेड सुरक्षा के लिए बिट्स को छोड़ दिया, जो आवश्यक है। ताले या कुछ ऐसा उपयोग करना सुनिश्चित करें जब सेवा पर टुकड़े के संदर्भों की जांच और उपयोग करना या बदलना।


8
LocalBroadcastManager को किसी एप्लिकेशन के भीतर संचार के लिए डिज़ाइन किया गया है, इसलिए यह काफी कुशल है। जब आप सेवा के लिए सीमित संख्या में ग्राहक होते हैं और सेवा को स्वतंत्र रूप से चलाने की आवश्यकता नहीं होती है तो बाध्य सेवा दृष्टिकोण ठीक है। स्थानीय प्रसारण दृष्टिकोण सेवा को अपने ग्राहकों से अधिक प्रभावी ढंग से डिकोड करने की अनुमति देता है, और थ्रेड-सुरक्षा को गैर-मुद्दा बनाता है।
क्लाइड

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

Let मान लीजिए कि आप एक है कि 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
स्थैतिक परिवर्तनशील चीज को न तो कभी सेवा में रखें और न ही किसी गतिविधि में
मुर्तजा खुर्शीद हुसैन

@ मुर्तजा शहीद हुसैन - क्या आप उस पर अधिक विस्तार कर सकते हैं?
सॉलिडस्नेक

2
स्टेटिक सदस्य गतिविधियों में मेमोरी लीक का स्रोत हैं (चारों ओर बहुत सारे लेख हैं) और साथ ही उन्हें सेवा में रखने से यह और खराब हो जाता है। ओपी की स्थिति में एक प्रसारण रिसीवर अधिक उपयुक्त है या लगातार भंडारण में रखना भी एक समाधान है।
मुर्तजा खुर्शीद हुसैन

@ मुर्तज़ाखुरशीद हुसैन - तो मान लीजिए कि अगर मेरे पास एक क्लास है (सर्विस क्लास नहीं है तो बस कुछ मॉडल क्लास मैं डेटा को पॉप्युलेट करने के लिए इस्तेमाल करता हूँ) एक स्टैटिक हैशमैप के साथ जो कुछ डेटा के साथ फिर से पॉपुलेट हो जाता है (एक एपीआई से आने वाला हर मिनट एक माना जाता है) स्मृति रिसाव?
सॉलिडस्नेक

androidहाँ, यदि इसकी सूची के संदर्भ में , एक एडेप्टर बनाने की कोशिश करें या साइक्लाइट / दायरे डीबी का उपयोग करके डेटा को बनाए रखें।
मुर्तजा खुर्शीद हुसैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.