android 登錄成功后再跳轉(zhuǎn)到目標(biāo)界面的思考

項(xiàng)目中經(jīng)常有遇到一個(gè)典型的需求红柱,就是在用戶在需要進(jìn)入A界面的時(shí)候,需要先判斷用戶是否登錄强岸,如果沒有登錄漩仙,則需要先進(jìn)入登錄界面撒遣,如果登錄成功了喳逛,再直接跳轉(zhuǎn)到A界面瞧捌。

需求定義

所以這里有兩個(gè)需求: 1、自動(dòng)跳轉(zhuǎn)到登錄界面 2润文、登錄成功后再自動(dòng)跳轉(zhuǎn)到目標(biāo)A界面

如果我們直接判斷用戶有沒有登錄,提醒用戶登錄殿怜。也沒有讓用戶登錄成功后再直接跳轉(zhuǎn)到目標(biāo)界面典蝌,這樣的用戶體驗(yàn)恐怕是不能滿足一個(gè)高逼格程序員的要求。那么头谜,我們來思考下骏掀,如何才能更加優(yōu)雅的完成這個(gè)工作呢?

當(dāng)然柱告,在開始之前截驮,我們可以先了解下其他人都是怎么做的,畢竟我們可以站在巨人的肩膀上才能看得更遠(yuǎn)际度。

思考可行的方案

首先我們第一個(gè)想到的解決方式葵袭,就是攔截器。如果我們?cè)谶M(jìn)入A界面的時(shí)候乖菱,可以在操作之前加入一個(gè)攔截器的話坡锡,豈不是可以做到在進(jìn)入A界面前的判斷呢蓬网?

在google之后,找到兩個(gè)方案鹉勒。

A帆锋、 Android攔截器 (可以點(diǎn)擊查看)

此方案通過注解。在進(jìn)入目標(biāo)界面A時(shí)禽额,判斷是否有指定的攔截器锯厢,如果有,則檢驗(yàn)是否滿足攔截器要求脯倒,不滿足哲鸳,則執(zhí)行攔截器的處理,處理完成后盔憨,通過onActivityResult最后觸發(fā)invoke的回調(diào)方法徙菠。

此方案和我們需求略有不同,那么說下此方案存在的缺點(diǎn):
1郁岩、用了繼承的方式,來插入invoke的回調(diào)方法婿奔。由于java的單繼承的特性,如果工程中已經(jīng)有基類的情況问慎,調(diào)整起來比較麻煩萍摊。侵入性太高。

2如叼、此方案中冰木,在沒有登錄的情況下,其實(shí)已經(jīng)進(jìn)入了目標(biāo)A頁面笼恰。相應(yīng)的初始化都已經(jīng)執(zhí)行了踊沸。如果沒有登錄成功,這樣工作其實(shí)是白做了社证。如果目標(biāo)A界面要登錄才能進(jìn)入的話逼龟,此方案不符合要求的。

B追葡、我們直接使用路由框架腺律,參考下阿里的ARouter方案,可以看到宜肉,我們可以在固定路由上面插入攔截器匀钧。這里有一篇文章介紹 阿里ARouter攔截器使用及源碼解析

看了文章后,發(fā)現(xiàn)攔截器實(shí)現(xiàn)的非常優(yōu)雅谬返,但是依然不是我們想要的之斯。因?yàn)檫@個(gè)攔截器執(zhí)行完后,馬上會(huì)執(zhí)行目標(biāo)方法朱浴。中間并不會(huì)等待吊圾。所以我們根本沒有辦法去執(zhí)行我們的登錄操作达椰。 所以pass了。

我們?cè)倩剡^頭來思考项乒,攔截器似乎并不能直接完成我們的需求啰劲,因?yàn)槲覀冃枰迦胍粋€(gè)驗(yàn)證行為后(例如進(jìn)入登錄界面),還要執(zhí)行相應(yīng)的操作后檀何,保證這個(gè)驗(yàn)證行為通過后蝇裤,才能真正進(jìn)入到我們的目標(biāo)界面。

