在Android开发中,我们经常需要调用startActivityForResult
来启动带返回结果的Activity
界面,该方法需要一个int
类型的requestCode
参数,我们在Activity
的onActivityResult
方法中根据requestCode
来区分是来自哪个Activity
的返回结果。在实际项目中,一个项目经常包含十几个甚至几十个不同的requestCode
。最初我把这些code作为静态常量写在Activity
中,比如:
public class TestActivity extends Activity{
public static final int CHOOSE_IMAGE_CODE = 0x1;
public static final int CHOOSE_VIDEO_CODE = 0x2;
//...
}
但是这样在不同的Activity
可能出现相同数值的不同code,可能出现比较隐晦的bug。而且到处分散,取值的时候就得通过对应Activity来取,比如:XXXActivity.CHOOSE_IMAGE_CODE
,管理起来也不太方便。
后来我将其写在一个单独的RequestCode文件中,像下面这样:
public interface RequestCode {
int CHOOSE_IMAGE_CODE = 0x1;
int CHOOSE_VIDEO_CODE = 0x2;
//...
}
这样就干净很多,还不用写public static final
这类代码,而且所有code在一个文件中,就更容易发现是否有code重复,也更容易新增新的code。但是这种写法每新增一个都得去找一个新的数字,写在等号后面,万一手抖一下写错了也有可能。再进一步,我将其改为
public class CodeSupplier{
public static int code = 1;
public static int getNextCode(){
return code++;
}
}
public interface RequestCode {
int CHOOSE_IMAGE_CODE = CodeSupplier.getNextCode();
int CHOOSE_VIDEO_CODE = CodeSupplier.getNextCode();
//...
}
但是,这样每次都要写一遍CodeSupplier.getNextCode()
也同样虐心,可不可以不写呢?本文就介绍一种100行代码不到的利用动态代理实现的解决方法。
1. 实现效果
我们的最终效果如下:
public interface RequestCode {
RequestCode codes = RequestCodeFactory.impl(RequestCode.class, 0x100);
int chooseImageCode();
int chooseImagesCode();
int chooseFileCode();
int chooseMusicCode();
int takePhotoCode();
}
我们只需要定义一个接口RequestCode
,在借口中设置一个常量(名称随便),值为RequestCodeFactory.impl(RequestCode.class)
方法的返回值(该方法第二个参数表示code从哪个整数开始递增,第三个参数表示到哪结束)。每次新增code只需要新增一个int xxx();
方法声明就可以了,使用的时候从那个常量中取对应code就行,例如:
System.out.println(RequestCode.codes.chooseFileCode());
System.out.println(RequestCode.codes.chooseImagesCode());
System.out.println(RequestCode.codes.chooseImageCode());
System.out.println(RequestCode.codes.takePhotoCode());
System.out.println(RequestCode.codes.chooseMusicCode());
改为静态import后,简化为:
System.out.println(codes.chooseFileCode());
System.out.println(codes.chooseImagesCode());
System.out.println(codes.chooseImageCode());
System.out.println(codes.takePhotoCode());
System.out.println(codes.chooseMusicCode());
2. RequestCodeFactory的实现
完整代码如下:
import androidx.annotation.IntRange;
import androidx.core.util.Supplier;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public final class RequestCodeFactory {
private static final HashMap<Integer, String> codeNameMap = new HashMap<>();
private RequestCodeFactory() {
}
/**
* 判断该code是否已经被使用
*
* @param code 要检查的code
* @return 如果已经被使用就返回 false,否则为 true
*/
public static boolean hasCreated(int code) {
return codeNameMap.containsKey(code);
}
/**
* 获取当前最大的 code
*
* @return 当前最大 code
*/
public static int maxCode() {
int max = 0;
for (Integer code : codeNameMap.keySet()) {
max = Math.max(code, max);
}
return max;
}
/**
* 获取 code 对应的可读名称
*
* @param code 目标code
* @return 对应的名称,以“类名.方法名”的形式,例如:"RequestCode.chooseImageCode"
*/
public static String getCodeName(int code) {
return codeNameMap.get(code);
}
/**
* 获取所有code和名称的映射表
*
* @return 对应映射表
*/
public static Map<Integer, String> getAllCodeNameMap() {
return Collections.unmodifiableMap(codeNameMap);
}
public static <T> T impl(Class<T> tClass) {
return impl(tClass, 1);
}
public static <T> T impl(Class<T> tClass, @IntRange(from = 1, to = 65535) int start) {
return impl(tClass, start, 65535);
}
public static <T> T impl(Class<T> tClass, @IntRange(from = 1, to = 65535) int start, @IntRange(from = 0, to = 65535) int end) {
if (tClass.isInterface()) {
HashMap<String, Integer> codeMap = new HashMap<>();
Supplier<Integer> codeSupplier = new Supplier<Integer>() {
int count = start;
@Override
public Integer get() {
count++;
if (count <= end) {
if (hasCreated(count)) {
throw new IllegalStateException(String.format("code [%d] has been used!", count));
} else {
return count;
}
} else {
throw new RuntimeException("count over flow!");
}
}
};
for (Method method : tClass.getMethods()) {
if ((method.getReturnType() == Integer.class || method.getReturnType() == int.class)
&& (method.getParameterTypes().length == 0)) {
Integer code = codeSupplier.get();
codeMap.put(method.getName(), code);
codeNameMap.put(code, tClass.getSimpleName() + "." + method.getName());
}
}
return (T) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{tClass}, (Object proxy, Method method, Object[] args) -> {
Integer integer = codeMap.get(method.getName());
if (integer != null) {
return integer;
} else {
throw new RuntimeException(String.format("not support calling method[%s]", method.getName()));
}
});
} else {
throw new IllegalArgumentException("This method only support interface");
}
}
}