Plagiarism is prohibited without consent, if you need to reprint, please note in a prominent position

preface

Login should be a very common function in application development, generally there are two kinds of login in the application, one is to enter the application must log in to use (such as wechat and QQ, etc.), the other is to log in when you need to log in (such as Taobao and Jingdong, etc.). Most of the cases I encounter in my work are in the second case. For the second login, I used to judge whether to log in or not by if(){}else(), but this will make the code bloated when the project structure becomes too large. Judging the user login status is a very frequent operation, so FOR this aspect I consider whether there is a solution that can be very convenient to judge the login status and make the code very simple.

There are two options. One is to hook AMS to intercept the intent in startActivity and judge whether to log in when the activity is started. If there is no dynamic replacement for the intent, the other is to add the code fragment to judge the login through AOP implementation. Hook is compatible with the system, so it needs to consider whether the API of each version is changed. However, there is no compatibility problem between the implementation method of AOP and the version, so AOP is finally adopted to realize the centralized login of APP.

Use of a centralized login architecture

The reason I’m going to talk about architecture first is because you won’t be interested in learning how to implement it until you know how convenient it is to use it. Ok, first to use demo to show you!

After watching the GIF, do you think this is not a very simple demo, by judging the login status jump to different pages, what difficult ah! The demo is pretty simple, but as you look down at the code, you’ll see how cool the code implementation is. Here’s the code: we initialize it in the Application (the login event can’t be received until after initialization, so the earlier the better).

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        LoginSDK.getInstance().init(this, new ILogin() {
            @Override
            public void login(Context applicationContext, int userDefine) {
                switch (userDefine) {
                    case 0:
                        startActivity(new Intent(applicationContext, LoginActivity.class));
                        break;
                    case 1:
                        Toast.makeText(applicationContext, "You have not logged in, please log in and execute.", Toast.LENGTH_SHORT).show();
                        break;
                    case 2:
                        new AlertDialog.Builder(MyApplication.this)...
                        break;
                    default:
                        Toast.makeText(applicationContext, "Execution failed because you are not logged in!", Toast.LENGTH_SHORT).show();
                        break;
                }
            }

            @Override
            public boolean isLogin(Context applicationContext) {
                returnSharePreferenceUtil.getBooleanSp(SharePreferenceUtil.IS_LOGIN, applicationContext); }}); }}Copy the code

As you can see, the initialization method implements the ILogin interface. The ILogin interface has two methods, the first login() is used to receive the login event, and the second isLogin is used to determine the login status. These two methods are left to the user to implement, improving the usability of the architecture. All of our login requests are called back to the ILogin interface, which means there is only one unified entry point for login events, which is the core benefit of our centralized login architecture.

Ok, let’s start with the following.

Example 1:
@loginFilter (userDefine = 0) public void onClick(View View) {startActivity(new) Intent(this, SecondActivity.class)); }Copy the code

The above code is to listen for a Button click event, and then add the @loginFilter annotation to see that the method implementation just jumps to SecondActivity. There is no login logic, but with this annotation we can check whether the method is logged in at runtime. If it is not logged in, it will interrupt the method execution. Instead, we call our own login() method in init() in MyApplication, login(Context applicationContext, int userDefine) where userDefine is a value for the user to define, To distinguish which login method is used. Isn’t that easy? Let’s look at example 2:

Example 2:

If we don’t want to bother adding @loginFilter () annotations to buttons that need to check login status, and want to start an Activity to automatically check whether it’s logged in or not, and call back to our ILogin interface if it’s not logged in, then you just need to create a LoginFilterActivity like this:

// Demo 2 filter login directly, without annotations, LoginFilterActivity public class LoginFilterActivity extends AppCompatActivity {@override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);if(! LoginAssistant.getInstance().getiLogin().isLogin(getApplicationContext())) { //TODO: You can do whatever you want, in this case I let the page end and prompt the user toasts. MakeText (this,"Not logged in!", Toast.LENGTH_SHORT).show(); finish(); }}}Copy the code

