Arhn - архитектура программирования

Как остановить одну и ту же трансляцию (от PendingIntent) в очереди?

Сценарий

Я реализовал виджет приложения для Android.

Щелчок по виджету запустит трансляцию с помощью PendingIntent.getBroadcast(...).

Я хочу сделать сетевой запрос внутри onReceive широковещательного приемника.

(Вы спросите, почему бы мне не использовать PendingIntent.getService(...) и не запустить IntentService? Ну, это естественная идея, но, к сожалению, из-за фоновых ограничений служба не может быть запущена, если приложение не находится на переднем плане. Вы можете взглянуть на этот пост.)

Проблема

Чтобы доказать, что это работает, я реализовал образец BroadcastReceiver:

class WidgetClickBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        if (context == null || intent == null) return

        Log.i("Sira", "onReceive called")

        val pendingResult = goAsync()
        Observable.just(true).delay(3, TimeUnit.SECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe {
                Log.i("Sira", "Fake completion of network call")
                pendingResult.finish()
        }
    }

}

Да, это работает.

Однако я заметил, что если я нажму на виджет несколько раз, будет создано несколько трансляций и поставлено в очередь одна за другой, пока не будет вызвана предыдущая pendingResult.finish().

Это можно объяснить документацией goAsync():

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

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

Или любой другой способ предотвратить вызовы в очереди из-за сумасшедших кликов по виджету?


Ответы:


1

Изменить 2: возможное решение для виджета:
Сохраните timestamp в SharedPreferences (для каждого действия, если оно вам нужно) после завершения вашего действия.
После вызова onReceive снова проверьте timestamp для предпочитаемой дельты millis и запустите действие снова, только если дельта достаточно длинная.

Edit1: ответ ниже не работает для виджетов, я оставлю его для тех, кто ищет "обычный" случай

Я пробовал довольно много вещей (включая использование Handler и Reflection), наконец, я придумал следующее решение: когда вы получаете сообщение, которое вы не хотите получать снова, unregister (это конкретное действие) и register когда действие сделано. BroadcastReceiver ниже и вот полный пример проекта

package com.exmplae.testbroadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

public class SelfRegisteringBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "SelfRegisteringBR";
    public static final String TEST_ACTION1 = "TEST_ACTION1";
    public static final String TEST_ACTION2 = "TEST_ACTION2";
    private final ArrayList<String> registeredActions = new ArrayList<>();
    private final ILogListener logListener;
    private final Object registeringLock = new Object();

    public static IntentFilter getIntentFilter() {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(TEST_ACTION1);
        intentFilter.addAction(TEST_ACTION2);

        return intentFilter;
    }

    public SelfRegisteringBroadcastReceiver(ILogListener logListener) {
        this.logListener = logListener;
        registeredActions.add(TEST_ACTION1);
        registeredActions.add(TEST_ACTION2);
    }

    private void register(Context context, String action) {
        synchronized (registeringLock) {
            if (!registeredActions.contains(action)) {
                registeredActions.add(action);
                context.unregisterReceiver(this);
                register(context);
            }
        }
    }

    private void register(Context context) {
        IntentFilter intentFilter = new IntentFilter();
        for (String action : registeredActions) {
            intentFilter.addAction(action);
        }

        context.registerReceiver(this, intentFilter);
    }

    private void unregister(Context context, String action) {
        synchronized (registeringLock) {
            if (registeredActions.contains(action)) {
                registeredActions.remove(action);
                context.unregisterReceiver(this);
                register(context);
            }
        }
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        logListener.d(TAG, "onReceive");
        if (intent == null) {
            logListener.e(TAG, "intent = null");
            return;
        }

        String action = intent.getAction();
        if (action == null) {
            logListener.e(TAG, "action = null");
            return;
        }

        //noinspection IfCanBeSwitch
        if (action.equals(TEST_ACTION1)) {
            doAction1(context, TEST_ACTION1);
        } else if (action.equals(TEST_ACTION2)) {
            doAction2();
        } else {
            logListener.e(TAG, "Received unknown action: " + action);
        }
    }

    private void doAction1(final Context context, final String actionName) {
        logListener.d(TAG, "doAction1 start (and unregister)");
        unregister(context, actionName);
        Observable.just(true).delay(10, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Boolean>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        logListener.d(TAG, "doAction1 - onSubscribe");
                    }

                    @Override
                    public void onNext(Boolean aBoolean) {
                        logListener.d(TAG, "doAction1 - onNext");
                    }

                    @Override
                    public void onError(Throwable e) {
                        logListener.e(TAG, "doAction1 - onError");
                    }

                    @Override
                    public void onComplete() {
                        logListener.d(TAG, "doAction1 - onComplete (and register)");
                        register(context, actionName);
                    }
                });

        logListener.d(TAG, "doAction1 end");
    }

    private void doAction2() {
        logListener.d(TAG, "doAction2 start");
        Observable.just(true).delay(3, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Boolean>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        logListener.d(TAG, "doAction2 - onSubscribe");
                    }

                    @Override
                    public void onNext(Boolean aBoolean) {
                        logListener.d(TAG, "doAction2 - onNext");
                    }

                    @Override
                    public void onError(Throwable e) {
                        logListener.e(TAG, "doAction2 - onError");
                    }

                    @Override
                    public void onComplete() {
                        logListener.d(TAG, "doAction2 - onComplete");
                    }
                });

        logListener.d(TAG, "doAction2 end");
    }

}
18.12.2018
  • Спасибо за ваш ответ! Ваши действительно работают как местное вещание. Но, к сожалению, дело не в виджете; и вы также можете заметить, что ваше действие2 также не поставлено в очередь, оно все еще запускается, даже если предыдущее выполняется. Он просто складывается, но не ставится в очередь. В случае виджета (PendingIntent) он ставится в очередь. 19.12.2018
  • @SiraLam Я не уверен, что понимаю вас, action1 предназначен для работы один раз (одновременно), а action2 несколько раз - для примера. Вы имеете в виду, что он не работает из виджета, как из основного потока? 19.12.2018
  • Широковещательный приемник виджета имеет две вещи, сильно отличающиеся от вашего примера: (1) широковещательная передача получена через pendingIntent; (2) и, следовательно, у него нет фильтра намерений, приемник вещания указывается напрямую без каких-либо действий (фильтр намерений) при построении ожидающего намерения. 19.12.2018
  • Я вижу, я давно не смотрел на виджеты, мой плохой :) Тогда у меня есть идея, но она хакерская: вы можете использовать SharedPreferences для хранения своего состояния и проверять его перед запуском действия 19.12.2018
  • Но дело в том, что у меня даже нет возможности проверить, потому что onReceive вызывается только после завершения предыдущего, лол. 19.12.2018
  • Блин, простите... и это вид издалека, так что вы не можете знать там, верно? у меня нет идей 19.12.2018
  • Давайте продолжим обсуждение в чате. 19.12.2018
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..