LocalBroadcastManager устарел, как отправлять данные из службы в активность?

У меня есть служба, которая должна уведомлять об основной деятельности. Я использую LocalBroadcastManager, и он отлично работает, но LocalBroadcastManager устарел.

Это мой фактический код в службе:

public void onTokenRefresh() {
      
    /* build the intent */
    Intent intent = new Intent(ACTION_TOKENREFRESHED);
    intent.putExtra("token", "xxx");
    
    /* send the data to registered receivers */
    try{
      LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    } catch (Throwable e){
      //no exception handling
    }  
  
  }

В основном действии я получаю уведомление об уведомлении следующим образом:

context.registerReceiver(broadcastReceiver, intentFilter);

Что я могу использовать сейчас, чтобы удалить устаревшее предупреждение? Все найденные мной примеры отправки данных из службы в активность используют LocalBroadcastManager. Может ли кто-нибудь дать мне работоспособную модель для переноса моего существующего кода?

ПРИМЕЧАНИЕ

В моем примере onTokenRefresh вызывается из background thread. Это очень важно, потому что это означает, что я могу одновременно получать несколько onTokenRefresh, и я должен пересылать все эти токены активности. Большинство предлагаемых решений используют данные в реальном времени, но делают объявление вроде:

public static final MutableLiveData<String> tokenLiveData = new MutableLiveData<>();

Background Thread1:
tokenLiveData.postValue(Token1);

Background Thread2 (at same time):
tokenLiveData.postValue(Token2);

Будут ли пересылаться ВСЕ токены, полученные одновременно, в основное действие, которое наблюдает за tokenLiveData? Будет ли основная активность всегда получать наверняка token1 и token2?

Один из способов решить эту дилемму — использовать оперативные данные на уровне приложения, которые любая деятельность может наблюдать и манипулировать ими из сервиса.

Jeel Vankhede 31.10.2022 15:58

@JeelVankhede спасибо, но у вас есть образец этого?

zeus 31.10.2022 16:37

Попросите службу обновить какой-то репозиторий или другой синглтон. Пусть этот репозиторий предоставляет реактивный API (RxJava, LiveData и т. д.), который может наблюдать ваш уровень пользовательского интерфейса (например, модель представления).

CommonsWare 31.10.2022 16:53

спасибо @commonsWare, у вас есть пример работы? в сети, когда я ищу отправку данных из службы в активность, я нашел только пример с LocalBroadcastManager

zeus 31.10.2022 17:01

Если вы имеете в виду примеры синглтонов репозитория и реактивных API, я демонстрирую это в этой бесплатной книге в нескольких местах, включая этот пример. Я не показываю ничего, связанного с сервисом, так как в настоящее время немногие приложения нуждаются в сервисах. Но, в конце концов, это не сильно отличается от работы пользовательского интерфейса с репозиторием, за исключением, возможно, различий в том, как вы получаете доступ к синглтону репозитория.

CommonsWare 31.10.2022 17:16

В более общем плане использование многоуровневой архитектуры, включая репозитории, за последние несколько лет довольно много обсуждалось в Интернете. См., например, документацию.

CommonsWare 31.10.2022 17:18

Используйте функцию AIDL Android. Это встроенная функция андроида. Ссылка: developer.android.com/guide/components/aidl

Aniruddh Parihar 25.11.2022 08:10

спасибо @AniruddhParihar, но как использовать AIDL?

zeus 25.11.2022 08:15

подождите, я отправляю ответ.

Aniruddh Parihar 25.11.2022 08:17

@zeus: проверьте опубликованный ответ.

Aniruddh Parihar 25.11.2022 11:47
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
6
10
499
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Если у вас есть возможность использовать LiveData, это будет самый простой и чистый вариант. Если у вас есть сложные данные, которыми вы не хотите делиться в LiveData, или если это не сохранение потока, вы можете просто использовать LiveData для сигнализации и читать определенные данные способом сохранения потока.

Однако, если это невозможно/точно, есть варианты с шинами событий, такие как GreenRobot (например, есть это руководство). Но у них могут отсутствовать некоторые из тех же проблем, почему они устарели LocalBroadcastManager.

