1 Activity的onActivityResult使用起来非常麻烦,为什么不设计成回调?
答:(1)onActivityResult的用法是:ActivityA调用startActivityForResult(intent, requestCode)启动了ActivityB;当ActivityB给ActivityA回复时调用setResult设置返回的结果,然后调用finish;最后在ActivityA的onActivityResult只不过处理B的返回结果;
(2)onActivityResult使用很麻烦,存在以下使用的问题:①代码处理逻辑分离,容易出现遗漏和不一致的地方;②写法不够直观,且结果数据没有类型安全保障;③当结果种类比较多时,onActivityResult会逐渐臃肿且难以维护;
(3)onActivityResult不能设计成回调。假设onActivityResult可以设计成回调,由于匿名内部类会持有外部类的引用,当ActivityA被意外销毁时,由于其对象被匿名内部类OnResultCallback持有,可能会导致其无法正常被GC。当ActivityB给ActivityA回复时,OnResultCallback被调用,新的ActivityA*被恢复,但是onResult方法持有的依旧是被销毁的ActivityA的引用,回调没有任何意义。
答:我觉得这是一个很有趣的问题,有趣的除了题目本身,也在于其引申出的感悟。我们在使用和学习Android Framework相关的内容时,除了要了解其内部实现机制,还要求自己要试着去反思,或者提问其设计的目的,这样才能帮助我们更好的理解和吸收其设计的精髓之处。
1.1 这道题想考察什么?
答:(1)考察要点
●是否熟悉onActivityResult的用法(初级)
●是否思考过用回调替代onActivityResult;是否实践过用回调替代onActivityResult ( 中级)
●是否意识到回调存在的问题;是否能给出匿名内部类对外部引用的解决方案(高级)
(2)题目剖析
●Activity 的onActivityResult 使用起来非常麻烦,为什么不设计成回调?
●onActivityResult是干什么的,怎么用?
●回调在这样的场景下适用吗?
●如果适用,那为什么不用回调? 如果不适用,给出你的理由?
2 onActivityResult好用吗?(onActivityResult为什么麻烦?)
答:onActivityResult使用很麻烦,存在以下使用的问题:
// ActivityA
startActivityForResult(intent, requestCode);
// ActivityB
setResult(resultCode, intent);
finish();
// ActivityA
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(resultCode) {
...
}
}
(1)代码处理逻辑分离,容易出现遗漏和不一致的地方
在使用上startActivity和onActivityResult的逻辑是分开的,例如:startActivity的requestCode被修改了,那么onActivityResult的requestCode也要修改,这样就很容易出现修改遗漏而导致不一致的地方。
(2)写法不够直观,且结果数据没有类型安全保障
onActivityResult的用法要求ActivityA和ActivityB需要事先沟通好回传的内容字段和格式,如:
// ActivityB
Intent intent = new Intent();
intent.putExtra("result", "true");
setResult(resultCode, intent);
finish();
// ActivityA
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(resultCode) {
...
boolean result = Boolean.valueOf(data.getString("result"));
}
}
如果ActivityB修改了result的回传格式为boolean,但是又没有通知ActivityA修改,这样编译时是不会发现问题,只有在运行时抛出异常了才发现:
// ActivityB
Intent intent = new Intent();
intent.putExtra("result", true); // String修改为boolean
setResult(resultCode, intent);
// Activity A
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(resultCode) {
...
// throws exception
boolean result = Boolean.valueOf(data.getString("result"));
}
}
(3)当结果种类比较多时,onActivityResult会逐渐臃肿且难以维护
这个问题也是显而易见的,当处理场景较多时,就会出现很多switch-case语句。当然,把相应的case实现抽成方法,可以提高代码的可读性:
// ActivityA
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(resultCode) {
case PICK_PHOTO_BACK:
handlePickPhotoBack(data);
case CAMERA_BACK:
handleCameraBack(data);
case CROP_PHOTO_BACK:
handleCropPhotoBack(data);
...
}
}
3 onActivityResult能设计成回调吗?
答:不能。
假设onActivityResult可以设计成回调,那么startActivity的代码应该如下:
// ActivityA
startActivityForResult(intent, new OnResultCallback(){
@Override
public void onResult(Intent data) {
textView.setText(data.getString("result"));
}
})
假设onActivityResult可以设计成回调,由于匿名内部类会持有外部类的引用,当ActivityA被意外销毁时,由于其对象被匿名内部类OnResultCallback持有,可能会导致其无法正常被GC。当ActivityB给ActivityA回复时,OnResultCallback被调用,新的ActivityA*被恢复,但是onResult方法持有的依旧是被销毁的ActivityA的引用,回调没有任何意义,即:
// Activity A2
startActivityForResult(intent, new OnResultCallback(){
@Override
public void onResult(Intent data) {
activityA.textView.setText(data.getString("result"));
}
})
所以,结论就是不能使用回调,因为Activity的销毁和恢复机制不允许匿名内部类出现。
3.1 ActivityA销毁后的情景?
答:ActivityA被销毁,等到返回到A的时候,系统会创建一个新的ActivityA*实例,然后调用新实例的onActivityResult。ActivityB不需要知道ActivityA在不在,B只需要调用setResult设置返回的结果,然后调用finish,AMS会处理重新创建A的问题。
4 基于注解处理器和Fragment的回调 通过替换匿名内部类的外部引用实现回调 方案?
答:回调的时候,ActivityA销毁了之后,它的Fragment也会被销毁。但返回的时候,即回调前一刻,系统会重新创建一个ActivityA*,并且根据之前Bundle savedInstance里面的值来恢复UI的状态,调用的是新ActivityA*的onActivityResult。而且,这时候FragmentManager当中的Fragment也会被重建,还会有一个无界面的Fragment实例来接收onActivityResult。调用的时候,我们获取到刚设置的回调进行调用,也就是匿名内部类,它持有的引用一开始其实是旧ActivityA的引用。
解决思路:(1)FragmentManager有个隐藏的方法叫findFragmentByWho,通过反射可以调用mWho去获取新的Fragment实例,获取到新的Fragment之后能获取到新的ActivityA,再通过View的id和findViewById得到ActivityA*和Fragment当中对应的View。替换回调匿名内部类当中引用的Activity 、View、Fragment的实例为重建之后的新实例,问题就解决了。(2)如果发现捕获了View 、Fragment以外的引用类型,适当给出警告或者错误来提示开发者规避就好了。
4.1 通过反射可以调用mWho去获取新的Fragment的思路?
答:FragmentManager有个隐藏的方法叫findFragmentByWho,通过反射可以调用mWho去获取新的Fragment实例。
4.2 Activity嵌套Fragment,解决Fragment中onActivityResult()方法无响应问题?
答:(1)ActivityA中以startActivityForResult开启ActivityB,ActivityB关闭后,只会调用ActivityA的onActivityResult。
(2)在ActivityA嵌套的Fragment中以startActivityForResult开启ActivityB,ActivityB关闭后,调用当前的Fragment的onActivityResult,和Activity的onActivityResult(它的requestCode非请求的requestCode,所以本质它不算合法回调);
注意:如果Activity中覆写了onActivityResult,则需要确保调用了super.onActivityResult()方法,否则Fragment的onActivityResult不会回调。
(3)在ActivityA嵌套的Fragment中以getActivity().startActivityForResult开启ActivityB,ActivityB关闭后,只会调用ActivityA的onActivityResult,不会回调Fragment的onActivityResult。
(4)所以:Activity嵌套Fragment时,可通过在Activity的onActivityResult中调用Fragment的startActivityForResult。
(5)总结起来就是:从哪里发起调用,最终就会走到哪里。
Fragment中调用startActivityForResult的那些坑
4.3 基于注解处理器和Fragment的回调方案 是如何实现ActivityA中以startActivityForResult开启ActivityB,依然会调用Fragment的onActivityResult?
答:在调用框架的ActivityBuilder.startActivityForResult启动Activity时,给ResultFragment设置回调监听,以及调用ResultFragment的startActivityForResult。所以,原因就是在ActivityBuilder.startActivityForResult中调用了Fragment的startActivityForResult。
4.4 补充
答:1.如果它是一个值,例如整数,同时又是final的,那么这时候我们就不需要重新获取了,直接使用它的值就可以了。
2.如果它是一个引用,那么引用的对象如果跟Activity的实例没有关系,同样,我们不需要去管他,因为逻辑上这个对象不会因为Activity的变更而受到什么影响。
3.如果它是一个引用,并且跟Activity有关系,那么除了是Activity成员的情况,大概率是引用了UI,对于这种情况,我们可以根据View的id或者Fragment的mWho这个字段来获得新Activity当中对应的View或者Fragment来解决;
4.如果它是一个引用,并且跟Activity有关系,也可能指向Activity成员的成员,这种情况其实就比较复杂了,目前没有很好的解决办法,但这种情况其实很容易就可以规避掉。作为一个成熟的框架,可以在这里对回调捕获的变量做一个检查,如果发现捕获了View 、Fragment以外的引用类型,适当给出警告或者错误来提示开发者规避就好了,那么检查的方法也可以包括运行时和编译期,能采用的手段也是比较多的,又可以开始一个新的话题了。这其实也是就是最后思考题的答案。