Originally, I just wanted to share my thoughts and ideas with everyone, but I didn’t expect so many people’s attention and love. I was really a little spoiled and surprised. Perhaps thinking is too long, to some people caused a misunderstanding, here to make an explanation. (If you haven’t read the article yet, you can start with the separator line below.)

  1. This has nothing to do with network interception.

  2. This is not just about login jumps, but a special scenario requirement that is elicited by login jumps. It’s the problem of delayed task processing with preconditions.

  3. Because I did not find a better plan, so I did this design. Hope to have a better plan for friends to leave a message.

In addition, I received feedback from some friends that the task logic was not concise enough. The implementation of the second edition is hereby modified. The core code is as follows

Ps: One of the original intentions of the design at that time was to consider the possibility of nested target tasks in the preconditions. But now after thinking for a long time, I still have no idea of the possible business scenario. Since technology exists for business, nested tasks are removed. Please let me know if any of your friends have such a scene.

/** * Created by Jinyabo on 13/12/2017. ** Created by Jinyabo on 13/12/2017. */ public class SingleCall {CallUnit CallUnit = new CallUnit(); public SingleCall addAction(Action action){ clear(); callUnit.setAction(action);returnthis; } public SingleCall addValid(Valid Valid){// Add only invalid ones.if(valid.check()){
            return this;
        }
        callUnit.addValid(valid);
        return this;
    }

    public void doCall(){// If the last valid message does not pass, return it directlyif(callUnit.getLastValid() ! = null && ! callUnit.getLastValid().check() ){return; } // Execute the actionif(callUnit.getValidQueue().size() == 0 && callUnit.getAction() ! = null){ callUnit.getAction().call(); / / to empty the clear (); }else{// Perform validation. Valid valid = callUnit.getValidQueue().poll(); callUnit.setLastValid(valid); valid.doValid(); } } public voidclear(){ callUnit.getValidQueue().clear(); callUnit.setAction(null); callUnit.setLastValid(null); } // SingleCall public static SingleCallgetInstance() {
        returnSingletonHolder.mInstance; } // Static inner class, mInstance is not initialized when the Singleton class is loaded for the first time, Private static class SingletonHolder {private static SingleCall mInstance = new SingleCall(); }}Copy the code

In addition, the author also summarizes the following application scenarios according to his usual business requirements.

So here’s the benefit of doing that.

1. Fully support all of the above scenarios. You don’t have to make a special judgment.

2. The yellow area in the figure is executed in the context where the main interface is located. The logic is in the current interface, not in the irrelevant interface. Clear responsibilities were achieved.

3. Easier to call.

If the introduction is not clear, look directly at the code. It’s really quite simple.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — this is the dividing line — — — — — — — — — — — — — — — — — — — — — — — — — —

A typical requirement is often encountered in projects, that is, when users need to enter interface A, they need to check whether the user has logged in first. If not, they need to enter the login interface first. If the login succeeds, they can directly jump to interface A.

Requirements definition

Therefore, there are two requirements: 1. Automatically redirect to the login page; 2. Automatically redirect to target A after successful login

If we directly determine whether the user is logged in or not, remind the user to log in. Also did not let the user login success and then directly jump to the target interface, such user experience is afraid can not meet the requirements of a high force grid programmer. So, let’s think about, how can we do this more gracefully?

Of course, before we start, we can learn how others do it, after all, we can stand on the shoulders of giants to see further.

Think about possible solutions

The first solution we came up with was interceptors. If we can add an interceptor before operation when entering the A interface, wouldn’t we be able to make the judgment before entering the A boundary?

After Google, find two solutions.

A, Android interceptor (you can click to view)

This scheme is annotated. When entering the target interface A, judge whether there is A specified interceptor. If so, check whether the interceptor requirements are met. If not, execute the interceptor processing.

This solution is slightly different from our requirements, so the disadvantages of this solution are as follows: 1. It uses inheritance to insert the invoke callback method. Due to the single-inheritance nature of Java, it can be difficult to adjust if there is already a base class in the project. Too invasive.

2. In this scheme, in the case of no login, the target A page has actually been entered. The corresponding initialization has already been performed. If the login is not successful, the work is in vain. If the target A interface requires login to access, this scheme does not meet the requirements.

B. We use the routing framework directly, refer to Ali’s ARouter scheme, and see that we can insert interceptors on fixed routes. Here is an article on the use of Ali ARouter interceptor and source code parsing

After reading the article, the interceptor implementation is very elegant, but it is still not what we want. As soon as the interceptor is done, the target method is executed. There is no waiting in between. So there is no way to perform our login operation. So I passed.

Looking back, the interceptor didn’t seem to be able to do what we wanted directly, because we needed to insert a validation action (such as entering the login screen) and perform something to ensure that the validation action passed before we could actually get to our target screen.

In fact, if we were to simply implement this functionality, it would be easy to imagine loading an intent for a target in the intent when entering the login screen. If the login is successful, check whether there is a target target, if so, jump to the target target.

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

This approach is straightforward and understandable, but the most obvious problem is that it leads to a lot of business decisions that are not relevant to you. So let’s go ahead and Google and see if we can do something similar and implement something a little more elegant?

Android login judge, after successful login to help you accurately jump to the target activity this page is relatively large, seems to be a more reliable method. So let’s take a look at what it does.

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, "No Activity to jump to", 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);  
    }  