Альтернативой является использование глобальной трансляции. Если у вас есть сложные данные, вы можете использовать их только для сигнализации и добавить ContentResolver или что-то подобное, чтобы получить данные. Но обратите внимание, что он не очень производительный, так как он обрабатывается ОС и пытается найти любые подходящие BroadcastRecievers на устройстве и выполняет много операций с оперативной памятью (вы найдете это в видео из Google)

Таким образом, для службы, используемой только локально, вы можете использовать IBinder / Binder.

Если у вас есть простые данные, вы используете transact(int code, Parcel data, Parcel reply, int flags), где третий параметр — это данные, которые вы хотите вернуть в качестве ответа от Service. Но есть два ограничения:

  1. Это посылка, поэтому возможны только сериализуемые данные.
  2. Из сервиса вы не можете напрямую уведомить клиента (Activity в вашем случае).

Если у вас есть более сложные данные, вы можете реализовать определенный метод в своем классе Binder и использовать параметры и возвращаемые значения.

Если вы реализуете LiveData в Binder, вы можете использовать его для уведомления или, если данные не очень сложны (например, токен в вашем случае), чтобы содержать все данные.

Вы бы реализовали это так:

public class MyService extends Service
{
    protected MutableLiveData<String> mToken;

    public MyService()
    {
        mToken = new MutableLiveData<>("current token");
    }

    @Override
    public IBinder onBind(Intent intent)
    {
        return new Binder();
    }

    public class Binder extends android.os.Binder
    {
        public void changeToken(String newToken)
        {
            mToken.postValue(newToken);
        }

        public LiveData<String> getToken()
        {
            return mToken;
        }
    }
}

и ваша активность:

public class MainActivity extends AppCompatActivity
{
    private ActivityMainBinding binding;
    private ServiceConnection mConnection;
    
    @Override
    protected void onStart()
    {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, MyService.class);
        mConnection = new ServiceConnection();
        // If your service is already running, this will just bind it.
        // If it's not running, it will also start it because of the second argument
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop()
    {
        super.onStop();
        if (mConnection != null)
        {
            unbindService(mConnection);
        }
    }

    /**
     * Defines callbacks for service binding, passed to bindService()
     */
    private class ServiceConnection implements android.content.ServiceConnection
    {
        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder service)
        {
            // We've bound to MyService.Binder, cast the IBinder and get MyService.Binder instance
            MyService.Binder binder = (MyService.Binder) service;
            binder.getToken().observe(MainActivity.this, (newToken) -> binding.Token.setText(newToken));
            binding.StartButton.setOnClickListener((click) -> binder.changeToken("newToken" + new Random().nextInt()));
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0)
        {
            mConnection = null;
        }
    }
}

Я проверил пример, он работает ;-)

Если по какой-либо причине вы не можете связать Service и Activity так плотно, вы можете вместо этого установить LiveData (любого рода) в своем классе Application, послушать его в своем Activity и заменить его своим Service. Но делайте это только в том случае, если Service а) очень независим от вашего Activity и б) работает большую часть времени. В противном случае у вас будет LiveData, что в большинстве случаев неуместно.

Я лично часто пользуюсь "Сервисами". Не в том смысле, как их описывает Android, а в одноэлементных классах, которые отвечают за какую-то часть моего приложения (вы также можете называть их контроллерами, хотя это не совсем точно). Затем я регистрирую их как синглтон с Рукоятью ( Кинжалом некоторое время назад). В вашем случае вы можете установить LiveData на такой «Сервис».

Обновлять

Из-за вашего обновления о том, что вы не должны терять ни один из этих токенов, ответ немного сложнее.

Если вы уверены, что обновление может происходить не чаще, чем, скажем, каждые 10 секунд, вы можете смело использовать приведенный выше код. Если это может происходить чаще или вы не уверены, то не делайте этого.

Почему? Потому что на LiveData есть два метода: setValue и postValue. setValue выполняет уведомление немедленно, поэтому каждый новый токен будет отправлен наблюдателю. Но его можно вызывать только из основного потока, что для вас не так. Поэтому вам нужно вызвать postValue, который помещает задачу в конец внутреннего списка задач основного потока. Если новый вызов postValue выполняется до выполнения уведомления, новая задача не добавляется в очередь и будет доставлено только самое последнее значение.

Что вы можете сделать вместо этого? Вы можете изменить службу на следующую:

