OnSharedPreferenceChangeListener не работает в другой задаче потока

При использовании потока/задачи в службе Android, которая реализует интерфейс OnSharedPreferenceChangeListener, изменения, сделанные на экране настроек, не отражаются обратно в объект потока/задачи в службе Android.

Я хочу выполнить две вещи:

  • Данные SharedPreference должны загружаться при создании и инициализации MyTask.

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

Проблема в том, что инициализация и изменения предпочтений не отражаются в объекте MyTask.

Это моя установка (упомянуты только основные части):

MyService.class:

public class MyService extends Sevice {
    private MyTask myTask;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!serviceStarted) {
            serviceStarted = true;
            myTask = new MyTask(this);
            Thread t = new Thread(myTask);
            t.start();
        }
        return Service.START_STICKY;
    }

    @Override
    public void onDestroy() {
        myTask.cancel();
        super.onDestroy();
    }
}

Моя задача.класс:

public MyTask implements Runnable, OnSharedPreferenceChangeListener {
    private Context mContext;
    private boolean mCancelled;
    public MyTask(Context context) {
        mContext = context;
    }

    @Override
    public void run() {
        while(!mCancelled) {
            // do something
        }
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
        String key) {
        // FIXME: DOESN'T GET CALLED after change in preference!!!!
        Log.d(TAG, "Key= " + key);
    }

    public void cancel() {
        mCancelled = true;
    }
}

preference_devices.xml:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <PreferenceCategory
        android:key="pref_category_devices"
        android:title="@string/pref_category_devices_title" >
        <CheckBoxPreference
            android:defaultValue="true"
            android:key="pref_devices_server"
            android:title="@string/pref_devices_server_title" />
    </PreferenceCategory>
</PreferenceScreen>

Я попытался закодировать объект прослушивателя SharedPreferences как поле-член класса MyTask и зарегистрировать/отменить регистрацию прослушивателя из предоставленного контекста, но это тоже не сработало. Эти изменения также не сработали:

MyTask.class (с использованием прослушивателя SharedPreference в качестве члена поля класса):

public MyTask implements Runnable {
    private Context mContext;
    private boolean mCancelled;
    private boolean mServerEnabled;
    private SharedPreferences mPrefs;
    private SharedPreferences.OnSharedPreferenceChangeListener
        mPreferenceListener;

    public MyTask(Context context) {
        mContext = context;
        mPrefs = mContext.getSharedPreferences("pref_category_devices",
                Context.MODE_PRIVATE);
        mPreferenceListener = new OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(
                SharedPreferences sharedPreferences, String key) {
                // FIXME: DOESN'T GET CALLED after change in preference!!!!
                Log.d(TAG, "Key= " + key);
            }
        };
        mPrefs.registerOnSharedPreferenceChangeListener(mPreferenceListener);
        // set the initial value of the preference setting
        mServerEnabled = mPrefs.getBoolean("pref_devices_server", false);
    }

    @Override
    public void run() {
        while(!mCancelled) {
            // do something
        }
    }

    public void cancel() {
        mCancelled = true;
    }
}

Я уже дошел до того, что выбросил свой компьютер из окна :(

Любая помощь в правильном направлении приветствуется :)

РЕДАКТИРОВАТЬ: В коде

mPrefs = mContext.getSharedPreferences("pref_category_devices", Context.MODE_PRIVATE);

Я предположил, что первым аргументом должно быть имя категории предпочтений файла настроек, например: «pref_category_devices». ЭТО НЕПРАВИЛЬНО! Первым аргументом должно быть общее имя файла настроек. Это не решило проблему, но, по крайней мере, теперь вы знаете, что не попадете в эту ловушку.

=== РЕШЕНИЕ: === См. ответ Mr_and_Mrs_D + код под этой строкой:

Изменение в MyTask:

mPrefs = mContext.getSharedPreferences("pref_category_devices",
            Context.MODE_PRIVATE);

в:

mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mPreferenceListener = new OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (key.equals("preference_name_here")) {
            mPrefValue = sharedPreferences.getBoolean(key, false);
            // do something with boolean pref value 
        }
    }
};
mPrefs.registerOnSharedPreferenceChangeListener(myPreferenceListener);

Где mPrefValue — это член поля логического типа в MyTask, который необходимо установить при изменении предпочтения «preference_name_here».


person user504342    schedule 12.12.2013    source источник
comment
Кстати, ваша служба отменяется, когда вызывается onDestroy?   -  person Mr_and_Mrs_D    schedule 13.12.2013
comment
@Mr_and_Mrs_D да, это отменяется. Я вижу это в журнале logcat. Он выходит из цикла while.   -  person user504342    schedule 13.12.2013
comment
re: ваше редактирование - я так догадался - но в любом случае лучше использовать getDefaultSharedPreferences, за исключением случаев, когда вам действительно нужен файл   -  person Mr_and_Mrs_D    schedule 13.12.2013
comment
re: ваше решение - я не знал, что вы хотите что-то изменить в своей myTask, но в любом случае, если бы ваша myTask реализовала OnSharedPreferenceChangeListener, где бы вы его ни зарегистрировали, это было бы то же самое. В любом случае, будьте осторожны с многопоточностью — попробуйте прочитать об этом — это очень сложно, и все может пойти не так, как надо. Для одного сделайте mCancelled volatile   -  person Mr_and_Mrs_D    schedule 13.12.2013


Ответы (1)


Сдача :

private volatile boolean mCancelled; //otherwise the myTask thread may never stop

Для вашей проблемы:

if (!serviceStarted) { 
    serviceStarted = true;
    myTask = new MyTask(this);
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
    sp.registerOnSharedPreferenceChangeListener(myTask); //err, you must register
    Thread t = new Thread(myTask); t.start();
}

документы :

Эти настройки будут автоматически сохраняться в SharedPreferences по мере взаимодействия с ними пользователя. Чтобы получить экземпляр SharedPreferences, который будет использоваться иерархией предпочтений в этом действии, вызовите getDefaultSharedPreferences(android.content.Context) с контекстом в том же пакете, что и это действие.

[выделено мной]

Редактировать: ваш второй фрагмент, вероятно, терпит неудачу, потому что вы получаете неправильные общие настройки - вы должны получить те, что по умолчанию - я думал, что это не удается из-за:

SharedPreferences.onSharedPreferenceChangeListener не вызывается последовательно

person Mr_and_Mrs_D    schedule 12.12.2013
comment
Зачем использовать SharedPreferences sp = ... в конструкции if? Разве это не должно быть добавлено в объект MyTask вместо этого? - person user504342; 13.12.2013
comment
Если внутри onStartCommand - очистить (без конструктора)? Теперь я делаю следующее: 1. получаю общие настройки по умолчанию. 2. скажите андроиду: «Привет, зарегистрируйте этот объект myTask, который я только что создал, чтобы отслеживать изменения настроек по умолчанию, которые я только что получил». Затем я продолжаю с вашим кодом. Таким образом, вы не добавляете sp в myTask — вы добавляете myTask в (слушатели) sp. Вы можете сделать это в конструкторе myTask как SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); sp.registerOnSharedPreferenceChangeListener(this);, но лучше сделать это в сервисе - person Mr_and_Mrs_D; 13.12.2013
comment
То, как вы это описываете, означает для меня, что я не могу прочитать значение измененного ключа/значения pref после его изменения и срабатывания. Мне нужно изменить настройку в объекте MyTask, а не в объекте MyService. Таким образом, каждая реализация OnSharedPreferenceChangeListener может иметь различную реализацию для каждого прослушивателя, который вы хотите добавить (см. редактирование моего решения). - person user504342; 13.12.2013