告別onActivityResult

一、背景和目標

我們先來看下正常情況下啟動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,封裝一下startActivityForResultonActivityResult方法。

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仍然需要外部ActivityonActivityResult來手動調用,顯得很麻煩,調用者很容易忘記,而且這樣也沒有做到真正的跟Activity隔離,其實本質上跟原始的方式沒太大區別。

其實這種方式,只解決了上面提到的第二個問題,把onActivityResult中的很多不同跳轉的回調邏輯分散到了各自調用跳轉的地方,看起來會清爽一些。但是第一個問題還是沒解決,我們需要把調用跳轉的地方和接收回調的地方真正的綁定在一起,不需要其他多余的耦合。

2. 利用java反射和hook技術
既然需要跟外部ActivityonActivityResult解耦,我們能想到的就是,如果上面的那種方法,可以自動幫我們調用onActivityResult那該多好。我們能想到的一種方法就是利用java的反射機制,把ActivityonActivityResult調用流程反射出來,并且注入我們自己改造后的代碼,替換原來的流程,實現自動調動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方法,并通過回調形式返回給調用者。由于FragmentonActivityResult是系統自動調用的,所以我們就可以從此簡單輕松的告別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地址,里面實現了對FragmentFragmentActivityActivity的支持。

歡迎大家star和點贊哈~~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容