public class MyService extends Service
{
    protected MutableLiveData<Integer> mVersion = new MutableLiveData<>(0);
    protected ConcurrentLinkedQueue<String> mTokens = new ConcurrentLinkedQueue<>();

    public void newToken(String token)
    {
        mTokens.offer(token);
        mVersion.postValue(mVersion.getValue() + 1);
    }

    @Override
    public IBinder onBind(Intent intent)
    {
        return new Binder();
    }

    public class Binder extends android.os.Binder
    {
        public AbstractQueue<String> getTokens()
        {
            return mTokens;
        }

        public LiveData<Integer> getSignal()
        {
            return mVersion;
        }
    }
}

В своей деятельности вы просто звоните mTokens.poll(), пока очередь не пуста. Но это работает только в том случае, если ваша служба работает только тогда, когда активность присутствует (более или менее), и ваша активность является единственной, которая привязывается к вашей службе (=> является единственным потребителем). Если служба может работать в фоновом режиме, это может в конечном итоге привести к переполнению памяти, если у вас много новых токенов. Кроме того, вы можете потерять свои данные, если по какой-либо причине ваша служба будет отключена операционной системой.

Лучший способ — сохранить данные где-нибудь и использовать LiveData только для сохранения версии. Затем ваша активность может хранить локальную переменную, содержащую последнюю версию, и читать только оставшиеся токены. Для этого можно использовать несколько подходов:

Конечно, есть и другие способы, но поскольку я недостаточно знаю о токенах и о том, что вы хотите с ними делать, я даже не могу сказать, какой из этих подходов подходит вам лучше всего.

спасибо, Аорлин, но мне нужен работающий пример, у вас есть? Я не хочу использовать библиотеку уровней, такую ​​​​как GreenRobot, по той же причине, по которой они устарели LocalBroadCastManager.

zeus 23.11.2022 07:05

Поскольку ответ @MariosP не показывает, как вы связываете сервис и активность, я добавил пример.

Aorlinn 23.11.2022 21:26

спасибо за вашу помощь, я обновил вопрос и добавил примечание в конце о фоновых потоках. ваше решение будет работать в таком сценарии?

zeus 26.11.2022 09:51

Это зависит ;-) Смотрите мое обновление...

Aorlinn 26.11.2022 17:46

Ваш ответ был очень хорошим, спасибо! но это как и все остальные, поэтому я просто дал награду тому, у кого больше всего голосов :(

zeus 29.11.2022 09:41

Согласно официальной документации LocalBroadcastManager:

Этот класс устарел. LocalBroadcastManager — это приложение для всего приложения. шина событий и охватывает нарушения уровня в вашем приложении: любой компонент может слушать события из любого другого. Вы можете заменить использование LocalBroadcastManager с другой реализацией наблюдаемого шаблона, в зависимости от вашего варианта использования подходящие варианты могут быть androidx.lifecycle.LiveData или реактивные потоки.

Таким образом, вы можете заменить его другим наблюдаемым шаблоном, таким как LiveData или реактивные потоки. Поскольку LiveData наиболее популярен в настоящее время и учитывает жизненный цикл, что гарантирует обновление наблюдателей компонентов приложения, таких как действия, фрагменты или службы, которые имеют активное состояние жизненного цикла, только ниже я опишу шаги по замене LocalBroadcastManager с помощью LiveData.

1.) Объявите класс NotificationLiveData, который наследуется от LiveData<T>, где T — это строка, в приведенном ниже примере:

public class NotificationLiveData extends LiveData<String> {

    public void setNotification(String message){
        postValue(message);
    }
}

2.) Объявите класс NotificationManager, который отвечает за обновление сообщения, как показано ниже:

public class NotificationManager {

    private static final NotificationLiveData notificationLiveData = new NotificationLiveData();

    public static void updateNotificationMessage(String message){
        notificationLiveData.setNotification(message);
    }

    public static NotificationLiveData getNotificationLiveData() {
        return notificationLiveData;
    }
}

3.) В каждом Activity вы хотите слушать обновления, вы можете наблюдать изменения, как показано ниже:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    NotificationManager.getNotificationLiveData().observe(this, notificationObserver);
}

