一、背景和目標
我們先來看下正常情況下啟動Activity和接收回調信息的方式:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 啟動Activity
startActivityForResult(new Intent(this, TestActivity.class), 1);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 接收Activity回調
if (requestCode == 1) {
// 處理回調信息
}
}
這樣看起來似乎也簡潔,但是會有兩個問題:
-
onActivityResult
必須在原始Activity中才能接收,如果想在非Activity中調用startActivityForResult
,那么調用和接收的地方就不在同一個地方了,代碼可讀性會大大降低。 -
onActivityResult
中所有的頁面跳轉回調處理都會在這里,需要通過對resultCode
進行if...else...
判斷才能區分是哪個跳轉的回調,如果跳轉比較多的話,看起來會特別煩人。
那么我們希望的是,可不可以像按鈕點擊事件一樣通過回調的方式接收頁面跳轉的回調信息呢?在哪調用的跳轉,就在哪接收回調,這樣看起來就會爽多了,提高代碼可讀性,也有利于模塊的封裝和隔離。類似于下面這樣:
// 啟動Activity
startActivityForResult(TestActivity.class, new Callback() {
@Override
public void onActivityResult(int resultCode, Intent data) {
// 處理回調信息
}
});
這樣看起來是不是更加簡潔呢~~~
二、探索與實現
1. 先介紹一種比較直接容易想到的方法:
通過一個代理類ActivityLauncher
,封裝一下startActivityForResult
和onActivityResult
方法。
public class ActivityLauncher {
private FragmentActivity mActivity;
private SparseArray<Callback> mCallbacks = new SparseArray<>();
private ActivityLauncher(FragmentActivity activity) {
mActivity = activity;
}
public void startActivityForResult(Intent intent, int requestCode, Callback callback) {
// 保存下Callback
mCallbacks.put(requestCode, callback);
mActivity.startActivityForResult(intent, requestCode);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// 取出對應Callback
Callback callback = mCallbacks.get(resultCode);
if (callback != null) {
callback.onActivityResult(requestCode, resultCode, data);
}
}
public interface Callback {
void onActivityResult(int requestCode, int resultCode, Intent data);
}
}
如何使用:
private ActivityLauncher mActivityLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 啟動Activity
Intent intent = new Intent(this, TestActivity.class);
mActivityLauncher = new ActivityLauncher(this);
mActivityLauncher.startActivityForResult(intent, 1, new ActivityLauncher.Callback() {
@Override
public void onActivityResult(int resultCode, Intent data) {
// 接收Activity回調
if (requestCode == 1) {
// 處理回調信息
}
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mActivityLauncher.onActivityResult(requestCode, resultCode, data);
}
這種方式看似簡單,但是實際用起來會有點問題。這里的onActivityResult
仍然需要外部Activity
的onActivityResult
來手動調用,顯得很麻煩,調用者很容易忘記,而且這樣也沒有做到真正的跟Activity
隔離,其實本質上跟原始的方式沒太大區別。
其實這種方式,只解決了上面提到的第二個問題,把onActivityResult
中的很多不同跳轉的回調邏輯分散到了各自調用跳轉的地方,看起來會清爽一些。但是第一個問題還是沒解決,我們需要把調用跳轉的地方和接收回調的地方真正的綁定在一起,不需要其他多余的耦合。
2. 利用java反射和hook技術
既然需要跟外部Activity
的onActivityResult
解耦,我們能想到的就是,如果上面的那種方法,可以自動幫我們調用onActivityResult
那該多好。我們能想到的一種方法就是利用java的反射機制,把Activity
的onActivityResult
調用流程反射出來,并且注入我們自己改造后的代碼,替換原來的流程,實現自動調動onActivityResult
的目的。
這里貼一段網上找到的實現hook的方案,有興趣的同學可以拿來研究下:
public class ActivityThreadCallbackHook {
// Copy from ActivityThread.mH Handler
public static final int SEND_RESULT = 108;
public static void hook() {
try {
ActivityThread activityThread = ActivityThread.currentActivityThread();
// 由于ActivityThread一個進程只有一個,我們獲取這個對象的mH
Field mHField;
mHField = ActivityThread.class.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(activityThread);
// 設置它的回調, 根據源碼:
// 我們自己給他設置一個回調,就會替代之前的回調;
// public void dispatchMessage(Message msg) {
// if (msg.callback != null) {
// handleCallback(msg);
// } else {
// if (mCallback != null) {
// if (mCallback.handleMessage(msg)) {
// return;
// }
// }
// handleMessage(msg);
// }
// }
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
// 塞入我們的 hook 對象
mCallBackField.set(mH, new MyHandlerCallback(mH));
Log.d("hook","success");
} catch (Exception e) {
// hook 失敗,整個 callback 就 gg了
e.printStackTrace();
}
}
private static class MyHandlerCallback implements Handler.Callback {
private Handler mOldHandler;
public MyHandlerCallback(Handler mOldHandler) {
this.mOldHandler = mOldHandler;
}
@Override
public boolean handleMessage(Message msg) {
// 不干擾系統分發邏輯
mOldHandler.handleMessage(msg);
// 通知 ResultManager
if (msg.what == SEND_RESULT) {
Object obj = msg.obj;
try {
// step 1 reflect to get activity
Object token = ReflectUtils.on(obj).get("token");
ArrayMap mActivities = (ArrayMap) ReflectUtils.on(ActivityThread.currentActivityThread()).get("mActivities");
Object activityClientRecord = mActivities.get(token);
Activity activity = (Activity) ReflectUtils.on(activityClientRecord).get("activity");
// step2 reflect to get ResultInfo
// 注意這里的分發,無法分發到 Fragment 內部,所以采用動態塞入一個 Fragment 是最穩定的方案
ArrayList<ResultInfo> results = (ArrayList<ResultInfo>) ReflectUtils.on(obj).get("results");
for (ResultInfo result : results) {
OnResultManager.getInstance().trigger(activity, result.mRequestCode, result.mResultCode, result.mData);
}
} catch (RuntimeException e) {
e.printStackTrace();
}
}
// default
return true;
}
}
}
3. 還有另一種實現注入onActivityResult
的方法,就是是利用AOP
技術,通過android-aspectjx
插件,可以實現面向切片編程。
直接看代碼:
@Aspect
public class OnResultAspect {
private static final String TAG = "OnResultAspect";
@After("execution(* android.app.Activity.onActivityResult(..))")
public void onActivityResultAfter(JoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
int requestCode = (int) args[0];
int resultCode = (int) args[1];
Intent data = (Intent) args[2];
Activity activity = (Activity) joinPoint.getTarget();
OnResultManager.getInstance().trigger(activity, requestCode, resultCode, data);
}
}
直接在每次系統調用onActivityResult
的時候,插入我們自己的onActivityResult
方法自動調用。
方法2和方法3,基本思路是一樣的,都是通過注入代碼的方式實現自動調用我們自己的onActivityResult
方法,但是都有不盡人意的地方:
方法2通過反射的方式,兼容性和穩定性都較差,而且容易和其他第三方插件產生沖突,出現問題也很難排查。而且,這兩種方法都會對所有頁面的onActivityResult
都產生注入,而實際使用中可能并不需要對所有頁面都生效,可控性較差。
4. 下面介紹一種既簡單又穩定的方法,來實現自動調用onActivityResult
:
安卓系統中,Fragment
有著跟Activity
一樣的生命周期,卻比Activity
更輕量級。我們可以利用一個空的無界面的Fragment
來監聽onActivityResult
方法,并通過回調形式返回給調用者。由于Fragment
的onActivityResult
是系統自動調用的,所以我們就可以從此簡單輕松的告別onActivityResult
了,而且這個都是系統自帶的方法,穩定又可靠。
下面簡單貼下實現代碼:
public class ActivityLauncher {
private static final String TAG = "ActivityLauncher";
private Context mContext;
private RouterFragment mRouterFragment;
public static ActivityLauncher init(FragmentActivity activity) {
return new ActivityLauncher(activity);
}
private ActivityLauncher(FragmentActivity activity) {
mContext = activity;
mRouterFragment = getRouterFragment(activity);
}
private RouterFragment getRouterFragment(FragmentActivity activity) {
RouterFragment routerFragment = findRouterFragment(activity);
if (routerFragment == null) {
routerFragment = RouterFragment.newInstance();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
fragmentManager
.beginTransaction()
.add(routerFragment, TAG)
.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
}
return routerFragment;
}
private RouterFragment findRouterFragment(FragmentActivity activity) {
return (RouterFragment) activity.getSupportFragmentManager().findFragmentByTag(TAG);
}
public void startActivityForResult(Class<?> clazz, Callback callback) {
Intent intent = new Intent(mContext, clazz);
startActivityForResult(intent, callback);
}
public void startActivityForResult(Intent intent, Callback callback) {
mRouterFragment.startActivityForResult(intent, callback);
}
public interface Callback {
void onActivityResult(int resultCode, Intent data);
}
}
public class RouterFragment extends Fragment {
private SparseArray<ActivityLauncher.Callback> mCallbacks = new SparseArray<>();
private Random mCodeGenerator = new Random();
public RouterFragment() {
// Required empty public constructor
}
public static RouterFragment newInstance() {
return new RouterFragment();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void startActivityForResult(Intent intent, ActivityLauncher.Callback callback) {
int requestCode = makeRequestCode();
mCallbacks.put(requestCode, callback);
startActivityForResult(intent, requestCode);
}
/**
* 隨機生成唯一的requestCode,最多嘗試10次
*
* @return
*/
private int makeRequestCode() {
int requestCode;
int tryCount = 0;
do {
requestCode = mCodeGenerator.nextInt(0x0000FFFF);
tryCount++;
} while (mCallbacks.indexOfKey(requestCode) >= 0 && tryCount < 10);
return requestCode;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
ActivityLauncher.Callback callback = mCallbacks.get(requestCode);
mCallbacks.remove(requestCode);
if (callback != null) {
callback.onActivityResult(resultCode, data);
}
}
}
如何調用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 啟動Activity
ActivityLauncher.init(this)
.startActivityForResult(TestActivity.class, new ActivityLauncher.Callback() {
@Override
public void onActivityResult(int resultCode, Intent data) {
// 處理回調信息
}
});
}
基本上實現了我們文章開頭的預期。上面是我簡化過后的代碼,只支持FragmentActivity
調用,具體可以看我的Github地址,里面實現了對Fragment
,FragmentActivity
和Activity
的支持。
歡迎大家star和點贊哈~~