Страница 1 из 1

Правильная работа с SharedPreference в двух процессах

Добавлено: 15 май 2013, 13:32
Mikhail_dev
(Update 12.08.2013. Изменена логика работы с устройствами версии API>=11, всё таки флаг MODE_MULTI_PROCESS понадобился в полной мере).

Заметка касается работы с данными именно в одном приложении. (Если вам надо "расшарить" настройки на другие приложения, то вам наверное подойдет ContentProvider, но тема не про это)
1. Чего бы хотелось.
Думаю многие согласятся, что чем больше приложение, тем больше настроек оно может использовать в разных местах программы. Для этого нам может пригодиться единый класс управления настройками, будь он статический, либо singleton. Отлично, давайте возьмем singleton, будем обновлять настройки, после чего через геттеры и сеттеры использовать значения. Есть одна проблема.
2. Подводные камни.
Dalvik машина запускает каждый процесс (не путать с потоком) в отдельной своей копии, и как следствие мы получаем не один объект синглтона, а два (если пользуемся настройками с двух процессов). Это очень плохо. И это нужно понимать. С потоками всё в порядке. В рамках одного процесса все его потоки используют один экземпляр (один объект синглтона).
Да, но причем тут собственно настройки, если они сохраняются на носитель сразу? Ведь без разницы должно быть, записываем на носитель и читаем с любого процесса тот файл, что с настройками. И да, это действительно работало. Если кому интересно, вот информация о том, когда это работает, а когда нет.
Кто не силён в английском, я поясню всю тяжесть проблемы при работе с двумя процессами.
- До версии 2.3 это прекрасно работало.
- Вресия 2.3 - не работает.
- Версия >2.3 - надо использовать флаг MODE_MULTI_PROCESS
На вопрос "почему?", скажу что Google скорее всего с версии 2.3 начал использовать кеширование в память настроек и не сразу сохранять настройки. А это, как я уже указал, в двух процессах разные объекты. В итоге в одном процессе меняем - в другом читаем старые значения.
Но во всей это истории я не понял гугла. Ну сделали они кеширование с API 9, но почему флаг MODE_MULTI_PROCESS появился только с 11? Они полностью проигнорировали 2.3 линейку, которая на март 2013 составляет 38 процентов от всех пользователей!
3. Как обойти.
А всё гениальное просто. Сделайте commit настройкам после изменения (смотреть второй кусок кода!). Вот тут нашел ответ на данный вопрос. Немного кода:
[syntax=java]
/* Класс - менеджер для работы с данными настроек */
public class Preferences {
private static final Context c = App.getInstance().getApplicationContext();
public static final String NAVI_ALTERNATIVE_ROUTES_KEY = c.getString(R.string.navi_alternative_routes_key);
public static final String NAVI_RECALCULATION_ROUTE_KEY = c.getString(R.string.navi_recalculation_route_key);
private SharedPreferences mPreferences;
private boolean useAlternateRoutes;
private int alternateRouteDistance;

private Preferences() { }

public static synchronized Preferences getInstance() {
if (instance==null) {
instance = new Preferences();
}
return instance;
}

/* Обновляет переменные класса в соответствии с выбранными настройками */
public synchronized void updatePreferences() {
if (Build.VERSION.SDK_INT < 11) {
this.mPreferences = PreferenceManager.getDefaultSharedPreferences(c);
} else {
this.mPreferences = c.getSharedPreferences(c.getPackageName() + "_preferences",Context.MODE_MULTI_PROCESS);
}
useAlternateRoutes = mPreferences.getBoolean(NAVI_ALTERNATIVE_ROUTES_KEY, false);
alternateRouteDistance = Integer.parseInt(mPreferences.getString(NAVI_RECALCULATION_ROUTE_KEY, "100"));
}

public int getAlternateRouteDistance() {
return alternateRouteDistance;
}

public boolean isUseAlternateRoutes() {
return useAlternateRoutes;
}
}
[/syntax]
Как-то так, урезанно, выглядит singleton у нас. Думаю тут пояснять нечего. Скажу лишь что названия ключей я беру с ресурсов. Это по желанию. Таким способом просто надо будет в дальнейшем менять только ресурс. Ах да, App класс - это вспомогательный класс, унаследованный от Application, через который мы получаем applicationContext. Вам решать, как получить доступ к ресурсам. Можете, как уже сказал, работать через обычные строки.
А теперь непосредственно к тому, где делать коммит настроек, потому как если это сделать в конце метода updatePreferences, то проблема не уходит.
[syntax=java]
public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences); //ссылка на ваши настройки
}

@Override
protected void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}

@Override
protected void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}


/**
* Раcсылает Broadcast, уведомляющий о изменении настроек.
* Содержит в себе ключ, который изменился
*/
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
//тут берем измененный sharedPreference и делаем этим настройкам коммит
sharedPreferences.edit().commit();
//а после бросаем бродкаст с уведомлением о том, что настройки изменились.
Intent intent = new Intent("settings have changed");
intent.putExtra("key", key);
sendBroadcast(intent);
}
}
[/syntax]
Создаем новую активность, которая наследуется от PreferenceActivity и регистрирует слушатель OnSharedPreferenceChangeListener. В метод onSharedPreferenceChanged будет передаваться тот самый объект настроек, который изменился. Берем его и делаем commit, после чего бросаем Broadcast с уведомлением о том, что настройки изменились. Ну а после, как поймали Broadcast, делаем Preferences.getInstance().updatePreferences();

Re: Правильная работа с SharedPreference в двух процессах

Добавлено: 02 июл 2013, 05:14
klblk
Хорошая статья.
Единственное в тексте говориться о методе updateSettings() (причем в одном случае "S" заглавная, а в другом прописная), в то время как в классе Preferences метод называется updatePreferences(), и еще объявления instance не вижу.

Re: Правильная работа с SharedPreference в двух процессах

Добавлено: 02 июл 2013, 07:24
Mikhail_dev
Благодарю за указанные ошибки. Действительно, updatePreferences(), а не updateSettings().

Re: Правильная работа с SharedPreference в двух процессах

Добавлено: 12 авг 2013, 22:26
Mikhail_dev
Статья обновлена. С третьего андроида всё таки необходимо использовать флаг MODE_MULTI_PROCESS, поэтому немного изменен код.

Re: Правильная работа с SharedPreference в двух процессах

Добавлено: 08 дек 2013, 23:57
w201
Кстати было бы неплохо отметить, что singleton вещь практически не реализуемая в андроиде. Единственный возможный вариант - через Application.
Вот тут хорошо написано.

Re: Правильная работа с SharedPreference в двух процессах

Добавлено: 10 дек 2013, 19:49
Mikhail_dev
Application тоже не может быть сингтоном в полной мере. У нас в проекте два процесса, что влечет за собой два объекта Application, и да, это действительно проблема...