private final Observer<String> notificationObserver = new Observer<String>() {
    @Override
    public void onChanged(String s) {
        //do anything after a change happened
    }
};

4.) В каждом Fragment вы хотите слушать обновления, вы можете наблюдать изменения, как показано ниже:

public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    NotificationManager.getNotificationLiveData().observe(getViewLifecycleOwner(), notificationObserver);
} 

private final Observer<String> notificationObserver = new Observer<String>() {
    @Override
    public void onChanged(String s) {
        //do anything after a change happened
    }
};

5.) Из вашего Service вы можете отправить сообщение, как показано ниже:

NotificationManager.updateNotificationMessage("My message");

6.) Наконец, не забудьте удалить наблюдателя из любого метода Activity или Fragment в onDestroy(), как показано ниже:

@Override
protected void onDestroy() {
    super.onDestroy();
    NotificationManager.getNotificationLiveData().removeObserver(notificationObserver);
}

Вышеприведенное будет прослушивать изменение только тогда, когда Activity/Fragment имеет активное состояние жизненного цикла. Если вы хотите всегда слушать изменения независимо от состояния жизненного цикла, вы можете использовать observeForever вместо observe, как показано ниже:

NotificationManager.getNotificationLiveData().observeForever(notificationObserver);

Обновлять:

В соответствии с вашим новым требованием, имеющим несколько фоновых потоков, которые одновременно отправляют несколько токенов, вы больше не можете использовать метод postValue, поскольку он не гарантирует, что конкретный токен будет получен от наблюдателя, если postValue вызывается много раз одновременно поэтому в этом случае наблюдатель получит только последний токен. Вы можете проверить это, если вызовете метод postValue() и немедленно вызовете метод getValue(), где вы можете не получить значение, которое вы только что установили. Однако это можно решить только с помощью метода setValue, который выполняется только из основного потока. Когда основной поток устанавливает значение, вы сразу же получите значение в наблюдателе, но будьте осторожны, observe прослушивает изменение, только если Activity/Fragment имеет активное состояние жизненного цикла. Если вы хотите всегда слушать изменения независимо от жизненного цикла, вы должны вместо этого использовать observeForever.

Поэтому вам нужно изменить класс NotificationLiveData, чтобы использовать setValue в основном потоке, как показано ниже:

public class NotificationLiveData extends MutableLiveData<String> {

    public void setNotification(String message){
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                setValue(message);
            }
        });
    }
}

Но в среде многопоточности лучше использовать RxJava, который использует реактивные потоки для одновременной отправки пакетов данных. RxJava имеет лучший уровень абстракции вокруг многопоточности, что упрощает реализацию сложного параллельного поведения, поэтому в данном случае он лучше подходит. Ниже я опишу, как этого можно добиться, используя RxJava вместо LiveData.

RxJava — Подход

1.) Сначала объявите в зависимостях вашего приложения следующие Reactivex реализации:

implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
implementation 'io.reactivex.rxjava3:rxjava:3.1.5'

Вот официальная документация ReactiveX/RxAndroid для более подробной информации.

2.) Объявите новый класс NotificationManager таким, как показано ниже:

public class NotificationManager {

    private static final Subject<String> notificationSubject = PublishSubject.create();

    public static void updateNotificationMessage(String message){
        notificationSubject.onNext(message);
    }

    public static Subject<String> getNotificationSubject() {
        return notificationSubject;
    }
} 

PublishSubject представляет собой Observer и Observable одновременно, что позволяет выполнять многоадресную рассылку событий из одного источника нескольким дочерним наблюдателям. Он передает наблюдателю все последующие элементы исходного Observable на момент подписки.

3.) Вы можете подписаться на любой Activity, как показано ниже:

Disposable disposable = NotificationManager.getNotificationSubject()
        .doOnNext(s -> { Log.d("OnNext = ", s);})
        .doOnComplete(() -> { })
        .doOnError(throwable -> { })
        .subscribe();

doOnNext вызывается каждый раз, когда вы вызываете notificationSubject.onNext(token).

4.) В вашем Service в каждом из ваших потоков вы можете одновременно выпускать новые токены, как показано ниже:

Thread thread1 = new Thread() {
    public void run() {
        for (int i = 0; i < 10; i++) {
            NotificationManager.updateNotificationMessage("Thread1:" + i);
        }
    }
};
thread1.start();

