如何避免使用onActivityResult,以提高代碼可讀性

問題

Android中,通過startActivityForResult跳轉(zhuǎn)頁面獲取數(shù)據(jù)應該不必多說,但是這種所有獲取到的結(jié)果都需要到onActivityResult中處理的方式實在令人蛋疼。

試想一下,我們敲著代碼唱著歌。突然,半路上跳出一群馬匪,讓我們到另一個頁面獲取一點數(shù)據(jù),獲取后還不讓在當前代碼位置處理邏輯,要去onActivityResult添加一個requestCode分支處理結(jié)果,處理完才讓回來,等這一切都做完回來難免就會陷入這樣的思考:我是誰,我在哪,我在干什么,我剛才寫到哪了……

高興.JPEG

再想一下,你跟同事的代碼,跟到一個startActivityForResult,于是不耐煩地ctrl+f找到onActivityResult,發(fā)現(xiàn)里面充斥著大量的requestCode分支,然后突然意識到剛才沒記下requestCode是什么……

fine.JPEG

分析問題

問題的根源是所有處理結(jié)果的邏輯都要放到onActivityResult中,在里面根據(jù)requestCode作不同處理。而我們渴望的是能在發(fā)起startActivityForResult的時候捎帶著把獲取結(jié)果后處理的邏輯也傳進去,并能在內(nèi)部對requestCode判斷好,不用我們再判斷一遍。

解決問題

嘗試一(不完美方式)

新建一個OnResultManager類,用來管理獲取結(jié)果的回調(diào),下面詳細說。

分析問題時說了,我們希望在發(fā)起startActivityForResult的時候就指定好處理結(jié)果的邏輯,這個簡單,在OnResultManager中創(chuàng)建一個Callback接口,里面定義一個OnActivityResult方法,參數(shù)和Activity的OnActivityResult方法參數(shù)完全一樣,在發(fā)起start的時候除了intent和requestCode,再傳一個callback進去。而OnresultManager負責控制在系統(tǒng)的onActivityResult觸發(fā)時,調(diào)用對應callback的方法。

下面是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,使用弱引用以防止內(nèi)存泄漏,在構(gòu)造器中為其賦值。還有一個static的WeakHashMap<Activity,HashMap<Integer,Callback>> mCallbacks 用來存放所有的callback,先以activity分,在各個activity中又使用hashmap存儲requestCode和callback的對應關(guān)系。

在startForResult時,最終還是調(diào)用的activity的startActivityForResult,只不過在跳轉(zhuǎn)頁面之前,把callback存入了mCallbacks中。

而trigger方法則是根據(jù)activity和requestCode從mCallbacks中取出對應的callback,調(diào)用方法。

現(xiàn)在callback的存和取都搞定了,那么問題來了,什么時候觸發(fā)“取”的操作呢,即trigger方法怎么觸發(fā)呢?答案是在onActivityResult中調(diào)用,嫌麻煩可以在BaseActivity中調(diào)用。

使用示例:

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)
    }

可是這樣好蠢啊,你是不是覺得要是不用手動觸發(fā),能自動觸發(fā)多好。我也是這么想的,所以有整整一天我一直在找有什么辦法能hook到onActivityResult方法,最后hook了Instrumentation,也hook了AMS,但是都對這個onActivityResult無能為力,看源碼發(fā)現(xiàn)好像是在ActivityThread中傳遞的,但是很不幸的是這個ActivityThread沒辦法hook,至少通過簡單的反射和代理沒辦法做到(如果誰有辦法hook到,懇請您能分享出來,我真的特別想知道,我不甘心啊)

OnResultManager項目地址

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

前段時間又來了個小項目,領(lǐng)導扔給我了,然后在這個項目里就把之前沒用過(沒錯,之前總用H5開發(fā)……)的rxjava、kotlin都加進來了。有一天做動態(tài)權(quán)限處理,驚奇地發(fā)現(xiàn)RxPermissions居然擺脫了Activity的onRequestPermissionsResult方法!!!大家都知道,動態(tài)權(quán)限大體就是先調(diào)用requestPermissions方法,然后授權(quán)的結(jié)果要到onRequestPermissionsResult中處理,簡直和startActivityForResult如出一轍。那RxPermissions是怎么做到的呢!!!

接著在前幾天不太忙的時候看了下RxPermissions的源碼,發(fā)現(xiàn)它內(nèi)部持有一個Fragment,這個fragment沒有視圖,只負責請求權(quán)限和返回結(jié)果,相當于一個橋梁的作用,我們通過rxPermissions發(fā)起request的時候,其實并不是activity去request,而是通過這個fragment去請求,然后在fragment的onRequestPermissionsResult中把結(jié)果發(fā)送出來,如此來避開activity的onRequestPermissionsResult方法。

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

操作.jpg

RxPermissions的源碼就不貼了,網(wǎng)上的講解應該也很多。

同樣,F(xiàn)ragment也有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,在構(gòu)造器中先去獲取這個AvoidOnResultFragment,getAvoidOnResultFragment、findAvoidOnResultFragment這兩個方法是從RxPermissions扒來的,大體就是先通過TAG獲取fragment,如果是null就新建一個并add進去。

這個類內(nèi)部也定義了一個Callback接口,不必多說。

繼續(xù),這個類有多個startForResult方法,主要看public void startForResult(Intent intent, int requestCode, Callback callback),它本身不干實事,只是調(diào)用fragment的同名方法,所有的邏輯都是fragment中處理,待會兒我們來看這個“同名方法”。

AvoidOnResultFragment

再看這個fragment,它持有一個mCallbacks,存著requestCode和callback的對應關(guān)系。然后找到上邊說的同名方法startForResult,只有兩行代碼,1、把callback存起來,2、調(diào)用fragment的startActivityForResult

繼續(xù)看fragment的onActivityResult方法,主要看注釋有“callback方式的處理”的代碼,就是從mCallbacks中拿到requestCode對應的callback,調(diào)用callback的onActivityResult方法。總體的就是這樣了,是不是很簡單。

拓展

可以看到除了返回值為void的startForResult方法外,還有幾個返回值為Observable的,原理一樣,只不過fragment中不再是存callback,而是存subject,然后通過doOnSubscribe使它在subscribe的時候跳轉(zhuǎn)頁面,最后把得到的Observable返回。對應的,在onActivityResult中拿到對應的subject,通過onNext把數(shù)據(jù)發(fā)出去。

使用示例:

//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編寫,出現(xiàn)java無法調(diào)用kotlin的情況。測試代碼用的kotlin,不過沒用太多kotlin的特性,所以即便沒接觸過kotlin的應該也很好看懂吧!

最后祝大家新年快樂!要是能賞幾個star就更好啦!

天天開心.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容