AsyncTask内でSharedPreferencesのcommitを呼ぶ時の注意点


仕事で作っているアプリで起きた問題を再現させるためにサンプル作った。
イマイチ処理をトレースできていない部分もあり、もしかしたら原因は違うかもしれないが、実際にサンプル作って再現を確認。

###AsyncTask.cancel(true)を実行するとEditor.commit()の戻り値はfalseでも書き込みは成功してる

コードの様にAsyncTaskのdoInBackground上でSharedPreferencesを使用中にAsyncTaskのcancel(true)が呼ばれると,
メソッド内でExceptionが吐かれSharedPreferencesのcommitメソッドがそれをキャッチしfalseを返してしまう。

Editor.commitメソッドの中はディスク書き込み処理を非同期で走らせた後、終了するまで処理を待機している。
この待機による例外と AsyncTask.cancel(true)で吐かれる例外が同じなためこのような動きになっていると推測。

* startButtonを押下するとSharedPreferencesに保存された数値をインクリメントし再保存する。
* stopButtonを押下するとAsyncTaskを止めるためにcancel(true)が呼ばれる。
* startButtonを押下後すぐにstopButtonを押下すると再現する。
* commit()がfalseを返すと失敗しているはずだが、失敗後の値は保存開始時の値+1になっている。


```java:MainActivity.java
public class MainActivity extends Activity implements View.OnClickListener {

    protected static final String PREFERENCES_NAME = "shared";

    protected static final String TAG = MainActivity.class.getSimpleName();

    class Pref {
        public static final String Name = "name";
    }

    class Task extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... params) {
            Log.d(TAG, "タスク実行");

            int j = 0;
            for (int i = 0; i < 10000000; i++) {
                j += i;
            }

            SharedPreferences sp = getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
            int i = sp.getInt(Pref.Name, 0);

            Editor edit = sp.edit();
            edit.putInt(Pref.Name, i + 1);
            Log.d(TAG, "保存開始 値：" + i);
            if (!edit.commit()) {
                Log.d(TAG, "保存失敗");

                int after = sp.getInt(Pref.Name, 0);
                Log.d(TAG, "失敗後の値:" + after);

                throw new IllegalAccessError("保存失敗");
            }

            int after = sp.getInt(Pref.Name, 0);
            Log.d(TAG, "保存終了 値:" + after);
            return null;
        }
    };

    Task task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.startButton) {
            task = new Task();
            task.execute();
        } else if (v.getId() == R.id.stopButton) {
            if (task != null) {
                task.cancel(true);
                task = null;
                Log.d(TAG, "タスク停止");
            }
        }
    }
}
```

```xml:activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <Button
        android:id="@+id/startButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView1"
        android:layout_below="@+id/textView1"
        android:layout_marginLeft="27dp"
        android:layout_marginTop="48dp"
        android:onClick="onClick"
        android:text="Start" />

    <Button
        android:id="@+id/stopButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/startButton"
        android:layout_alignBottom="@+id/startButton"
        android:layout_alignParentRight="true"
        android:layout_marginRight="38dp"
        android:onClick="onClick"
        android:text="Stop" />

</RelativeLayout>
```SharedPreferencesのcommitの戻り値をチェックする人自体少ないと思うが、
**ビジネスアプリを作ってる人の中には、会社のガイドラインにストレージ不足状況下で保存失敗をユーザに通知しなければいけない。**
といったテストも存在するのではないだろうか。