問題
最近遇到了一個很有趣的問題,爲什麼不能夠用回調的方式使用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有銷燬重建的動作,則使用反射重新設置一下。