(更新,已反射hook到onActivityResult)如何避免使用onActivityResult,以提升代碼可讀性

想直接看更新內容的請點此處

更新,強迫症福音,onActivityResult方法hook到了java

問題

Android中,經過startActivityForResult跳轉頁面獲取數據應該沒必要多說,可是這種全部獲取到的結果都須要到onActivityResult中處理的方式實在使人蛋疼。android

試想一下,咱們敲着代碼唱着歌。忽然,半路上跳出一羣馬匪,讓咱們到另外一個頁面獲取一點數據,獲取後還不讓在當前代碼位置處理邏輯,要去onActivityResult添加一個requestCode分支處理結果,處理完才讓回來,等這一切都作完回來不免就會陷入這樣的思考:我是誰,我在哪,我在幹什麼,我剛纔寫到哪了……git

再想一下,你跟同事的代碼,跟到一個startActivityForResult,因而不耐煩地ctrl+f找到onActivityResult,發現裏面充斥着大量的requestCode分支,而後忽然意識到剛纔沒記下requestCode是什麼……github

分析問題

問題的根源是全部處理結果的邏輯都要放到onActivityResult中,在裏面根據requestCode做不一樣處理。而咱們渴望的是能在發起startActivityForResult的時候捎帶着把獲取結果後處理的邏輯也傳進去,並能在內部對requestCode判斷好,不用咱們再判斷一遍。bash

解決問題

嘗試一(不完美方式)

新建一個OnResultManager類,用來管理獲取結果的回調,下面詳細說。app

分析問題時說了,咱們但願在發起startActivityForResult的時候就指定好處理結果的邏輯,這個簡單,在OnResultManager中建立一個Callback接口,裏面定義一個OnActivityResult方法,參數和Activity的OnActivityResult方法參數徹底同樣,在發起start的時候除了intent和requestCode,再傳一個callback進去。而OnresultManager負責控制在系統的onActivityResult觸發時,調用對應callback的方法。ide

下面是OnResultManager的所有代碼。函數

public class OnResultManager {
    private static final String TAG = "OnResultManager";
    //HashMap的key Integer爲requestCode
    private static WeakHashMap<Activity,HashMap<Integer,Callback>> mCallbacks = new WeakHashMap<>();
    private WeakReference<Activity> mActivity;

    public OnResultManager(Activity activity) {
        mActivity = new WeakReference<Activity>(activity);
    }

    public void startForResult(Intent intent, int requestCode, Callback callback){
        Activity activity = getActivity();
        if(activity == null){
            return;
        }

        addCallback(activity,requestCode,callback);
        activity.startActivityForResult(intent,requestCode);
    }

    public void trigger(int requestCode, int resultCode, Intent data){
        Log.d(TAG,"----------- trigger");
        Activity activity = getActivity();
        if(activity == null){
            return;
        }

        Callback callback = findCallback(activity,requestCode);
        if(callback != null){
            callback.onActivityResult(requestCode,resultCode,data);
        }
    }

    //獲取該activity、該requestCode對應的callback
    private Callback findCallback(Activity activity,int requestCode){
        HashMap<Integer,Callback> map = mCallbacks.get(activity);
        if(map != null){
            return map.remove(requestCode);
        }
        return null;
    }

    private void addCallback(Activity activity,int requestCode,Callback callback){
        HashMap<Integer,Callback> map = mCallbacks.get(activity);
        if(map == null){
            map = new HashMap<>();
            mCallbacks.put(activity,map);
        }
        map.put(requestCode,callback);
    }

    private Activity getActivity(){
        return mActivity.get();
    }

    public interface Callback{
        void onActivityResult(int requestCode, int resultCode, Intent data);
    }
}
複製代碼

邏輯很簡單,裏面持有一個mActivity,使用弱引用以防止內存泄漏,在構造器中爲其賦值。還有一個static的WeakHashMap<Activity,HashMap<Integer,Callback>> mCallbacks 用來存放全部的callback,先以activity分,在各個activity中又使用hashmap存儲requestCode和callback的對應關係。工具

在startForResult時,最終仍是調用的activity的startActivityForResult,只不過在跳轉頁面以前,把callback存入了mCallbacks中。測試

而trigger方法則是根據activity和requestCode從mCallbacks中取出對應的callback,調用方法。