其實(shí)如果我們只是單純的完成這個(gè)功能的話频鉴,可能大家最容易想到的就是栓辜,在進(jìn)入登錄界面的時(shí)候,在intent中裝載一個(gè)目標(biāo)target的intent.如果登錄成功了垛孔,就判斷是否有目標(biāo)target藕甩,如果有,就跳轉(zhuǎn)到目標(biāo)target.

        Intent intent = new Intent(this,LoginActivity.class);
        Intent target = new Intent(this,OrderDetailActivity.class);
        intent.putExtra("target",target);
        startActivity(intent);

這種方式做起來非常直接周荐,也可理解狭莱,但是最明顯的問題就是,會(huì)導(dǎo)致登錄界面多了很多與自己無關(guān)的業(yè)務(wù)判斷概作。那我們繼續(xù)google看看腋妙,有沒有類似的做法,并且實(shí)現(xiàn)優(yōu)雅一點(diǎn)的呢讯榕?

Android 登錄判斷器骤素,登錄成功后幫你準(zhǔn)確跳轉(zhuǎn)到目標(biāo)activity 這篇的訪問量比較大,似乎是個(gè)比較靠譜的方法愚屁。我們來大概分析下它的做法济竹。

public static void interceptor(Context ctx, String target, Bundle bundle, Intent intent) {  
        if (target != null && target.length() > 0) {  
            LoginCarrier invoker = new LoginCarrier(target, bundle);  
            if (getLogin()) {  
                invoker.invoke(ctx);  
            } else {  
                if (intent == null) {  
                    intent = new Intent(ctx, LoginActivity.class);  
                }  
                login(ctx, invoker, intent);  
            }  
        } else {  
            Toast.makeText(ctx, "沒有activity可以跳轉(zhuǎn)", 300).show();  
        }  
    } 

private static void login(Context context, LoginCarrier invoker, Intent intent) {  
        intent.putExtra(mINVOKER, invoker);  
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);  
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  
        context.startActivity(intent);  
    }  

我們看上面的核心代碼就是,封裝一個(gè)LoginCarrier集绰。如果沒有登錄规辱,則把這個(gè)LoginCarrier傳入到登錄界面。登錄成功后栽燕,觸發(fā)invoke()方法。本質(zhì)上和我們上面的想法差不多改淑。

看完之后碍岔,還是覺得實(shí)現(xiàn)上不夠完美,總覺得有些缺點(diǎn)朵夏。例如

1蔼啦、在登錄界面還是侵入了過多的邏輯(這似乎不可避免,但是否可以簡(jiǎn)潔些呢)

2仰猖、擴(kuò)展性比較差捏肢。比方說我要購買某個(gè)禮品奈籽,需要登錄,然后再跳轉(zhuǎn)到充值界面充值完成后再回來鸵赫。

那到底有沒有更好的實(shí)現(xiàn)方案呢衣屏,谷歌后,發(fā)現(xiàn)暫時(shí)沒有找到可靠的方案了辩棒,所以說靠天靠地狼忱,不如靠自己,既然找不到合適的方案一睁,那就好好思考下钻弄,自己動(dòng)手來干了。

首先者吁,我們?cè)倩剡^頭考慮我們的需求窘俺,我們需要執(zhí)行一個(gè)目標(biāo)方法。但是目標(biāo)方法需要一個(gè)前置的條件滿足才能執(zhí)行复凳,并且這個(gè)前置條件可能不只一個(gè)瘤泪,還有就是這個(gè)前置條件并不是馬上就能完成的。

那我們根據(jù)需求抽象出來的數(shù)據(jù)模型應(yīng)該是染坯。

public class CallUnit {
    //目標(biāo)行為 
    private Action action;
    //先進(jìn)先出驗(yàn)證模型
    private Queue<Valid> validQueue = new ArrayDeque<>();
    //上一個(gè)執(zhí)行的valid
    private Valid lastValid;

}

那么目標(biāo)行為action就是一個(gè)執(zhí)行體均芽。負(fù)責(zé)執(zhí)行目標(biāo)方法。

public interface Action {
    void call();
}

驗(yàn)證操作validQueue保存一個(gè)驗(yàn)證隊(duì)列单鹿,Valid的驗(yàn)證模型是

public interface Valid {

    /**
     * 是否滿足檢驗(yàn)器的要求掀宋,如果不滿足的話,則執(zhí)行doValid()方法仲锄。如果滿足劲妙,則執(zhí)行目標(biāo)action.call
     * @return
     */
    boolean check();
   //去執(zhí)行驗(yàn)證前置行為,例如跳轉(zhuǎn)到登錄界面儒喊。(但并未完成驗(yàn)證镣奋。)
    void doValid();
}

那么整個(gè)邏輯用一幅圖表達(dá)出來,會(huì)比較清楚怀愧。


執(zhí)行邏輯

接下來根據(jù)圖侨颈,來講解代碼實(shí)現(xiàn)。

第一步芯义,我們需要構(gòu)造一個(gè)CallUnit單元哈垢。例如,我們需要跳轉(zhuǎn)到折扣界面扛拨,前置是我們必須要登錄耘分,并且要有折扣碼。

所以這里,我們有兩個(gè)驗(yàn)證模型求泰,一個(gè)是登錄央渣,一個(gè)是拿到折扣。

public class DiscountValid implements Valid {
    private Context context;

    public DiscountValid(Context context) {
        this.context = context;
    }

    /**
     * 
     * @return
     */
    @Override
    public boolean check() {
        return UserConfigCache.isDiscount(context);
    }


    /**
     * if check() return false. then doValid was called
     */
    @Override
    public void doValid() {
         DiscountActivity.start((Activity) context);
    }
}


public class LoginValid implements Valid {
    private Context context;

    public LoginValid(Context context) {
        this.context = context;
    }

    /**
     * check whether it login in or not
     * @return
     */
    @Override
    public boolean check() {
        return UserConfigCache.isLogin(context);
    }


    /**
     * if check() return false. then doValid was called
     */
    @Override
    public void doValid() {
         LoginActivity.start((Activity) context);
    }
}

然后我們需要構(gòu)造一個(gè)執(zhí)行體渴频。直接在當(dāng)前的Activity里面實(shí)現(xiàn)Action接口即可芽丹。例如我們?cè)贛ainActivity中實(shí)現(xiàn)。

    @Override
    public void call() {
        //這是我們的目標(biāo)行為
        OrderDetailActivity.startActivity(MainActivity.this, "1234");
    }

接下來枉氮,我們就可以構(gòu)造一個(gè)CallUnit對(duì)象并進(jìn)行執(zhí)行了志衍。

                CallUnit.newInstance(MainActivity.this)
                        .addValid(new LoginValid(MainActivity.this))
                        .addValid(new DiscountValid(MainActivity.this))
                        .doCall();

我們來看看doCall到底做了什么?

    public void doCall(){
        ActionManager.instance().postCallUnit(this);
    }

發(fā)現(xiàn)聊替,我們是通過ActionManager的單例調(diào)用了postCallUnit().我們看下這個(gè)單例有啥作用

public class ActionManager {

    static ActionManager instance = new ActionManager();

    public static ActionManager instance() {

        return instance;
    }

    Stack<CallUnit> delaysActions = new Stack<>();
    ....
}

這個(gè)單例維護(hù)了一個(gè)CallUnit的堆棧楼肪,表示我們支持一個(gè)目標(biāo)行為里面再嵌入一個(gè)目標(biāo)行為。但是這個(gè)需求恐怕很少會(huì)遇到惹悄。但是設(shè)計(jì)上是支持的春叫。

