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

問(wèn)題

Android中悍及,通過(guò)startActivityForResult跳轉(zhuǎn)頁(yè)面獲取數(shù)據(jù)應(yīng)該不必多說(shuō)东且,但是這種所有獲取到的結(jié)果都需要到onActivityResult中處理的方式實(shí)在令人蛋疼割以。

試想一下金度,我們敲著代碼唱著歌。突然严沥,半路上跳出一群馬匪猜极,讓我們到另一個(gè)頁(yè)面獲取一點(diǎn)數(shù)據(jù),獲取后還不讓在當(dāng)前代碼位置處理邏輯消玄,要去onActivityResult添加一個(gè)requestCode分支處理結(jié)果跟伏,處理完才讓回來(lái)丢胚,等這一切都做完回來(lái)難免就會(huì)陷入這樣的思考:我是誰(shuí),我在哪受扳,我在干什么携龟,我剛才寫(xiě)到哪了……

高興.JPEG

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

fine.JPEG

分析問(wèn)題

問(wèn)題的根源是所有處理結(jié)果的邏輯都要放到onActivityResult中层亿,在里面根據(jù)requestCode作不同處理。而我們渴望的是能在發(fā)起startActivityForResult的時(shí)候捎帶著把獲取結(jié)果后處理的邏輯也傳進(jìn)去立美,并能在內(nèi)部對(duì)requestCode判斷好匿又,不用我們?cè)倥袛嘁槐椤?/p>

解決問(wèn)題

嘗試一(不完美方式)

新建一個(gè)OnResultManager類(lèi),用來(lái)管理獲取結(jié)果的回調(diào)建蹄,下面詳細(xì)說(shuō)碌更。

分析問(wèn)題時(shí)說(shuō)了,我們希望在發(fā)起startActivityForResult的時(shí)候就指定好處理結(jié)果的邏輯洞慎,這個(gè)簡(jiǎn)單痛单,在OnResultManager中創(chuàng)建一個(gè)Callback接口,里面定義一個(gè)OnActivityResult方法劲腿,參數(shù)和Activity的OnActivityResult方法參數(shù)完全一樣旭绒,在發(fā)起start的時(shí)候除了intent和requestCode,再傳一個(gè)callback進(jìn)去焦人。而OnresultManager負(fù)責(zé)控制在系統(tǒng)的onActivityResult觸發(fā)時(shí)挥吵,調(diào)用對(duì)應(yīng)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對(duì)應(yīng)的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);
    }
}

邏輯很簡(jiǎn)單,里面持有一個(gè)mActivity矿辽,使用弱引用以防止內(nèi)存泄漏丹允,在構(gòu)造器中為其賦值。還有一個(gè)static的WeakHashMap<Activity,HashMap<Integer,Callback>> mCallbacks 用來(lái)存放所有的callback袋倔,先以activity分雕蔽,在各個(gè)activity中又使用hashmap存儲(chǔ)requestCode和callback的對(duì)應(yīng)關(guān)系。

在startForResult時(shí)奕污,最終還是調(diào)用的activity的startActivityForResult萎羔,只不過(guò)在跳轉(zhuǎn)頁(yè)面之前,把callback存入了mCallbacks中碳默。

而trigger方法則是根據(jù)activity和requestCode從mCallbacks中取出對(duì)應(yīng)的callback贾陷,調(diào)用方法缘眶。

現(xiàn)在callback的存和取都搞定了,那么問(wèn)題來(lái)了髓废,什么時(shí)候觸發(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)
    }

可是這樣好蠢啊,你是不是覺(jué)得要是不用手動(dòng)觸發(fā)冈爹,能自動(dòng)觸發(fā)多好涌攻。我也是這么想的,所以有整整一天我一直在找有什么辦法能hook到onActivityResult方法频伤,最后hook了Instrumentation恳谎,也hook了AMS,但是都對(duì)這個(gè)onActivityResult無(wú)能為力憋肖,看源碼發(fā)現(xiàn)好像是在A(yíng)ctivityThread中傳遞的因痛,但是很不幸的是這個(gè)ActivityThread沒(méi)辦法hook,至少通過(guò)簡(jiǎn)單的反射和代理沒(méi)辦法做到(如果誰(shuí)有辦法hook到岸更,懇請(qǐng)您能分享出來(lái)鸵膏,我真的特別想知道,我不甘心霸醮丁)

OnResultManager項(xiàng)目地址

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

前段時(shí)間又來(lái)了個(gè)小項(xiàng)目谭企,領(lǐng)導(dǎo)扔給我了,然后在這個(gè)項(xiàng)目里就把之前沒(méi)用過(guò)(沒(méi)錯(cuò)结胀,之前總用H5開(kāi)發(fā)……)的rxjava赞咙、kotlin都加進(jìn)來(lái)了。有一天做動(dòng)態(tài)權(quán)限處理糟港,驚奇地發(fā)現(xiàn)RxPermissions居然擺脫了Activity的onRequestPermissionsResult方法!T悍隆秸抚!大家都知道,動(dòng)態(tài)權(quán)限大體就是先調(diào)用requestPermissions方法歹垫,然后授權(quán)的結(jié)果要到onRequestPermissionsResult中處理剥汤,簡(jiǎn)直和startActivityForResult如出一轍。那RxPermissions是怎么做到的呢E挪摇?愿摇!

接著在前幾天不太忙的時(shí)候看了下RxPermissions的源碼暮芭,發(fā)現(xiàn)它內(nèi)部持有一個(gè)Fragment鹿驼,這個(gè)fragment沒(méi)有視圖欲低,只負(fù)責(zé)請(qǐng)求權(quán)限和返回結(jié)果,相當(dāng)于一個(gè)橋梁的作用畜晰,我們通過(guò)rxPermissions發(fā)起request的時(shí)候砾莱,其實(shí)并不是activity去request,而是通過(guò)這個(gè)fragment去請(qǐng)求凄鼻,然后在fragment的onRequestPermissionsResult中把結(jié)果發(fā)送出來(lái)腊瑟,如此來(lái)避開(kāi)activity的onRequestPermissionsResult方法。

當(dāng)時(shí)块蚌,沒(méi)見(jiàn)過(guò)什么大場(chǎng)面的我差點(diǎn)就給跪了闰非。

操作.jpg

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

同樣财松,F(xiàn)ragment也有startActivityForResult方法啊,那我們也可以采取類(lèi)似的方法虎敦,為所欲為游岳。

這次取名叫AvoidOnResult,主要就是AvoidOnResult和AvoidOnResultFragment兩個(gè)類(lèi)其徙。先上代碼:

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一樣,也持有一個(gè)無(wú)視圖的fragment唾那,在構(gòu)造器中先去獲取這個(gè)AvoidOnResultFragment访锻,getAvoidOnResultFragment、findAvoidOnResultFragment這兩個(gè)方法是從RxPermissions扒來(lái)的闹获,大體就是先通過(guò)TAG獲取fragment期犬,如果是null就新建一個(gè)并add進(jìn)去。

這個(gè)類(lèi)內(nèi)部也定義了一個(gè)Callback接口避诽,不必多說(shuō)龟虎。

繼續(xù),這個(gè)類(lèi)有多個(gè)startForResult方法沙庐,主要看public void startForResult(Intent intent, int requestCode, Callback callback)鲤妥,它本身不干實(shí)事,只是調(diào)用fragment的同名方法拱雏,所有的邏輯都是fragment中處理棉安,待會(huì)兒我們來(lái)看這個(gè)“同名方法”。

AvoidOnResultFragment

再看這個(gè)fragment铸抑,它持有一個(gè)mCallbacks贡耽,存著requestCode和callback的對(duì)應(yīng)關(guān)系。然后找到上邊說(shuō)的同名方法startForResult,只有兩行代碼蒲赂,1阱冶、把callback存起來(lái),2凳宙、調(diào)用fragment的startActivityForResult

繼續(xù)看fragment的onActivityResult方法熙揍,主要看注釋有“callback方式的處理”的代碼,就是從mCallbacks中拿到requestCode對(duì)應(yīng)的callback氏涩,調(diào)用callback的onActivityResult方法届囚。總體的就是這樣了是尖,是不是很簡(jiǎn)單意系。

拓展

可以看到除了返回值為void的startForResult方法外,還有幾個(gè)返回值為Observable的饺汹,原理一樣蛔添,只不過(guò)fragment中不再是存callback,而是存subject兜辞,然后通過(guò)doOnSubscribe使它在subscribe的時(shí)候跳轉(zhuǎn)頁(yè)面迎瞧,最后把得到的Observable返回。對(duì)應(yīng)的逸吵,在onActivityResult中拿到對(duì)應(yīng)的subject凶硅,通過(guò)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項(xiàng)目地址

所有的工具類(lèi)都是用java寫(xiě)的扫皱,避免使用kotlin編寫(xiě)足绅,出現(xiàn)java無(wú)法調(diào)用kotlin的情況。測(cè)試代碼用的kotlin韩脑,不過(guò)沒(méi)用太多kotlin的特性氢妈,所以即便沒(méi)接觸過(guò)kotlin的應(yīng)該也很好看懂吧!

最后祝大家新年快樂(lè)段多!要是能賞幾個(gè)star就更好啦首量!

天天開(kāi)心.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市进苍,隨后出現(xiàn)的幾起案子蕾总,更是在濱河造成了極大的恐慌,老刑警劉巖琅捏,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異递雀,居然都是意外死亡柄延,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搜吧,“玉大人市俊,你說(shuō)我怎么就攤上這事÷四危” “怎么了摆昧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)蜒程。 經(jīng)常有香客問(wèn)我绅你,道長(zhǎng),這世上最難降的妖魔是什么昭躺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任忌锯,我火速辦了婚禮,結(jié)果婚禮上领炫,老公的妹妹穿的比我還像新娘偶垮。我一直安慰自己,他們只是感情好帝洪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布似舵。 她就那樣靜靜地躺著,像睡著了一般葱峡。 火紅的嫁衣襯著肌膚如雪砚哗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天族沃,我揣著相機(jī)與錄音频祝,去河邊找鬼。 笑死脆淹,一個(gè)胖子當(dāng)著我的面吹牛常空,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盖溺,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼漓糙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了烘嘱?” 一聲冷哼從身側(cè)響起昆禽,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝇庭,沒(méi)想到半個(gè)月后醉鳖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哮内,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年盗棵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纹因,死狀恐怖喷屋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞭恰,我是刑警寧澤屯曹,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站惊畏,受9級(jí)特大地震影響恶耽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陕截,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一驳棱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧农曲,春花似錦社搅、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至暮的,卻和暖如春笙以,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冻辩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工猖腕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恨闪。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓倘感,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親咙咽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子老玛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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