如今callback的存和取都搞定了,那麼問題來了,何時觸發「取」的操做呢,即trigger方法怎麼觸發呢?答案是在onActivityResult中調用,嫌麻煩能夠在BaseActivity中調用。

使用示例:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        go.setOnClickListener {
            val intent = Intent(this,SecondActivity::class.java)
            onResultManager.startForResult(intent,REQUEST_CODE,{requestCode: Int, resultCode: Int, data: Intent? ->
                if (resultCode == Activity.RESULT_OK){
                    val text = data?.getStringExtra("text")
                    Toast.makeText(this,"result -> "+text,Toast.LENGTH_SHORT).show()
                }else{
                    Toast.makeText(this,"canceled",Toast.LENGTH_SHORT).show()
                }
            })
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        onResultManager.trigger(requestCode,resultCode,data)
    }
複製代碼

但是這樣好蠢啊,你是否是以爲要是不用手動觸發,能自動觸發多好。我也是這麼想的,因此有整整一天我一直在找有什麼辦法能hook到onActivityResult方法,最後hook了Instrumentation,也hook了AMS,可是都對這個onActivityResult無能爲力,看源碼發現好像是在ActivityThread中傳遞的,可是很不幸的是這個ActivityThread沒辦法hook,至少經過簡單的反射和代理沒辦法作到(若是誰有辦法hook到,懇請您能分享出來,我真的特別想知道,我不甘心啊)

更新,強迫症福音,onActivityResult方法hook到了

這裏感謝一下@world_hello的提醒,十分感謝。

以前看到ActivityThread的mH的時候總想着弄個代理繼承它,而後重寫handleMessage方法,來獲取結果信息,但是卻苦於繼承不了,其實我一直忽視了Handler內部的一個Callback接口,其實徹底不用代理。下面詳細講解一下。

先說一下Handler,咱們通常使用的時候大可能是繼承自Handler,而後重寫handleMessage方法,在裏面處理接收消息。其實Handler還有個構造函數能夠接收一個Handler.Callback(不要和咱們本身定義的callback搞混了啊),在Handler.Callback的handleMessage方法中處理消息。

看一下Handler源碼:

/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

能夠看到,若是mCallback不爲null,就會執行mCallback的handleMessage方法。若是這個handleMessage方法的返回值爲true,就會直接return,若是爲false,就會繼續執行Handler自己的handleMessage方法。

下面打開Activity源碼,startActivityForResult方法中有這麼一段

if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
複製代碼

ar是ActivityResult,若是不爲null,就調用mMainThread的sendActivityResult方法,這個mMainThread就是ActivityThread類型的,因此繼續打開ActivityThread源碼,找到這個方法,順着這個方法一直找一直找,這裏中間的方法我就不貼了,最後會找到一個叫sendMessage的方法,這個方法最後一行是

mH.sendMessage(msg);
複製代碼

這個mH是一個Handler的子類,因此很明顯,activityresult在這個環節是經過handler傳遞的。

在H的handleMessage方法中有這樣一個case分支

