Користувацький інтерфейс користувача для оновлення Android від служби


102

У мене є служба, яка постійно перевіряє нове завдання. Якщо є нове завдання, я хочу оновити інтерфейс активності, щоб показати цю інформацію. Я знайшов https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/ цей приклад. Це хороший підхід? Будь-які інші приклади?

Дякую.


Дивіться мою відповідь тут. Інтерфейс легко визначати для спілкування між класами за допомогою слухачів. stackoverflow.com/questions/14660671/…
Саймон

Відповіді:


228

Нижче дивіться мою оригінальну відповідь - ця схема спрацювала добре, але останнім часом я почав використовувати інший підхід до комунікації служби / діяльності:

  • Використовуйте прив'язану послугу, яка дозволяє Діяльності отримати пряме посилання на Службу, таким чином дозволяючи прямі дзвінки на неї, а не використовувати Інтенти.
  • Використовуйте RxJava для виконання асинхронних операцій.

  • Якщо Сервісу потрібно продовжувати фонові операції, навіть коли жодна активність не запущена, також запустіть службу з класу 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);
    }
} 

І діяльність, яка прив'язується до служби та отримує оновлення висоти тиску:

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>

Якщо потреби служби , щоб працювати у фоновому режимі без пов'язаної активності він може бути запущений з класу Application , а також в OnCreate()використанні Context#startService().


Моя оригінальна відповідь (з 2013 року):

У вашій службі: (використовуючи COPA в якості сервісу в прикладі нижче).

Використовуйте LocalBroadCastManager. У програмі onCreate вашої служби налаштуйте мовник:

broadcaster = LocalBroadcastManager.getInstance(this);

Коли ви хочете сповістити інтерфейс користувача про щось:

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.
Ліор

1
Дякую :) Тим, хто запитує про COPA_RESULT, це не що інше, як створена користувачем статична змінна. "COPA" - це його послуга, щоб ви могли повністю замінити її на свою. У моєму випадку це статичний остаточний загальнодоступний String MP_Result = "com.widefide.musicplayer.MusicService.REQUEST_PROCESSED";
TheOnlyAnil

1
Користувальницький інтерфейс не оновлюватиметься, якщо користувач перейде від активності, а потім повернеться до нього після закінчення служби. Який найкращий спосіб впоратися з цим?
SavageKing

1
@SavageKing Вам потрібно надіслати будь-який запит до сервісу, відповідний вашому onStart()або onResume()способу. Загалом, якщо Активність вимагає від Служби щось зробити, але припиняє роботу, перш ніж отримувати результат, розумно припустити, що результат більше не потрібен. Аналогічно, починаючи діяльність, слід вважати, що не існує невирішених запитів, які обробляються Сервісом.
Клайд

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

не забудьте скасувати реєстрацію відновлення в діяльності на знищення:

unregisterReceiver(updateUIReciver);

1
Це краще рішення, але було б краще використовувати LocalBroadcastManager, якби він використовувався в додатку, який був би більш ефективним.
Псифер

1
наступні кроки після цього.sendBroadcast (локальний); в службі?
ангел

@angel немає наступного кроку, всередині додатків наміру просто додайте потрібні оновлення інтерфейсу, і це все
Eran Katsav

12

Я би використовував прив’язану послугу для цього і спілкувався з нею, використовуючи слухача у своїй діяльності. Отже, якщо ваш додаток реалізує myServiceListener, ви можете зареєструвати його як слухача у вашій службі після того, як ви зв’язалися з ним, зателефонуйте на прослуховування.


Це схоже саме на те, що я шукаю. Дозвольте спробувати. Дякую.
користувач200658

Будьте уважні, щоб не просочитися посиланням на свою діяльність. Тому що ваша діяльність може бути знищена та відтворена під час обертання.
Ерік

Привіт, перевірте це посилання. Я поділився зразком коду для цього. Розміщуючи посилання тут, припускаючи, що хтось може почувати себе корисним у майбутньому. myownandroid.blogspot.in/2012/08/…
jrhamza

+1 Це прийнятне рішення, але в моєму випадку мені дійсно потрібна послуга, щоб продовжувати працювати, навіть незважаючи на те, що вся активність відключається на неї (наприклад, користувач закриває програму, передчасне завершення операційної системи), у мене немає іншого вибору, як використовувати трансляцію приймачі.
Неонова Варга

9

Я рекомендую перевірити Otto , EventBus, призначений спеціально для Android. Ваша діяльність / інтерфейс користувача може прослуховувати події, розміщені в Автобусі від Сервісу, і від'єднуватись від бекенда.


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.

Скажімо, у вас є 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
Ніколи не займайтеся статичними змінними ні в сервісі, ні в будь-якій діяльності
Муртаза Хуршид Хуссейн

@MurtazaKhursheedHussain - чи можете ви детальніше зупинитися на цьому?
SolidSnake

2
Статичні члени є джерелом витоку пам’яті в діяльності (є багато статей навколо), а збереження їх у сервісі робить це ще гірше. Приймач широкомовної передачі набагато доречніший у ситуації з ОП, а зберігання в постійному сховищі також є рішенням.
Муртаза Хуршід Хуссей

@MurtazaKhursheedHussain - так, скажімо, якщо у мене є клас (а не клас обслуговування просто якийсь модельний клас, який я використовую для заповнення даних) зі статичним Hashmap, який повторно заповнюється деякими даними (що надходять від API) щохвилини вважається витік пам'яті?
SolidSnake

У контексті androidтак, якщо його список, спробуйте створити адаптер або зберегти дані, використовуючи sqllite / realm db.
Муртаза Хуршид Хуссей
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.