android Annotation을 사용하면 많은 코드를 줄일 수 있고 이를 위해 다양한 라이브러리가 준비되어 있다. 하지만 내가 필요한 건 ViewById나 Click 정도 뿐이라서 직접 구현하는게 낫겠다 싶다.
ViewById.java
package com.mdiwebma.base.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author djkim
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
int[] value() default -1;
boolean click() default false;
boolean longClick() default false;
}
Click.java
package com.mdiwebma.base.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author djkim
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Click {
int[] value() default -1;
}
LongClick.java
package com.mdiwebma.base.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author djkim
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LongClick {
int[] value() default -1;
}
InjectionUtils.java
package com.mdiwebma.base.utils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import com.mdiwebma.base.BuildConfig;
import com.mdiwebma.base.annotation.Click;
import com.mdiwebma.base.annotation.Extra;
import com.mdiwebma.base.annotation.LongClick;
import com.mdiwebma.base.annotation.ViewById;
/**
* @author djkim
*/
public class InjectionUtils {
private static final String TAG = "InjectionUtils";
public static void injectAll(Activity activity) {
injectViewById(activity, activity.getWindow().getDecorView());
injectClick(activity, activity.getWindow().getDecorView());
}
public static void injectViewById(Object container, View layout) {
View view = null;
Field[] fields = container.getClass().getDeclaredFields();
for (Field field : fields) {
ViewById param = field.getAnnotation(ViewById.class);
if (param != null) {
int[] id = param.value();
if (id.length > 0) {
view = layout.findViewById(id[0]);
if (view == null) {
if (BuildConfig.DEBUG) {
Log.e(TAG, "@ViewById warning! findViewById() return null.. member=" + field.getType().getSimpleName() + " " + field.getName());
}
continue;
}
try {
field.setAccessible(true);
field.set(container, view);
} catch (Exception ex) {
throw new RuntimeException("@ViewById error.. member=" + field.getType().getSimpleName() + " " + field.getName() + " but actually=" + view.getClass().getSimpleName(), ex);
}
if (param.click()) {
view.setOnClickListener((View.OnClickListener)container);
}
if (param.longClick()) {
view.setOnLongClickListener((View.OnLongClickListener)container);
}
}
}
}
}
public static void injectClick(final Object container, final View layout) {
Class clazz = container.getClass();
for (final Method method : clazz.getDeclaredMethods()) {
Click click = method.getAnnotation(Click.class);
if (click != null) {
if (Modifier.isPublic(method.getModifiers()) == false) {
throw new AssertionError("@Click Method modifier not public.. method=" + method.getName());
}
Class<?>[] params = method.getParameterTypes();
final boolean hasViewParameter = (params.length == 1);
if (params.length > 1) {
throw new AssertionError("@Click Too many method parameters error.. method=" + method.getName());
} else if (params.length == 1 && params[0] != View.class) {
throw new AssertionError("@Click Method parameter type is not View type.. method=" + method.getName());
}
int[] ids = click.value();
for (int id : ids) {
View view = layout.findViewById(id);
if (view == null) {
if (BuildConfig.DEBUG) {
Log.e(TAG, "@Click findViewById() return null.. method=" + method.getName());
}
continue;
}
view.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
try {
if (hasViewParameter) {
method.invoke(container, view);
} else {
method.invoke(container);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
});
}
} // click
LongClick longClick = method.getAnnotation(LongClick.class);
if (longClick != null) {
if (Modifier.isPublic(method.getModifiers()) == false) {
throw new AssertionError("@LongClick Method modifier not public.. method=" + method.getName());
}
Class<?>[] params = method.getParameterTypes();
final boolean hasViewParameter = (params.length == 1);
if (params.length > 1) {
throw new AssertionError("@LongClick Too many method parameters error.. method=" + method.getName());
} else if (params.length == 1 && params[0] != View.class) {
throw new AssertionError("@LongClick Method parameter type is not View type.. method=" + method.getName());
}
int[] ids = longClick.value();
for (int id : ids) {
View view = layout.findViewById(id);
if (view == null) {
if (BuildConfig.DEBUG) {
Log.e(TAG, "@LongClick findViewById() return null.. method=" + method.getName());
}
continue;
}
view.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) {
try {
if (hasViewParameter) {
method.invoke(container, view);
} else {
method.invoke(container);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
return true;
}
});
}
} // long click
}
}
public static void toIntentBundle(Intent intent, Object source, Class target) {
HashMap<String, Class> extraTypes = new HashMap<>();
Field[] fields = target.getDeclaredFields();
for (Field field : fields) {
Extra param = field.getAnnotation(Extra.class);
if (param != null) {
String key = param.value();
if (key.length() > 0) {
if (extraTypes.containsKey(key)) {
throw new AssertionError("@Extra(toIntentBundle) key duplicate=" + key);
}
extraTypes.put(key, field.getType());
}
}
}
fields = source.getClass().getDeclaredFields();
for (Field field : fields) {
Extra param = field.getAnnotation(Extra.class);
if (param != null) {
String key = param.value();
if (key.length() > 0) {
if (extraTypes.containsKey(key)) {
Class targetKlass = extraTypes.get(key);
Class sourceKlass = field.getType();
if (targetKlass != sourceKlass) {
throw new AssertionError("@Extra(toIntentBundle) key type mismatch=" + key);
}
try {
field.setAccessible(true);
Object data = field.get(source);
if (sourceKlass == int.class || sourceKlass == Integer.class) {
intent.putExtra(key, (int)data);
} else if (sourceKlass == String.class) {
intent.putExtra(key, (String)data);
} else if (sourceKlass == long.class || sourceKlass == Long.class) {
intent.putExtra(key, (long)data);
} else if (sourceKlass == boolean.class || sourceKlass == Boolean.class) {
intent.putExtra(key, (boolean)data);
} else if (sourceKlass == double.class || sourceKlass == Double.class) {
intent.putExtra(key, (double)data);
} else if (sourceKlass == float.class || sourceKlass == Float.class) {
intent.putExtra(key, (float)data);
} else if (sourceKlass == short.class || sourceKlass == Short.class) {
intent.putExtra(key, (short)data);
} else {
throw new AssertionError("@Extra(toIntentBundle) unsupported key type=" + key);
}
} catch (Exception ex) {
throw new RuntimeException("@Extra(toIntentBundle) error.. key=" + key);
}
}
}
}
}
}
public static void fromIntentBundle(Intent intent, Object source) {
if (intent == null) {
return;
}
Field[] fields = source.getClass().getDeclaredFields();
for (Field field : fields) {
Extra param = field.getAnnotation(Extra.class);
if (param != null) {
String key = param.value();
if (key.length() > 0) {
if (intent.hasExtra(key)) {
Class sourceKlass = field.getType();
try {
field.setAccessible(true);
if (sourceKlass == int.class || sourceKlass == Integer.class) {
field.setInt(source, intent.getIntExtra(key, 0));
} else if (sourceKlass == String.class) {
field.set(source, intent.getStringExtra(key));
} else if (sourceKlass == long.class || sourceKlass == Long.class) {
field.set(source, intent.getLongExtra(key, 0));
} else if (sourceKlass == boolean.class || sourceKlass == Boolean.class) {
field.set(source, intent.getBooleanExtra(key, false));
} else if (sourceKlass == double.class || sourceKlass == Double.class) {
field.set(source, intent.getDoubleExtra(key, 0));
} else if (sourceKlass == float.class || sourceKlass == Float.class) {
field.set(source, intent.getFloatExtra(key, 0));
} else if (sourceKlass == short.class || sourceKlass == Short.class) {
field.set(source, intent.getShortExtra(key, (short)0));
} else {
throw new AssertionError("@Extra(fromIntentBundle) unsupported key type=" + key);
}
} catch (Exception ex) {
throw new RuntimeException("@Extra(fromIntentBundle) error.. key=" + key);
}
}
}
}
}
}
}
annotation class는 progurard에서 제외해야 하므로 설정파일에 아래를 추가한다.
progurad-project.txt
# inject annotation
-keep class com.mdiwebma.base.annotation.*
-keepclassmembers class * {
@com.mdiwebma.base.annotation.* *;
}
사용법은 http://androidannotations.org/ 여길 참조하시길.. 내가 만든건 자동으로 injection하지 않기 때문에 InjectionUtils의 static 메쏘드를 적절하게 호출해주면 끝.