Thread thread2 = new Thread() {
    public void run() {
        for (int i = 0; i < 10; i++) {
            NotificationManager.updateNotificationMessage("Thread2:" + i);
        }
    }
};
thread2.start();

5.) Наконец, в методе onDestroy() вашего Activity вы должны удалить PublishSubject, как показано ниже:

@Override
protected void onDestroy() {
  super.onDestroy();
  disposable.dispose();
}

И результаты приведенного выше примера многопоточности будут примерно такими:

D/OnNext=: Thread2:0
D/OnNext=: Thread2:1
D/OnNext=: Thread2:2
D/OnNext=: Thread2:3 
D/OnNext=: Thread2:4
D/OnNext=: Thread2:5
D/OnNext=: Thread1:0
D/OnNext=: Thread2:6
D/OnNext=: Thread2:7
D/OnNext=: Thread1:1
D/OnNext=: Thread2:8
D/OnNext=: Thread1:2
D/OnNext=: Thread2:9
D/OnNext=: Thread1:3
D/OnNext=: Thread1:4
D/OnNext=: Thread1:5
D/OnNext=: Thread1:6
D/OnNext=: Thread1:7
D/OnNext=: Thread1:8
D/OnNext=: Thread1:9

Также, если вы знаете, когда передача данных будет завершена, вы можете просто использовать метод notificationSubject.onComplete(), и .doOnComplete(() -> { }) будет вызываться в Activity и автоматически удалять PublishSubjectObservable. Аналогичный вызов можно использовать, когда что-то пойдет не так, используя notificationSubject.onError(new Throwable()), который запускает обратный вызов Activity.doOnError(throwable -> { }).

спасибо за вашу помощь, я обновил вопрос и добавил примечание в конце о фоновых потоках. ваше решение будет работать в таком сценарии?

zeus 26.11.2022 09:51

Ваш ответ был очень хорошим, спасибо! но это как и все остальные, поэтому я просто дал награду тому, у кого больше всего голосов :(

zeus 29.11.2022 09:40
Ответ принят как подходящий

Создайте класс service и определите LiveData для замены ответственности LocalBroadcastManager следующим образом:

//This service sends an example token ten times to its observers
public class MyService extends Service {
    //Define a LiveData to observe in activity
    public static final MutableLiveData<String> tokenLiveData = new MutableLiveData<>();

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //You need a separate thread if you don not use IntentService

        Thread thread1 = new Thread() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    //send random strings az an example token ten times.
                    //You can remove this loop and replace it with your logic
                    String token1 = UUID.randomUUID().toString();
                    new Handler(Looper.getMainLooper()).post(() -> sendTokenToObserver("Thread1: " + token1));

                }
            }
        };
        thread1.start();

        Thread thread2 = new Thread() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    String token2 = UUID.randomUUID().toString();
                    new Handler(Looper.getMainLooper()).post(() -> sendTokenToObserver("Thread2: " + token2));
                }
            }
        };
        thread2.start();
        return START_STICKY;
    }

    //Post token to observers
    public void sendTokenToObserver(String token) {
        tokenLiveData.setValue(token);

    }
}

Затем запустите service в activity и наблюдайте за LiveData, как показано ниже:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //You can observe the emitted token here and do what you want(Show in view or notification). 
        //I've just logged it to the console.
        startService(new Intent(this,MyService.class));
        MyService.tokenLiveData.observe(this, token -> Log.d("token", token));
    }
}

Вы также можете startservice из another activity и наблюдать за ним в MainActivity;

Статические данные могут быть очень подвержены ошибкам, особенно при тестировании. Если вы используете «статические» данные, лучше переместите их в класс вашего приложения.

Aorlinn 23.11.2022 22:00

Как это может быть подвержено ошибкам в моем примере? Более того, класс приложения не отвечает за обработку статических данных. @Аорлинн

Squti 24.11.2022 04:59

Например, когда у вас есть тесты, которые считывают его, или поведение, которое на него опирается. Следующий тест всегда будет иметь значение предыдущего, и вы не знаете, было ли значение записано в этот раз или в предыдущем тесте. Если вы поместите его в класс приложения, он будет удален и воссоздан в следующий раз.