我們?cè)倩剡^頭看看,postCallUnit()到底做了啥泣港?

    /**
     * 根據(jù)條件判斷暂殖,是否要執(zhí)行一個(gè)action
     *
     * @param callUnit
     */
    public void postCallUnit(CallUnit callUnit) {

        //清除所有的actions
        delaysActions.clear();
        //執(zhí)行check
        callUnit.check();
        //如果全部滿足,則直接跳轉(zhuǎn)目標(biāo)方法
        if (callUnit.getValidQueue().size() == 0) {
            callUnit.getAction().call();
        } else {
            //加入到延遲執(zhí)行體中來
            delaysActions.push(callUnit);

            Valid valid = callUnit.getValidQueue().peek();
            callUnit.setLastValid(valid);
            //是否會(huì)有后置任務(wù)
            valid.doValid();

        }
    }

備注非常清楚当纱,就是判斷是否驗(yàn)證條件都滿足呛每,如果滿足,則直接執(zhí)行目標(biāo)方法坡氯,如果不滿足晨横,則執(zhí)行doValid方法。并且保存當(dāng)前valid的引用箫柳,以便后面驗(yàn)證valid是否滿足條件手形。如果不滿足,是不允許再執(zhí)行下一輪的驗(yàn)證悯恍。

到這里库糠,我們知道,我們已經(jīng)觸發(fā)了執(zhí)行體涮毫,并順利進(jìn)入了登錄驗(yàn)證的執(zhí)行體瞬欧。因?yàn)榈卿涍@個(gè)操作需要用戶手動(dòng)觸發(fā)完成,我們只是引導(dǎo)用戶到了登錄界面(當(dāng)然登錄操作也可以代碼自動(dòng)完成罢防,那就沒有必要跳頁面了)黍判,由于我們因?yàn)榈却脩舻妮斎耄覀兊尿?yàn)證模型就在這里停下來了篙梢,如果登錄成功了,我們才需要讓整個(gè)驗(yàn)證模型再運(yùn)轉(zhuǎn)起來了美旧,所以驗(yàn)證后渤滞,永遠(yuǎn)少不了手動(dòng)開啟驗(yàn)證模型贬墩。

例如我們?cè)诘卿洺晒螅枰{(diào)用方法CallUnit.reCall():

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(LoginActivity.this,"登錄成功",Toast.LENGTH_SHORT).show();
                UserConfigCache.setLogin(LoginActivity.this, true);
                //這里執(zhí)行延遲的action方法妄呕。
                CallUnit.reCall();
                finish();
            }
        });

我們看看CallUnit.reCall()的執(zhí)行方法

    public static void reCall(){
        ActionManager.instance().checkValid();
    }

    public void checkValid() {

        if (delaysActions.size() > 0) {
            CallUnit callUnit = delaysActions.peek();

            if (callUnit.getLastValid().check() == false) {
                throw new ValidException(String.format("you must pass through the %s,and then reCall()", callUnit.getLastValid().getClass().toString()));

            }

            if (callUnit != null) {
                Queue<Valid> validQueue = callUnit.getValidQueue();

                validQueue.remove(callUnit.getLastValid());
                //valid已經(jīng)執(zhí)行完了陶舞,則表示此delay已經(jīng)檢驗(yàn)完了--執(zhí)行目標(biāo)方法
                if (validQueue.size() == 0) {
                    callUnit.getAction().call();
                    //把這個(gè)任務(wù)移出
                    delaysActions.remove(callUnit);
                } else {

                    Valid valid = callUnit.getValidQueue().peek();
                    callUnit.setLastValid(valid);
                    //是否會(huì)有后置任務(wù)
                    valid.doValid();
                }
            }
        }
    }


最終是調(diào)用ActionManager.instance().checkValid()的方法,就是判斷上一個(gè)valid是否執(zhí)行成功绪励,如果沒有成功肿孵,則會(huì)報(bào)出異常。提示必須滿足check()為true后疏魏,才能執(zhí)行下一個(gè)valid.如果你永遠(yuǎn)都不想目標(biāo)行為執(zhí)行過去停做,就不要調(diào)用CallUnit.reCall()方法即可。如果上一個(gè)valid執(zhí)行成功大莫,則會(huì)再調(diào)用下一個(gè)valid蛉腌,直到所有的valid都執(zhí)行完成后,則進(jìn)入callUnit.getAction().call()的執(zhí)行只厘。最后進(jìn)入訂單折扣界面了烙丛。

ps:其實(shí)工程也實(shí)現(xiàn)了注解調(diào)用的實(shí)現(xiàn)。但是前提是所有的檢驗(yàn)?zāi)P筒恍枰獋魅腩~外的參數(shù)才行羔味。 具體看代碼

    /**
     * 通過反射注解來組裝(但是這個(gè)前提是無參的構(gòu)造方法才行)
     *
     * @param action
     */
    public void postCallUnit(Action action) {
        Class clz = action.getClass();
        try {
            Method method = clz.getMethod("call");
            Interceptor interceptor = method.getAnnotation(Interceptor.class);
            Class<? extends Valid>[] clzArray = interceptor.value();
            CallUnit callUnit = new CallUnit(action);
            for (Class cla : clzArray) {
                callUnit.addValid((Valid) cla.newInstance());
            }

            postCallUnit(callUnit);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

演示流程圖如下

只需要進(jìn)行登錄的驗(yàn)證
需同時(shí)進(jìn)行登錄和優(yōu)惠券的驗(yàn)證

代碼地址

最后放下完整的代碼鏈接庫河咽,如果對(duì)你有幫助,記得star哦

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赋元,一起剝皮案震驚了整個(gè)濱河市忘蟹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌们陆,老刑警劉巖寒瓦,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異坪仇,居然都是意外死亡杂腰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門椅文,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喂很,“玉大人,你說我怎么就攤上這事皆刺∩倮保” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵羡蛾,是天一觀的道長(zhǎng)漓帅。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么忙干? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任器予,我火速辦了婚禮,結(jié)果婚禮上捐迫,老公的妹妹穿的比我還像新娘乾翔。我一直安慰自己,他們只是感情好施戴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布反浓。 她就那樣靜靜地躺著,像睡著了一般赞哗。 火紅的嫁衣襯著肌膚如雪雷则。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天懈玻,我揣著相機(jī)與錄音巧婶,去河邊找鬼。 笑死涂乌,一個(gè)胖子當(dāng)著我的面吹牛艺栈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播湾盒,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼湿右,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了罚勾?” 一聲冷哼從身側(cè)響起毅人,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尖殃,沒想到半個(gè)月后丈莺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡送丰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年缔俄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片器躏。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俐载,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出登失,到底是詐尸還是另有隱情遏佣,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布揽浙,位于F島的核電站状婶,受9級(jí)特大地震影響意敛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜太抓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一空闲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧走敌,春花似錦、人聲如沸逗噩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽异雁。三九已至捶障,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纲刀,已是汗流浹背项炼。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留示绊,地道東北人锭部。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像面褐,于是被迫代替她去往敵國(guó)和親拌禾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,501評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理展哭,服務(wù)發(fā)現(xiàn)湃窍,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • iOS網(wǎng)絡(luò)架構(gòu)討論梳理整理中匪傍。您市。。 其實(shí)如果沒有APIManager這一層是沒法使用delegate的役衡,畢竟多個(gè)單...
    yhtang閱讀 5,165評(píng)論 1 23
  • 說明 由于x64下的vc不支持_asm內(nèi)聯(lián)匯編的寫法茵休。但是還是可以通過鏈接外部的匯編文件實(shí)現(xiàn)。但是以下方法在x86...
    JetLu閱讀 340評(píng)論 0 2
  • 昨天下午去看電影帽撑,發(fā)現(xiàn)《愛樂之城》排片只有晚上,沒有下午鞍时,就還是看了極限特工亏拉。其實(shí)本來就是打算看《極限特工》的扣蜻,只...
    租了五顆星閱讀 389評(píng)論 0 0