We then make the Activity that requires a login to enter inherit from LoginFilterActivity. If UserActivity inherits LoginFilterActivity, when the user is not logged in, we start UserActivity and call back to our ILogin interface. Isn’t that convenient? That’s the centralized login architecture we’ll talk about today.

Now, let’s talk about how to implement this architecture.

AOP principle

Let’s take a look at AOP first, because the architecture is implemented based on AOP programming.

  • What is the AOP

AOP is an abbreviation of Aspect Oriented Programming, that is, Aspect Oriented Programming. AOP is two different ways of thinking with object Oriented Programming (OOP), and can also be seen as a supplement to OOP. Traditional OOP development advocates modularity of functions, etc., while AOP is suited for addressing a single type of problem. AOP ideas are not the focus of this article, if you do not understand AOP ideas, here I recommend a very good article, about Java AOP & Spring AOP principles and implementation

  • AspectJ is introduced

AspectJ is a framework for faceted programming that extends the Java language and defines the syntax for implementing AOP. As we know, the default javac compilation tool is used to compile.java files into.class files, and AspectJ will replace javac with a compilation tool that complies with the Java bytecode encoding specification. When compiling.java files into.class files, Code is dynamically inserted to achieve uniform treatment of a particular class of things. For example, there are many button onClick events in the application that need to check whether there is a login or not. If there is no login, it needs to be logged in to continue executing. For this type of problem, a relatively stupid approach is to explicitly determine the login status in each onClick method, which is too cumbersome. Using AOP, we would add an annotation to each onClick method that the compiler would recognize at compile time and generate code to detect the login status. Now, for those of you who are not quite AOP savvy, I’m going to show you how to use AOP to implement a unified, centralized login.

AOP implements centralized login

  • Aspectj environment setup

So first of all, let’s import the AspectJ JAR package, which you can find on the web, or you can just go to my demo,LoginArchitecture AOP implements centralized login to Github link point me. Jar package import in demo:

After importing the jar, you need to configure app.gradle as follows:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org. Aspectj: aspectjtools: 1.8.8'
        classpath 'org. Aspectj: aspectjweaver: 1.8.8'}}Copy the code

Then add the following code at the end of the file:

The import org. Aspectj. Bridge. Called IMessage -- the import org. Aspectj. Bridge. The MessageHandler import org. Aspectj. View the ajc. Main / / 1 final deflog= project. The logger final def variants = project. The android. ApplicationVariants variants. All {variant - > / / 2if(! variant.buildType.isDebuggable()) { log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return; JavaCompile = variable. JavaCompile JavaCompile. DoLast {String[] args = ["-showWeaveInfo"."1.8"."-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler);
        //标注4
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break;
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break; }}}}Copy the code

For aspectJ configuration, first look at annotation 1 to obtain the log printing tool and build configuration, and then annotation 2 to check whether debug, if release and return are removed, annotation 3 means that the AspectJ configuration takes effect. Note 4 is used to print information at compile time, such as warnings, errors and so on. These things are also available on the Internet. If you don’t understand them, you can go to search for them.

  • Section code writing

Ok, now that we have configured the above, we can start writing the code. First, we define an annotation LoginFilter to annotate methods so that the compiler detects methods that need to be cut at compile time.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginFilter {

    int userDefine() default 0;

}
Copy the code

As you can see, I added a userDefine in the annotation to give the user a custom implementation, such as different login processes based on the userDifine value.

Then, write the LoginSDK file to initialize and receive login events as follows:

public class LoginSDK { public void init(Context context, ILogin iLogin) { applicationContext = context.getApplicationContext(); LoginAssistant.getInstance().setApplicationContext(context); LoginAssistant.getInstance().setiLogin(iLogin); } / /... }Copy the code

Then, create a new LoginFilterAspect.java file to handle methods that add LoginFilter annotations, and make uniform cuts to those methods.