Copy the code

The core code that we see above is to encapsulate a LoginCarrier. If you are not logged in, pass the LoginCarrier to the login screen. Upon successful login, the invoke() method is triggered. Essentially the same idea we had above.

After reading, or feel that the implementation is not perfect, always feel some shortcomings. For example,

1. Too much logic is still being invaded in the login interface (this seems inevitable, but can it be simpler?)

2. Poor scalability. For example, IF I want to buy a gift, I need to log in, and then jump to the recharge interface and come back after recharge.

That in the end there is no better implementation of the scheme, Google, found that there is no reliable scheme for the time being, so it is better to rely on their own, since they can not find the right scheme, then think about it, do it yourself.

First, let’s go back to our requirements. We need to implement a targeted approach. But the target method requires a precondition to execute, and there may be more than one, and it is not an immediate one.

So the data model that we abstracted from the requirements should be.

Public class CallUnit {private Action Action; Private Queue<Valid> validQueue = new ArrayDeque<>(); // Last execution valid private valid lastValid; }Copy the code

Then the target action is an executor. Responsible for implementing target methods.

public interface Action {
    void call();
}
Copy the code

The validation operation validQueue holds an validation queue. The Valid validation model is

Public interface Valid {/** * specifies whether the validator requirements are met. If not, the validator is executeddoValid () method. If so, the target action.call * @ is executedreturn*/ boolean check(); // To perform pre-validation actions, such as jumping to the login screen. (Verification was not completed.) voiddoValid();
}

Copy the code

Then the whole logic would be better expressed in a picture.

Next, we’ll look at the code implementation based on the diagram.

As a first step, we need to construct a CallUnit. For example, if we need to jump to the discount screen, we must log in and have a discount code.

So here, we have two validation models, one for logging in and one for getting a discount.

public class DiscountValid implements Valid { private Context context; public DiscountValid(Context context) { this.context = context; } /** ** @return
     */
    @Override
    public boolean check() {
        returnUserConfigCache.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() {
        returnUserConfigCache.isLogin(context); } / * * *if check() return false. then doValid was called
     */
    @Override
    public void doValid() { LoginActivity.start((Activity) context); }}Copy the code

Then we need to construct an executor. Implement the Action interface directly in the current Activity. For example, we implement it in MainActivity.

    @Override
    public void call() {/ / it is our goal behavior OrderDetailActivity startActivity (MainActivity. This,"1234");
    }
Copy the code

Next, we can construct a CallUnit object and execute it.

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

So what does doCall do?

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

Copy the code

Notice that we are calling postCallUnit() through the ActionManager singleton. Let’s see what this singleton does

public class ActionManager {

    static ActionManager instance = new ActionManager();

    public static ActionManager instance() {

        returninstance; } Stack<CallUnit> delaysActions = new Stack<>(); . }Copy the code

This singleton maintains a stack of callunits, indicating that we support a target behavior embedded within a target behavior. But this need is probably rare. But it’s supported by design.

Back again, what does postCallUnit() do?

/** * Public void postCallUnit(callUnit callUnit) {// Clear all actions delaysactions.clear ();  // execute check callunit.check (); // Jump directly to the target methodif (callUnit.getValidQueue().size() == 0) {
            callUnit.getAction().call();
        } else{// Add delaysactions.push (callUnit); Valid valid = callUnit.getValidQueue().peek(); callUnit.setLastValid(valid); Valid.dovalid (); }}Copy the code

If yes, execute the target method directly. If no, execute the doValid method. A reference to the current VALID is saved so that valid can be verified later. If no, the next round of authentication is not allowed.

At this point, we know that we have triggered the executor and are well into the executor for login authentication. Login because this operation requires the user to manually trigger is completed, we just guide users to the login screen (the login operation can also code completion, of course, there is no need to jump page), because we wait for user input, we will stop here for validation of the model, if the login is successful, we need to make the whole validation model running again, So after validation, it is always necessary to turn on the validation model manually.

For example, after a successful login, we need to call callUnit.recall () :

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(LoginActivity.this,"Login successful",Toast.LENGTH_SHORT).show();
                UserConfigCache.setLogin(LoginActivity.this, true); // The deferred action method is implemented here. CallUnit.reCall(); finish(); }});Copy the code

Let’s look at how callunit.recall () is executed

    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()); // If the delay is valid, the delay is validif(validQueue.size() == 0) { callUnit.getAction().call(); // Remove the task from delaysactions. remove(callUnit); }else{ Valid valid = callUnit.getValidQueue().peek(); callUnit.setLastValid(valid); Valid.dovalid (); }}}}Copy the code

Actionmanager.instance ().checkValid() is called to check whether the last valid was executed successfully. If not, an exception will be raised. The next VALID message can be executed only after check() is true. Don’t call the callUnit.recall () method if you never want the target behavior to execute past. If the last VALID execution is successful, the next VALID will be called. After all valid execution is complete, the execution of callUnit.getAction().call() starts. Finally enter the order discount interface.

Ps: The project also implements the annotation call. But the premise is that all validation models do not need to pass in additional parameters. Look at the code

** @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(); }}Copy the code

The demonstration flow chart is as follows

Only login authentication is required

Both login and coupon verification are required


The code address

Finally put down the complete code link library, if it helps you, remember star oh