case SEND_RESULT:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
                    handleSendResult((ResultData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
複製代碼

不用再多說了吧,就是這裏了。它調用了handleSendResult((ResultData)msg.obj),因此這個ResultData應該就是存放了咱們須要的東西。只要能攔截到它,拿出咱們須要的數據,那就算是hook到了onActivityResult了,那麼怎麼拿到呢?如今就要提到咱們剛纔提的Handler.Callback了,咱們建立一個callback,並記得讓handleMessage的方法返回false,以避免影響mH自己的handleMessage方法的執行,而後經過反射把這個callback給mH set進去。下面須要您對反射有一點點了解。

public class HookUtil {
    public static void hookActivityThreadHandler() throws Exception {
        // 先獲取到當前的ActivityThread對象
        final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
        currentActivityThreadField.setAccessible(true);
        final Object currentActivityThread = currentActivityThreadField.get(null);

        // 因爲ActivityThread一個進程只有一個,咱們獲取這個對象的mH
        Field mHField = activityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        Handler mH = (Handler) mHField.get(currentActivityThread);
        
        Handler.Callback mHCallback = new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                if(msg.what == 108){
                    Log.d("hook-------","onActivityResult");

                    try{
                        Object resultData = msg.obj;

                        Field mActivitiesField = activityThreadClass.getDeclaredField("mActivities");
                        mActivitiesField.setAccessible(true);
                        ArrayMap mActivities = (ArrayMap) mActivitiesField.get(currentActivityThread);

                        Class<?> resultDataClass = Class.forName("android.app.ActivityThread$ResultData");
                        Field tokenField = resultDataClass.getDeclaredField("token");
                        tokenField.setAccessible(true);
                        IBinder token = (IBinder) tokenField.get(resultData);

                        //r是ActivityClientRecord類型的
                        Object r = mActivities.get(token);
                        Class<?> ActivityClientRecordClass = Class.forName("android.app.ActivityThread$ActivityClientRecord");
                        Field activityField = ActivityClientRecordClass.getDeclaredField("activity");
                        activityField.setAccessible(true);
                        Activity activity = (Activity) activityField.get(r); //至此,終於拿到activity了

                        Field resultsField = resultDataClass.getDeclaredField("results");
                        resultsField.setAccessible(true);
                        List results = (List) resultsField.get(resultData);

                        //ResultInfo類型
                        Object resultInfo = results.get(0);

                        Class<?> resultInfoClass = Class.forName("android.app.ResultInfo");
                        Field mRequestCodeField = resultInfoClass.getDeclaredField("mRequestCode");
                        mRequestCodeField.setAccessible(true);
                        int mRequestCode = (int) mRequestCodeField.get(resultInfo); //拿到requestCode

                        Field mResultCodeField = resultInfoClass.getDeclaredField("mResultCode");
                        mResultCodeField.setAccessible(true);
                        int mResultCode = (int) mResultCodeField.get(resultInfo); //拿到resultCode

                        Field mDataField = resultInfoClass.getDeclaredField("mData");
                        mDataField.setAccessible(true);
                        Intent mData = (Intent) mDataField.get(resultInfo); //拿到intent

                        new OnResultManager(activity).trigger(mRequestCode,mResultCode,mData);



                    }catch (Exception e){
                        e.printStackTrace();
                    }




                }

                return false;
            }
        };
        Field mCallBackField = Handler.class.getDeclaredField("mCallback");
        mCallBackField.setAccessible(true);

        mCallBackField.set(mH, mHCallback);

    }

}
複製代碼

hookActivityThreadHandler方法中就是先獲取到ActivityThread對象,再經過activityThread獲取到mH,而後建立了一個Handler.Callback,最後用反射把這個callback set給mH。

相比之下,Handler.Callback中的handleMessage方法看起來更長一些,但其實並不複雜,只是反射使它變得面目全非了,它主要就是爲了獲取咱們OnResultManager的trigger方法須要的數據,一個activity,以及requestCode、resultCode、data。

msg.what==108的判斷,這裏108就是SEND_RESULT,我圖方便直接寫死了,源碼裏看着爲108,固然經過反射動態獲取SEND_RESULT更規範一點,你們別在乎這些細節。

先看activity怎麼獲取,主要看ActivityThread的handleSendResult方法,它的第一行

