Last active
August 29, 2015 14:20
-
-
Save esmasui/ba1a1ac035870329ed4a to your computer and use it in GitHub Desktop.
AndroidのInstanceStateの保存・復元をアノテーションでやる再発明
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2015 uPhyca Inc. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.uphyca.android; | |
import android.app.Activity; | |
import android.net.Uri; | |
import android.os.Bundle; | |
import jp.co.oneteam.phonebook.ui.InstanceState; | |
import jp.co.oneteam.phonebook.ui.InstanceStateAnnotations; | |
public class ExampleActivity extends Activity{ | |
@InstanceState | |
Uri exmpleUri; | |
@Override | |
public void onSaveInstanceState(Bundle outState) { | |
super.onSaveInstanceState(outState); | |
InstanceStateAnnotations.saveInstanceState(this, outState); | |
} | |
@Override | |
protected void onRestoreInstanceState(Bundle savedInstanceState) { | |
super.onRestoreInstanceState(savedInstanceState); | |
InstanceStateAnnotations.restoreInstanceState(this, savedInstanceState); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2015 uPhyca Inc. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.uphyca.android; | |
import android.os.Bundle; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
/** | |
* InstanceStateとして状態の保存・復元の対象となるフィールドに注釈する。 | |
* InstanceStateの保存の際に {@link InstanceStateAnnotations#saveInstanceState(Object, Bundle)}、復元の際に {@link InstanceStateAnnotations#restoreInstanceState(Object, Bundle)} を呼び出すことで注釈したフィールドの保存・復元が行われる。 | |
* | |
* @see InstanceStateAnnotations | |
*/ | |
@Target(ElementType.FIELD) | |
@Retention(RUNTIME) | |
public @interface InstanceState { | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2015 uPhyca Inc. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package jp.co.oneteam.phonebook.ui; | |
import android.os.Bundle; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
/** | |
* InstanceStateの保存・復元を行うユーティリティクラス | |
* | |
* @see InstanceState | |
* @see android.app.Activity#onSaveInstanceState(Bundle) | |
* @see android.app.Activity#onRestoreInstanceState(Bundle) | |
*/ | |
public abstract class InstanceStateAnnotations { | |
private static class MetaInfo { | |
final Field field; | |
final Method putMethod; | |
final Method getMethod; | |
public MetaInfo(Field field, Method putMethod, Method getMethod) { | |
this.field = field; | |
this.putMethod = putMethod; | |
this.getMethod = getMethod; | |
} | |
} | |
private static final Map<Class<?>, List<MetaInfo>> metaInfoListMap = new HashMap<>(); | |
private InstanceStateAnnotations() { | |
throw new UnsupportedOperationException("No instances"); | |
} | |
public static void saveInstanceState(Object target, Bundle outState) { | |
Bundle myState = new Bundle(); | |
final Class<?> clazz = target.getClass(); | |
for (MetaInfo each : getMetaInfoList(clazz)) { | |
try { | |
each.putMethod.invoke(myState, each.field.getName(), each.field.get(target)); | |
} catch (IllegalAccessException e) { | |
throw new IllegalStateException(e); | |
} catch (InvocationTargetException e) { | |
throw new IllegalStateException(e); | |
} | |
} | |
outState.putBundle(clazz.getCanonicalName(), myState); | |
} | |
public static void restoreInstanceState(Object target, Bundle savedInstanceState) { | |
if (savedInstanceState == null) { | |
return; | |
} | |
final Class<?> clazz = target.getClass(); | |
Bundle myState = savedInstanceState.getBundle(clazz.getCanonicalName()); | |
if (myState == null) { | |
return; | |
} | |
for (MetaInfo each : getMetaInfoList(clazz)) { | |
try { | |
each.field.set(target, each.getMethod.invoke(myState, each.field.getName())); | |
} catch (IllegalAccessException e) { | |
throw new IllegalStateException(e); | |
} catch (InvocationTargetException e) { | |
throw new IllegalStateException(e); | |
} | |
} | |
} | |
private static List<MetaInfo> getMetaInfoList(Class<?> clazz) { | |
List<MetaInfo> metaInfoList = metaInfoListMap.get(clazz); | |
if (metaInfoList != null) { | |
return metaInfoList; | |
} | |
metaInfoList = buildMetaInfoList(clazz); | |
metaInfoListMap.put(clazz, metaInfoList); | |
return metaInfoList; | |
} | |
private static List<MetaInfo> buildMetaInfoList(Class<?> clazz) { | |
List<MetaInfo> metaInfoList = new ArrayList<>(); | |
for (Field each : clazz.getDeclaredFields()) { | |
if (!each.isAnnotationPresent(InstanceState.class)) { | |
continue; | |
} | |
final Method putMethod = findPutMethod(each.getType()); | |
if (putMethod == null) { | |
throw new IllegalStateException("No put method found for " + each.getType().getName()); | |
} | |
final Method getMethod = findGetMethod(each.getType()); | |
if (getMethod == null) { | |
throw new IllegalStateException("No get method found for " + each.getType().getName()); | |
} | |
metaInfoList.add(new MetaInfo(each, putMethod, getMethod)); | |
} | |
return metaInfoList; | |
} | |
private static Method findPutMethod(Class<?> type) { | |
for (Method each : Bundle.class.getMethods()) { | |
if (!each.getName().startsWith("put")) { | |
continue; | |
} | |
final Class<?>[] parameterTypes = each.getParameterTypes(); | |
if (parameterTypes.length != 2) { | |
continue; | |
} | |
if (!parameterTypes[0].equals(String.class)) { | |
continue; | |
} | |
if (!parameterTypes[1].isAssignableFrom(type)) { | |
continue; | |
} | |
return each; | |
} | |
return null; | |
} | |
private static Method findGetMethod(Class<?> type) { | |
for (Method each : Bundle.class.getMethods()) { | |
if (!each.getName().startsWith("get")) { | |
continue; | |
} | |
final Class<?>[] parameterTypes = each.getParameterTypes(); | |
if (parameterTypes.length != 1) { | |
continue; | |
} | |
if (!parameterTypes[0].equals(String.class)) { | |
continue; | |
} | |
final Class<?> returnType = each.getReturnType(); | |
if (!returnType.isAssignableFrom(type)) { | |
continue; | |
} | |
return each; | |
} | |
return null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2015 uPhyca Inc. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.uphyca.android; | |
import android.content.Intent; | |
import android.net.Uri; | |
import android.os.Bundle; | |
import android.support.test.runner.AndroidJUnit4; | |
import org.junit.After; | |
import org.junit.Before; | |
import org.junit.Test; | |
import org.junit.runner.RunWith; | |
import static org.assertj.core.api.Assertions.assertThat; | |
/** | |
* @see InstanceStateAnnotations | |
*/ | |
@RunWith(AndroidJUnit4.class) | |
public class InstanceStateAnnotationsTest { | |
static class TestTarget { | |
@InstanceState | |
String stringField; | |
@InstanceState | |
int intField; | |
@InstanceState | |
Long longWrapperField; | |
@InstanceState | |
Uri uriField; | |
@InstanceState | |
Intent intentField; | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
TestTarget target = (TestTarget) o; | |
if (intField != target.intField) return false; | |
if (stringField != null ? !stringField.equals(target.stringField) : target.stringField != null) | |
return false; | |
if (longWrapperField != null ? !longWrapperField.equals(target.longWrapperField) : target.longWrapperField != null) | |
return false; | |
if (uriField != null ? !uriField.equals(target.uriField) : target.uriField != null) | |
return false; | |
return !(intentField != null ? !intentField.equals(target.intentField) : target.intentField != null); | |
} | |
@Override | |
public int hashCode() { | |
int result = stringField != null ? stringField.hashCode() : 0; | |
result = 31 * result + intField; | |
result = 31 * result + (longWrapperField != null ? longWrapperField.hashCode() : 0); | |
result = 31 * result + (uriField != null ? uriField.hashCode() : 0); | |
result = 31 * result + (intentField != null ? intentField.hashCode() : 0); | |
return result; | |
} | |
} | |
@Before | |
public void setUp() throws Exception { | |
} | |
@After | |
public void tearDown() throws Exception { | |
} | |
@Test | |
public void saveAndRestoreInstanceState() throws Exception { | |
TestTarget target = new TestTarget(); | |
target.stringField = "string"; | |
target.intField = Integer.MAX_VALUE; | |
target.longWrapperField = Long.MAX_VALUE; | |
target.uriField = Uri.parse("http://example.com"); | |
target.intentField = new Intent(Intent.ACTION_VIEW, Uri.parse("http://example.com")); | |
Bundle outState = new Bundle(); | |
InstanceStateAnnotations.saveInstanceState(target, outState); | |
final TestTarget restoreTarget = new TestTarget(); | |
InstanceStateAnnotations.restoreInstanceState(restoreTarget, outState); | |
assertThat(restoreTarget).isEqualTo(target); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment