9692


Activityにボタンがあり、そのボタンを押すとギャラリーが開いて画像の読み込みをします。  
この時、画面回転を行ってもギャラリーから選択した画像が保持されるようにしようとしています。

以下のコードでギャラリーから画像を選択すると、選択した画像がUriImageViewに表示されます。その後で画面回転を行うと選択した画像が保持されていました。

しかし、開発者オプションで「Activityを保持しない」オプションを有効にすると、以下のエラーが発生してアプリが落ちます。

エラー

[code]

    04-26 21:30:43.793  22479-22479/? E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: jp.gcreate.sample.savestatecustomview, PID: 22479
    java.lang.RuntimeException: Unable to start activity ComponentInfo{jp.gcreate.sample.savestatecustomview/jp.gcreate.sample.savestatecustomview.MainActivity2Activity}: java.lang.RuntimeException: Parcel android.os.Parcel@18c09797: Unmarshalling unknown type code 2131296303 at offset 264
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2325)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
            at android.app.ActivityThread.access$800(ActivityThread.java:151)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5254)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
     Caused by: java.lang.RuntimeException: Parcel android.os.Parcel@18c09797: Unmarshalling unknown type code 2131296303 at offset 264
            at android.os.Parcel.readValue(Parcel.java:2228)
            at android.os.Parcel.readSparseArrayInternal(Parcel.java:2546)
            at android.os.Parcel.readSparseArray(Parcel.java:1874)
            at android.os.Parcel.readValue(Parcel.java:2209)
            at android.os.Parcel.readArrayMapInternal(Parcel.java:2485)
            at android.os.BaseBundle.unparcel(BaseBundle.java:221)
            at android.os.Bundle.getSparseParcelableArray(Bundle.java:822)
            at com.android.internal.policy.impl.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2006)
            at android.app.Activity.onRestoreInstanceState(Activity.java:1023)
            at android.app.Activity.performRestoreInstanceState(Activity.java:978)
            at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1162)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
            at android.app.ActivityThread.access$800(ActivityThread.java:151)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5254)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
    
[/code]

デバッグで確認すると、ギャラリーで画像を選択した後にActivityのonCreateで渡されるBundleの値が`Bundle[mParcelledData.dataSize=760]`となっていました。  
CustomViewのonSaveInstanceStateがうまく動作していないのかと思い、UriImageViewのonSaveInstanceStateにブレークポイントを置いてステップ実行を行って確認してみました。  
Bundleの値は`Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@33173215,
2131296291=jp.gcreate.sample.savestatecustomview.UriImageView$ImageState@8c95d9e,
2131296303=android.view.AbsSavedState$1@33173215,
2131296304=android.view.AbsSavedState$1@33173215,
2131296305=android.support.v7.widget.Toolbar$SavedState@303f5002,
2131296306=android.view.AbsSavedState$1@33173215,
2131296320=android.view.AbsSavedState$1@33173215}}]}]`となっており、アプリは落ちることなく動作しました。  
ステップ実行するとうまく動作する理由がよく分からず困惑しています。  
そもそもどこかのコードが根本的に間違っているのかもしれません。  
どうやれば「Activityを保持しない」が有効な状態でも、カスタムビューの状態を保存できるでしょうか？  
どこを直したら上手く動くようになるのか手助けしていただけるとありがたいです。

Activity

[code]

    public class MainActivity2Activity extends ActionBarActivity {
        private Button mButton;
        private UriImageView mImageView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main_activity2);
            mButton = (Button) findViewById(R.id.button);
            mImageView = (UriImageView) findViewById(R.id.image);
        }
    
        @Override
        public void onResume() {
            super.onResume();
            mButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent();
                    intent.setAction(Intent.ACTION_GET_CONTENT)
                            .setType("image/*");
                    startActivityForResult(intent, 1);
                }
            });
        }
    
        @Override
        public void onPause() {
            super.onPause();
            mButton.setOnClickListener(null);
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if(requestCode == 1 && resultCode == Activity.RESULT_OK){
                Uri uri = data.getData();
                mImageView.setUri(uri);
            }
        }
    }
    
[/code]

UriImageView

[code]

    public class UriImageView extends ImageView{
        private Uri mUri;
    
        public UriImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
            setImage();
        }
    
        private void setImage() {
            if(mUri == null){
                setImageDrawable(getContext().getResources().getDrawable(android.R.drawable.btn_star, getContext().getTheme()));
            }else{
                setImageURI(mUri);
            }
        }
    
        public void setUri(Uri uri) {
            mUri = uri;
            setImage();
        }
    
        @Override
        protected Parcelable onSaveInstanceState() {
            Parcelable superState = super.onSaveInstanceState();
            ImageState imageState = new ImageState(superState);
            imageState.savedUri = mUri;
            return imageState;
        }
    
        @Override
        protected void onRestoreInstanceState(Parcelable state) {
            ImageState imageState = (ImageState) state;
            super.onRestoreInstanceState(imageState.getSuperState());
            setUri(imageState.savedUri);
            requestLayout();
        }
    
        static class ImageState extends BaseSavedState{
            public static final Parcelable.Creator<ImageState> CREATER = new Parcelable.Creator<ImageState>(){
    
                @Override
                public ImageState createFromParcel(Parcel source) {
                    return new ImageState(source);
                }
    
                @Override
                public ImageState[] newArray(int size) {
                    return new ImageState[size];
                }
            };
            Uri savedUri;
    
            public ImageState(Parcel source) {
                super(source);
                savedUri = source.readParcelable(Uri.class.getClassLoader());
            }
    
            public ImageState(final Parcelable superState) {
                super(superState);
            }
    
            @Override
            public void writeToParcel(@NonNull Parcel dest, int flags) {
                super.writeToParcel(dest, flags);
                dest.writeParcelable(savedUri, flags);
            }
        }
    }
    
[/code]


CC BY-SA 3.0
2015-04-26T13:08:00.237
9571
2015-04-30T10:31:35.727
9498
question
android
Activityを保持しないを有効にした時にCustomViewでの状態保存がうまく動作しない


`ImageState`の`CREATER`がスペル間違いのため起きていると例外が出ているのだと思います。  
`Parcel`クラスの`readParcelableCreator`メソッド内で下記のようにリフレクションで名前を指定しているので`CREATOR`とする必要があります。

[code]

    Field f = c.getField("CREATOR");
    
[/code]


CC BY-SA 3.0
2015-04-30T10:31:35.727
9692
2015-04-30T10:31:35.727
8216
9571
answer