private void handleSendResult(ResultData res) {
        ActivityClientRecord r = mActivities.get(res.token);
複製代碼

這個res就是msg.obj,而獲取到ActivityClientRecord以後,它有一個名爲activity的field,獲取的就是咱們須要的activity對象了。自己很簡單,只不過這短短的一行代碼要用反射一點一點獲取,就繁瑣一些了。

另外三個數據都在一塊兒,在ResultData裏有個名爲results的Field,它是一個List,裏面就一個元素(可能有誤,但我打斷點看它一直都是一個),是ResultInfo類型,咱們須要的requestCode、resultCode、data就都在這個resultInfo裏面了,因此,繼續反射。

都獲取完了,調用

new OnResultManager(activity).trigger(mRequestCode,mResultCode,mData);
複製代碼

就是把咱們以前手動寫在onActivityResult中的那句放到這裏自動觸發。

如今方法寫完了,還要寫個自定義Application,在onCreate中調用

HookUtil.hookActivityThreadHandler()
複製代碼

大功告成啦!,趕忙測試一下吧,如今終於不用再在onActivityResult中手動觸發啦!github上代碼已經更新。

按理說咱們該在OnResultManager中定義個init方法,而後在init方法中去調hookActivityThreadHandler貌似更規範一些,不過這隻算個demo,就不考慮這麼多了。

還有,這種經過反射hook的方法在穩定性和兼容性上都沒法保證,因此這算是一種僅供娛樂的方式吧!領略一下hook的魅力。

OnResultManager項目地址

下面是world_hello實現的hook,雖然思路是同樣的,可是編寫的要比個人容易看懂得多,自愧不如,在此強烈推薦你們看一下。

world_hello實現的hook項目地址

第二種方式(參考RxPermissions的作法)

前段時間又來了個小項目,領導扔給我了,而後在這個項目裏就把以前沒用過(沒錯,以前總用H5開發……)的rxjava、kotlin都加進來了。有一天作動態權限處理,驚奇地發現RxPermissions竟然擺脫了Activity的onRequestPermissionsResult方法!!!你們都知道,動態權限大致就是先調用requestPermissions方法,而後受權的結果要到onRequestPermissionsResult中處理,簡直和startActivityForResult一模一樣。那RxPermissions是怎麼作到的呢!!!

接着在前幾天不太忙的時候看了下RxPermissions的源碼,發現它內部持有一個Fragment,這個fragment沒有視圖,只負責請求權限和返回結果,至關於一個橋樑的做用,咱們經過rxPermissions發起request的時候,其實並非activity去request,而是經過這個fragment去請求,而後在fragment的onRequestPermissionsResult中把結果發送出來,如此來避開activity的onRequestPermissionsResult方法。

當時,沒見過什麼大場面的我差點就給跪了。

RxPermissions的源碼就不貼了,網上的講解應該也不少。

一樣,Fragment也有startActivityForResult方法啊,那咱們也能夠採起相似的方法,隨心所欲。

此次取名叫AvoidOnResult,主要就是AvoidOnResult和AvoidOnResultFragment兩個類。先上代碼:

public class AvoidOnResult {
    private static final String TAG = "AvoidOnResult";
    private AvoidOnResultFragment mAvoidOnResultFragment;

    public AvoidOnResult(Activity activity) {
        mAvoidOnResultFragment = getAvoidOnResultFragment(activity);
    }

    public AvoidOnResult(Fragment fragment){
        this(fragment.getActivity());
    }

    private AvoidOnResultFragment getAvoidOnResultFragment(Activity activity) {
        AvoidOnResultFragment avoidOnResultFragment = findAvoidOnResultFragment(activity);
        if (avoidOnResultFragment == null) {
            avoidOnResultFragment = new AvoidOnResultFragment();
            FragmentManager fragmentManager = activity.getFragmentManager();
            fragmentManager
                    .beginTransaction()
                    .add(avoidOnResultFragment, TAG)
                    .commitAllowingStateLoss();
            fragmentManager.executePendingTransactions();
        }
        return avoidOnResultFragment;
    }

    private AvoidOnResultFragment findAvoidOnResultFragment(Activity activity) {
        return (AvoidOnResultFragment) activity.getFragmentManager().findFragmentByTag(TAG);
    }

    public Observable<ActivityResultInfo> startForResult(Intent intent, int requestCode) {
        return mAvoidOnResultFragment.startForResult(intent, requestCode);
    }

    public Observable<ActivityResultInfo> startForResult(Class<?> clazz, int requestCode) {
        Intent intent = new Intent(mAvoidOnResultFragment.getActivity(), clazz);
        return startForResult(intent, requestCode);
    }

    public void startForResult(Intent intent, int requestCode, Callback callback) {
        mAvoidOnResultFragment.startForResult(intent, requestCode, callback);
    }

    public void startForResult(Class<?> clazz, int requestCode, Callback callback) {
        Intent intent = new Intent(mAvoidOnResultFragment.getActivity(), clazz);
        startForResult(intent, requestCode, callback);
    }

    public interface Callback {
        void onActivityResult(int requestCode, int resultCode, Intent data);
    }
}
複製代碼
public class AvoidOnResultFragment extends Fragment {
    private Map<Integer, PublishSubject<ActivityResultInfo>> mSubjects = new HashMap<>();
    private Map<Integer, AvoidOnResult.Callback> mCallbacks = new HashMap<>();

    public AvoidOnResultFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    public Observable<ActivityResultInfo> startForResult(final Intent intent, final int requestCode) {
        PublishSubject<ActivityResultInfo> subject = PublishSubject.create();
        mSubjects.put(requestCode, subject);
        return subject.doOnSubscribe(new Consumer<Disposable>() {
            @Override
            public void accept(Disposable disposable) throws Exception {
                startActivityForResult(intent, requestCode);
            }
        });
    }

    public void startForResult(Intent intent, int requestCode, AvoidOnResult.Callback callback) {
        mCallbacks.put(requestCode, callback);
        startActivityForResult(intent, requestCode);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //rxjava方式的處理
        PublishSubject<ActivityResultInfo> subject = mSubjects.remove(requestCode);
        if (subject != null) {
            subject.onNext(new ActivityResultInfo(requestCode, resultCode, data));
            subject.onComplete();
        }

        //callback方式的處理
        AvoidOnResult.Callback callback = mCallbacks.remove(requestCode);
        if (callback != null) {
            callback.onActivityResult(requestCode, resultCode, data);
        }
    }
}
複製代碼

AvoidOnResult

先看AvoidOnResult,和RxPermissions同樣,也持有一個無視圖的fragment,在構造器中先去獲取這個AvoidOnResultFragment,getAvoidOnResultFragment、findAvoidOnResultFragment這兩個方法是從RxPermissions扒來的,大致就是先經過TAG獲取fragment,若是是null就新建一個並add進去。

這個類內部也定義了一個Callback接口,沒必要多說。

繼續,這個類有多個startForResult方法,主要看public void startForResult(Intent intent, int requestCode, Callback callback),它自己不幹實事,只是調用fragment的同名方法,全部的邏輯都是fragment中處理,待會兒咱們來看這個「同名方法」。

AvoidOnResultFragment

再看這個fragment,它持有一個mCallbacks,存着requestCode和callback的對應關係。而後找到上邊說的同名方法startForResult,只有兩行代碼,一、把callback存起來,二、調用fragment的startActivityForResult

繼續看fragment的onActivityResult方法,主要看註釋有「callback方式的處理」的代碼,就是從mCallbacks中拿到requestCode對應的callback,調用callback的onActivityResult方法。整體的就是這樣了,是否是很簡單。

拓展

能夠看到除了返回值爲void的startForResult方法外,還有幾個返回值爲Observable的,原理同樣,只不過fragment中再也不是存callback,而是存subject,而後經過doOnSubscribe使它在subscribe的時候跳轉頁面,最後把獲得的Observable返回。對應的,在onActivityResult中拿到對應的subject,經過onNext把數據發出去。

使用示例:

//callback方式
callback.setOnClickListener {
    AvoidOnResult(this).startForResult(FetchDataActivity::class.java, REQUEST_CODE_CALLBACK, object : AvoidOnResult.Callback {
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) =
                if (resultCode == Activity.RESULT_OK) {
                    val text = data?.getStringExtra("text")
                    Toast.makeText(this@MainActivity, "callback -> " + text, Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(this@MainActivity, "callback canceled", Toast.LENGTH_SHORT).show()
                }

    })
}

//rxjava方式
rxjava.setOnClickListener {
    AvoidOnResult(this)
            .startForResult(FetchDataActivity::class.java, REQUEST_CODE_RXJAVA)
            //下面可自由變換
            .filter { it.resultCode == Activity.RESULT_OK }
            .flatMap {
                val text = it.data.getStringExtra("text")
                Observable.fromIterable(text.asIterable())
            }
            .subscribe({
                Log.d("-------> ", it.toString())
            }, {
                Toast.makeText(this, "error", Toast.LENGTH_SHORT).show()
            }, {
                Toast.makeText(this, "complete", Toast.LENGTH_SHORT).show()
            })
}
複製代碼

AvoidOnResult項目地址

全部的工具類都是用java寫的,避免使用kotlin編寫,出現java沒法調用kotlin的狀況。測試代碼用的kotlin,不過沒用太多kotlin的特性,因此即使沒接觸過kotlin的應該也很好看懂吧!

最後祝你們新年快樂!要是能賞幾個star就更好啦!

相關文章
相關標籤/搜索