@Aspect
public class LoginFilterAspect {
    private static final String TAG = "LoginFilterAspect";

    @Pointcut("execution(@com.xsm.loginarchitecture.lib_login.annotation.LoginFilter * *(..) )")
    public void loginFilter() {}

    @Around("loginFilter()") public void aroundLoginPoint(ProceedingJoinPoint joinPoint) throws Throwable {// Annotation 1 ILogin ILogin = LoginAssistant.getInstance().getiLogin();if (iLogin == null) {
            throw new NoInitException("LoginSDK not initialized!"); } // Signature Signature = joinPoint.getSignature();if(! (signature instanceof MethodSignature)) { throw new AnnotationException("LoginFilter annotations can only be used on methods.");
        }
        MethodSignature methodSignature = (MethodSignature) signature;
        LoginFilter loginFilter = methodSignature.getMethod().getAnnotation(LoginFilter.class);
        if (loginFilter == null) {
            return;
        }

        Context param = LoginAssistant.getInstance().getApplicationContext();
        //标注3
        if (iLogin.isLogin(param)) {
            joinPoint.proceed();
        } else{ iLogin.login(param, loginFilter.userDefine()); }}}Copy the code

There’s not a lot of code, so let’s explain it all. Start with the loginFilter method, which adds the @pointcut annotation and specifies the path to the loginFilter annotation. @pointcut annotations, including the @around annotation on the aroundLoginPoint() method, are apis defined by AspectJ. The @pointcut annotation represents a Pointcut, specifically which methods need to be executed “AOP”. Execution () specifies the path to the LoginFilter annotations, and the method that adds the LoginFilter annotations is the slice that needs to be processed. The @around annotation indicates that the method execution time can be cut Before and After, such as @before, @after, and so on. @before means that the method is processed Before execution, and @after vice versa. The aroundLoginPoint(ProceedingJoinPoint) method is an implementation of the pointcut. The ProceedingJoinPoint parameter means a circular notification, and the signature of the method can be obtained in this class.

Mark 1

Looking first at annotation 1, we get the user-implemented ILogin class and throw an exception if init() is not called to set the initialization.

With 2

Annotation 2 gives the method’s signature, methodSignature, and then the @LoginFilter annotation. If the annotation is empty, you don’t go further.

With 3

The isLogin() method of iLogin is left for the user to implement. If the isLogin method is logged in, the method body will continue to call the method until it is finished. If the iLogin login method is not logged in, the userDefine will be passed. The login method is implemented by the user himself.

Now that we have covered the handling of the aspect code, we can build the project, which will generate a.class file compiled by the AspectJ compiler in the \build\intermediates\classes\debug folder. Skip (View v) : skip(View v) : skip(View v)

    @LoginFilter
    public void onClick(View view) {
        JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, view);
        skip_aroundBody1$advice(this, view, var3, LoginFilterAspect.aspectOf(), (ProceedingJoinPoint)var3);
    }
Copy the code

StartActivity (New Intent(this, secondActivity.class)); In fact, it encapsulates the execution of our method. At runtime, after the target class is loaded, the proxy class is dynamically generated for the interface, and the sections are woven into the proxy class, so as to achieve uniform processing of the method. Note: There is an interlude in this, which is when I am demonstrating

In addition, some students in the comments pointed out that single sign-on mechanism can deal with the trouble, so I added the unified access to background token authentication invalidation in LoginSDK. I posted the usage:

LoginSDK.getInstance().serverTokenInvalidation(TOKEN_INVALIDATION);
Copy the code

If you want to know more about it, you can refer to the demo.

summary

At this point, do you think it’s easy to log in through section processing? In fact, as long as we are familiar with the API of section programming, we can use such a simple method to do specific processing for a batch of things with a certain feature. I have put the demo of this project on Github. If you are interested in this article, you can clone it and get familiar with it and apply it to the project. Demo address, welcome star, my Github has many interesting libraries, welcome to visit oh

Contact: [email protected]