Aorlinn 24.11.2022 10:45

Вы используете сервисы в интеграционных тестах. В интеграционных тестах все состояния и переменные будут сброшены в каждом тестовом примере. Кроме того, поле LiveData является окончательным, поэтому вы не можете его переназначить.

Squti 24.11.2022 14:55

Когда вы говорите об интеграционных тестах, вы имеете в виду инструментальные тесты Android, верно? Если это так, статические переменные не сбрасываются автоматически (только если вы делаете это самостоятельно явно. Но в этом случае вам всегда нужно помнить об этом). Если нет, скажите мне, что вы используете, тогда мне обязательно нужно взглянуть на это.

Aorlinn 24.11.2022 15:14

Каждый раз, когда вы тестируете средство запуска тестов сценария, запускает новый экземпляр приложения с состояниями по умолчанию.

Squti 24.11.2022 19:58

Это делается путем «убийства» всех объектов, таких как приложение и т. д. Но поскольку все тесты выполняются в одном и том же процессе, это не влияет на статические поля. Проверьте сами: добавьте статическое поле, например. ваш класс приложения и инициализируйте его с помощью new Random().nextInt(). Вы увидите, что каждый тест будет иметь одно и то же значение, чего не было бы, если бы он повторно инициализировался для каждого нового выполнения теста.

Aorlinn 24.11.2022 20:11

@Squti У меня есть один вопрос, если мы удалим «Thread.sleep (1000);» будет ли действие по-прежнему получать ВСЕ сообщения, отправленные через sendTokenToObserver, или некоторые из них не будут просматриваться/обрабатываться действием? IE будет работать как очередь?

zeus 25.11.2022 23:50

LiveData postValue() отправляет задачу в основной поток, чтобы установить данное значение. Если вы вызвали этот метод несколько раз до того, как основной поток выполнил опубликованную задачу, будет отправлено только последнее значение, а предыдущие значения будут удалены. Если вам нужно получить доступ ко всем отправленным значениям, вы должны вместо этого использовать метод setValue(). Но проблема в том, что вам нужно вызвать setValue() в основном потоке, а в приведенном выше примере вам нужно переместить sendTokenToObserver() из метода run() класса Thread. Если вы выполняете тяжелую или длительную операцию в основном потоке, вы можете заблокировать рендеринг пользовательского интерфейса. @zeus

Squti 26.11.2022 06:47

@Squti спасибо за вашу помощь, я обновил вопрос и добавил в конце примечание относительно потоковой передачи в фоновом режиме. ваше решение будет работать в таком сценарии?

zeus 26.11.2022 09:51

Я обновил свое решение, и оно гарантирует, что все токены, испускаемые любым потоком, будут наблюдаться основной активностью. @Зевс

Squti 26.11.2022 11:11

Язык определения интерфейса Android (AIDL) аналогичен другим IDL, с которыми вам, возможно, приходилось работать. Это позволяет вам определить интерфейс программирования, с которым согласуются и клиент, и ремонт, чтобы общаться друг с другом с использованием межпроцессного взаимодействия (IPC). Традиционно в Android процесс не может получить доступ к памяти другого процесса. Таким образом, чтобы иметь возможность общаться, они разлагают свои объекты на примитивы, которые операционная система может хорошо понять, и распределяют объекты через эту границу для вас. Код для выполнения этого маршалинга утомительно записывать, поэтому Android обрабатывает его за вас с помощью AIDL.

Примечание. Использование AIDL важно, если вы разрешаете клиентам из разных приложений получать доступ к вашей службе для IPC и должны обрабатывать многопоточность в вашем сервисе.

Но прежде чем вы начнете разрабатывать свой собственный AIDL, обратите внимание, что вызовы AIDL являются прямыми вызовами правильных функций! Так что не стоит предполагать о потоке, во время выполнения которого происходит решение. То, что происходит, зависит от того, исходит ли решение от потока внутри локального процесса или от внешнего процесса.

