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

MyTask.class:

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 е член на поле от тип boolean в 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: your solution - Не знаех, че искате да промените нещо във вашия myTask - но така или иначе, ако вашият myTask внедри OnSharedPreferenceChangeListener, където и да го регистрирате, щеше да е същото. Както и да е, внимавайте с многопоточността - опитайте се да я прочетете - много е сложно и нещата могат да се объркат по странни начини. От една страна, направете mCancelled непостоянен   -  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
If е вътре в onStartCommand - ясно (без конструктор)? Сега това, което правя, е 1. да извлека споделените предпочитания по подразбиране. 2. кажете на android хей, регистрирайте този myTask обект, който току-що създадох, за да слуша за промени в предпочитанията по подразбиране, които току-що получих. След това продължавам с вашия код. Така че вие ​​не добавяте sp към myTask - вие добавяте myTask към (слушателите на) sp. Можете да го направите в конструктора myTask като SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); sp.registerOnSharedPreferenceChangeListener(this);, но по-добре го направете в услугата - person Mr_and_Mrs_D; 13.12.2013
comment
Начинът, по който го описвате, означава за мен, че не мога да прочета стойността на променения преф ключ/стойност, след като бъде променен и задействан. Трябва да променя настройка в обекта MyTask, а не в обекта MyService. По този начин всяка реализация на OnSharedPreferenceChangeListener може да има различна реализация за всеки слушател, който искате да добавите (вижте моята редакция на решение). - person user504342; 13.12.2013