问题
最近遇到了一个很有趣的问题,为什么不能够用回调的方式使用startActivityForResult呢?如果我们想要用回调的方式使用,有什么问题?
首先我们看一下目前官方的使用方式,如下图所示
其实这个流程很复杂,很不符合高内聚的原则,特别是如果页面的请求很多就会变成如下的情况
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case 1:
break;
case 2:
break;
case 3:
break;
.....
}
}
如果我们想要实现成回调的方式呢?
回调实现
参考这篇博客,https://juejin.im/entry/5b9a6e3cf265da0adc18b6f7
有几种实现方式:
- 使用一个代理类,帮忙处理startActivityForResult和onActivityResult,本质上和原生使用方式没有区别,还是必须要在Activity里的onActivityResult
里面手动的调用; - 使用反射或者AOP,修改onActivityResult的流程,但稳定性和兼容性较差,而且可能会和其他的框架产生冲突;
- 比较推荐的就是这个方法,在Activity里面增加一个空的fragment,通过这个fragment发起请求和接收结果,然后将收到的结果用回调函数传递给Activity。
回调的问题
那么为什么官方不设计成回调的方式呢?像下面这样子,回调的方式有啥问题
startActivityForResult(intent, new CallBack() {
@Override
public void onActivityResult(int resultCode, Intent data) {
}
});
匿名内部类的构造函数
在上述的例子当中,callback是一个匿名内部类,我们都知道匿名内部类会持有外部类的引用,那这个引用是何时传入的呢?
我们通过反射来查看以下匿名内部类的构造函数
private void reflect(Object callback){
Class cl = callback.getClass();
//构造函数
Constructor[] declaredConstructors = cl.getDeclaredConstructors();
for (Constructor constructor:declaredConstructors){
Log.i("构造函数",constructor.toString());
}
}
通过打印可以看到构造函数如下
也就是编译器在编译的时候替我们生成了一个构造函数,并且将对应的activity当作参数传入。
Activity被销毁的场景
若我们考虑下面这种情况,当A使用回调的方式跳转到B,此时由于某种原因A被销毁了,然后当B执行完成返回结果,系统会重新创建A1,而callback里面持有的是A引用,并不会对A1产生作用,这显然不是我们想要的结果。
当activity被重建,我们可以通过反射,将新的activity重新set进去,这样callback引用的就是重建后的新的activity了。
Class cl = callback.getClass();
Field[] fields = cl.getDeclaredFields();
for(Field f : fields) {
try {
f.setAccessible(true); // 设置些属性是可以访问的
String type = f.getType().toString(); // 得到此属性的类型
String name = f.getName();// 得到属性的名称
if(type.equals(FirstActivity.class.toString())) {
f.set(callback, this.getActivity());
}
Log.i("字段信息", type + ", " + name);
} catch (Exception e) {
e.printStackTrace();
}
}
结论
在Activity当中增加一个空的fragment可以解决此问题,但对于Activity销毁和重建的场景则需额外处理,由于反射会损耗性能,
初步想法是增加Activity生命周期的感知能力,当感知到Activity有销毁重建的动作,则使用反射重新设置一下。