Конкретно

  1. Вызовы, состоящие из локального процесса, выполняются внутри одного и того же процесса. поток, который принимает решение. Если этот вызов из вашего основного пользовательского интерфейса поток, который будет продолжать выполняться в AIDL интерфейс, однако в случае, если это другой (поток), то это один выполнит ваш в службе!

  2. Вызовы от стороннего процесса отправляются из пула потоков. платформа поддерживает внутри вашего собственного процесса. ты хочешь быть готовым для входящих вызовов из неизвестных потоков, с несколькими вызовами происходит в эквивалентное время.

  3. Ключевое слово oneway определяет, как будет вести себя удаленный вызов. будет, при использовании внешний вызов не блокируется; он просто отправляет данные транзакции и немедленно возвращается. Реализация интерфейс в конечном итоге получает это как ежедневный вызов от Пул потоков Binder как традиционный удаленный вызов. Если в одну сторону используется с локальным вызовом, нет никакого воздействия, и поэтому вызов остается синхронным.

Определение интерфейса AIDL

Вы должны определить свой интерфейс AIDL в файле .aidl, используя синтаксис языка программирования Java, а затем зарезервировать его в текстовом файле ASCII (в каталоге src/) как устройства, на котором размещается служба, так и другого приложения, которое привязывается к службе. Чтобы создать .aidl файл, используйте эту ссылку!

Ну вот например:

Процессу А требуется информация о статусе вызова, чтобы решить, должен ли он изменить тип вызова (например, изменить голос на видео). вы получите статус звонка от определенных слушателей, но чтобы изменить тип звонка с аудио на видео, процессу А нужен хук для изменения. Этот «крючок» или способ настройки вызовов обычно является частью «> части классов телефонии, которые являются частью процесса телефонии. Чтобы получить такую ​​информацию из процесса телефонии, можно написать службу телефонии (которая работает как часть процесса телефонии Android), которая позволит вам задать вопрос или изменить тип звонка. Поскольку процесс A (клиент) здесь использует эту удаленную службу, которая взаимодействует с процессом телефонии для изменения типа вызова, он должен иметь интерфейс для взаимодействия со службой. Поскольку услуга телефонии — это провайдер, а процесс А (клиент) — это пользователь, они оба должны согласовать интерфейс (протокол), который они будут понимать и за который будут цепляться. Таким интерфейсом является AIDL, который позволяет вам общаться (через стороннюю службу) с процессом телефонии и выполнять некоторую работу.

Проще говоря, AIDL — это «соглашение», которое получает Клиент, в котором говорится о том, как запросить услугу. Сама служба будет иметь копию этого соглашения (поскольку оно опубликовано для своих клиентов). Затем служба будет реализовывать детали того, как она обрабатывается после получения приглашения или сообщает, когда кто-то читает ему лекцию.

Таким образом, процесс A запрашивает изменение вызова через Сервис, Сервис получает запрос, он обращается к процессу телефонии (поскольку он является его частью) и изменяет вызов на видео.

Важно отметить, что AIDL просто необходим для многопоточной среды. вы покончите с Binders, если не сможете повлиять на многопоточную арку. Пример. Процессу А требуется информация о статусе вызова, чтобы решить, должен ли он изменить тип вызова (например, с аудио на видеовызов или наоборот). вы получите статус звонка от определенных слушателей, но чтобы изменить тип звонка с аудио на видео, процессу А нужен хук для изменения. Этот «крючок» или способ настройки вызовов обычно является частью «> части классов телефонии, которые являются частью процесса телефонии. Чтобы получить такую ​​информацию из процесса телефонии, можно написать службу телефонии (которая работает как окружение процесса телефонии Android), которая позволит вам задавать вопросы или изменять тип звонка. Поскольку процесс A (клиент) здесь использует эту удаленную службу, которая взаимодействует с процессом телефонии для изменения типа вызова, он должен иметь интерфейс для взаимодействия со службой. Поскольку услуга телефонии — это провайдер, а процесс А (клиент) — это пользователь, они оба должны согласовать интерфейс (протокол), который они будут понимать и за который будут цепляться. Таким интерфейсом является AIDL, который позволяет вам общаться (через стороннюю службу) с процессом телефонии и выполнять некоторую работу.

Проще говоря, AIDL — это «соглашение», которое получает Клиент, в котором говорится о том, как запросить услугу. Сама служба будет иметь копию этого соглашения (поскольку оно опубликовано для своих клиентов). Затем служба будет реализовывать детали того, как она обрабатывается после получения приглашения или сообщает, когда кто-то читает ему лекцию.

