[android] SharedPreferences with multi process service

지금 진행 중인 프로젝트에서 신기한 증상이 있는데, service가 multi process가 아니면 최초 실행 시 딱 한 번 동작을 안하는 오류가 있다.

앱을 최초로 실행했거나, 폰을 껐다가 켜서 서비스가 재시작 됐을 때에 나타난다.

다른 건 문제가 없었는데, 유독 preference에 저장된 값이 공유가 안되는 문제가 나타났다.

 

먼저 서비스의 process에 :remote를 넣었다.

<service
  android:name="kr.mint.testdesignpattern.MainService"
  android:process=":remote" />

 

 

 

그리고 값을 저장할 기본이 되는 클래스를 mode_multi_process로 수정했다.

public class BasePreferenceHelper
{
  private SharedPreferences _sharedPreferences;
  
  
  protected BasePreferenceHelper(Context $context)
  {
    super();
    _sharedPreferences = $context.getSharedPreferences($context.getPackageName(), Context.MODE_MULTI_PROCESS);
  }
  
  
  private SharedPreferences.Editor editor()
  {
    return _sharedPreferences.edit();
  }
  
  
  protected void put(String $key, String $value)
  {
    editor().putString($key, $value).commit();
  }
  
  
  protected String get(String $key)
  {
    return _sharedPreferences.getString($key, null);
  }
  
  
  protected void put(String $key, boolean $value)
  {
    editor().putBoolean($key, $value).commit();
  }
  
  
  protected boolean get(String $key, boolean $default)
  {
    return _sharedPreferences.getBoolean($key, $default);
  }
  
  
  protected void put(String $key, int $value)
  {
    editor().putInt($key, $value).commit();
  }
  
  
  protected int get(String $key, int $default)
  {
    return _sharedPreferences.getInt($key, $default);
  }
  
  
  protected void put(String $key, Set<String> $set)
  {
    editor().putStringSet($key, $set).commit();
  }
  
  
  protected Set<String> getStringSet(String $key)
  {
    return _sharedPreferences.getStringSet($key, null);
  }
}

 

 

 

 

그 다음에 실제 코드에서 호출할 preference helper를 약간 바꿨다.

처음에는 singleton이었던 클래스가 이젠 이도저도 아닌게 돼버렸다….

public class TestPreferenceHelper extends BasePreferenceHelper
{
  private static final String PROPERTY_APP_VERSION = "appVersion";
  
  
  public static synchronized TestPreferenceHelper instance(Context $context)
  {
    return new TestPreferenceHelper($context);
  }
  
  
  public TestPreferenceHelper(Context $context)
  {
    super($context);
  }
  
  
  public void putAppVersion(int $appVersion)
  {
    put(PROPERTY_APP_VERSION, $appVersion);
  }
  
  
  public int appVersion()
  {
    return get(PROPERTY_APP_VERSION, 0);
  }
}

 

 

 

 

메인으로 돌아갈 서비스에서도 수정할 부분이 있다.

예전에는 전역으로 선언한 preference helper 변수를 사용했지만, 이젠 매번 새로운 인스턴스를 호출해야한다…

public class MainService extends Service
{
  @Override
  public void onCreate()
  {
    super.onCreate();
    
    IntentFilter filter = new IntentFilter();
    filter.addAction("asdf");
    registerReceiver(broadcastReceiver, filter);
  }
  
  
  @Override
  public void onDestroy()
  {
    super.onDestroy();
    unregisterReceiver(broadcastReceiver);
  }
  
  
  private void show()
  {
    Log.i("MainService.java | show", "222|" + TestPreferenceHelper.instance(getApplicationContext()).appVersion());
  }
  
  /**************************************************
   * broadcast receiver
   ***************************************************/
  private BroadcastReceiver broadcastReceiver = new BroadcastReceiver()
  {
    @Override
    public void onReceive(Context $context, Intent $intent)
    {
      String action = $intent.getAction();
      if ("asdf".equals(action))
      {
        Log.i("MainService.java | onReceive", "111|" + TestPreferenceHelper.instance(getApplicationContext()).appVersion());
        show();
      }
    }
  };
  
  
  /**************************************************
   * useless
   ***************************************************/
  @Override
  public IBinder onBind(Intent arg0)
  {
    return null;
  }
}

 

 

 

편의를 위해 메뉴 클릭하면 신호가 가게 했다.

여기에서도 매번 새로운 인스턴스를 호출해야한다.

public class MainActivity extends Activity
{
  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    startService(new Intent(getApplicationContext(), MainService.class));
  }
  
  
  @Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
  }
  
  
  @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    int id = item.getItemId();
    if (id == R.id.action_settings)
    {
      int appVersion = TestPreferenceHelper.instance(getApplicationContext()).appVersion();
      appVersion++;
      TestPreferenceHelper.instance(getApplicationContext()).putAppVersion(appVersion);
      Log.i("MainActivity.java | onOptionsItemSelected", "000|" + TestPreferenceHelper.instance(getApplicationContext()).appVersion() + "|");
      
      Intent intent = new Intent("asdf");
      sendBroadcast(intent);
    }
    return super.onOptionsItemSelected(item);
  }
}

 

 

MainActivity.java | onOptionsItemSelected: 000|159|
MainService.java | onReceive: 111|159
MainService.java | show: 222|159

 

평소 같았으면 이런 코드를 쓰는 건 견딜 수 없을 정도로 신경이 쓰이는데, 아직 다른 방법을 찾지 못했다…