Таким образом, процесс A запрашивает изменение вызова через Сервис, Сервис получает запрос, он обращается к процессу телефонии (поскольку он является его частью) и изменяет вызов на видео.

Важно отметить, что AIDL просто необходим для многопоточной среды. вы покончите с Binders, если не сможете повлиять на многопоточную арку.

Вот фрагмент кода, который поможет вам в действии:

// Declare any non-default types
// here with import statements
interface AidlInterface {
        int add(int x,int y);
        int sub(int x,int y);
    }

Служба AIDL для приведенного выше кода:

public class AidlServiceGfG extends Service {

    private static final String TAG = "AIDLServiceLogs";
    private static final String className = " AidlService";

    public AidlService() {
        Log.i(TAG, className+" Constructor");
    }

    @Override
    public IBinder onBind(Intent intent) {
        // GfG Example of Binder
        Log.i(TAG, className+" onBind");
        return iCalculator.asBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, className+" onCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, className+" onDestroy");
    }

    ICalculator.Stub iCalculator = new ICalculator.Stub() {
        @Override
        public int add(int x, int y) throws RemoteException {
            Log.i(TAG, className+" add Thread Name: "+Thread.currentThread().getName());
            int z = x+y;
            return z;
        }

        @Override
        public int sub(int x, int y) throws RemoteException {
            Log.i(TAG, className+" add Thread Name: "+Thread.currentThread().getName());
            int z = x-y;
            return z;
        }
    };

}

А вот и сервисное подключение:

// Returns the stub
ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, className + " onServiceConnected");
            iCalculator = ICalculator.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

            unbindService(serviceConnection);
        }
    };

Заключение

AIDL использует драйвер ядра связующего для формирования вызовов. как только вы делаете вызов, идентификатор пути и каждый из объектов упаковываются в буфер и копируются во внешний процесс, где поток связывателя ждет, чтобы прочитать информацию. Когда вызовы выполняются в рамках одного и того же процесса и, следовательно, одного и того же бэкэнда, прокси-объектов не существует, тогда вызовы являются прямыми без упаковки или распаковки.

Я думаю, вы можете использовать EventBus, ссылка здесь: https://github.com/greenrobot/EventBus

FAQ: https://greenrobot.github.io/EventBus/

В: Чем EventBus отличается от системы Android BroadcastReceiver/Intent?

О: В отличие от системы Android BroadcastReceiver/Intent, EventBus использует в качестве событий стандартные классы Java и предлагает более удобный API. EventBus предназначен для гораздо большего количества случаев использования, когда вы не хотели бы проходить через хлопоты с настройкой Intent, подготовкой дополнений Intent, реализацией широковещательных приемников и повторным извлечением дополнений Intent. Кроме того, EventBus имеет гораздо меньшие накладные расходы.

Таким образом, вы можете объявить свой класс данных, например:

public class RefreshTokenData {
  private String token;
  private String...
  ...// Other field
}

В вашей деятельности простой способ:

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onRefreshToken(RefreshTokenData data) {
    // Do something
}

Вместо этого вы можете переключиться на другую тему main с помощью ThreadMode.

В вашей службе:

public void onTokenRefresh() {
      
    final RefreshTokenData refreshTokenData = new RefreshTokenData("xxx", ...);
    
    EventBus.getDefault().post(refreshTokenData);
}

После токен будет отправлен в активность в методе onRefreshToken, объявленном выше.

Не забудьте отписаться от своей активности:

@Override
public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
}

@Override
public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
}

Подробнее: https://greenrobot.org/eventbus/documentation/how-to-get-started/

Режим потока и ваша заметка

Ссылка: https://greenrobot.org/eventbus/documentation/delivery-threads-threadmode/

Я предлагаю использовать ThreadMode: MAIN_ORDERED, вы получите все свои токены обновления последовательно, потому что With MAIN_ORDERED, the first event handler will finish, and then the second one will be invoked at a later point in time.

@zeus, вы можете проверить мой отредактированный ответ для своей заметки.

dinhlam 29.11.2022 02:55

@dinhlan ваш ответ был очень хорош, как и все остальные, поэтому я просто отдал награду тому, у кого больше всего голосов :(

zeus 29.11.2022 09:40

Другие вопросы по теме