Re-learning Android Basics Series (4): Advanced Supplement for Architecture Dynamic Programming: Compile-time Processing

foreword

This series of articles mainly summarizes the technical articles of the big guys. Android基础部分As a qualified Android development engineer, we must be proficient in java and android. Let’s talk about these in this issue~

[非商业用途,如有侵权,请告知我,我会删除]

DD: Android进阶开发各类文档,也可关注公众号<Android苦做舟>获取。

1.Android高级开发工程师必备基础技能
2.Android性能优化核心知识笔记
3.Android+音视频进阶开发面试题冲刺合集
4.Android 音视频开发入门到实战学习手册
5.Android Framework精编内核解析
6.Flutter实战进阶技术手册
7.近百个Android录播视频+音视频视频dome
.......

Architecture dynamic programming technology principle

Advanced Dynamic Programming: Compile-Time Processing

1. Aspect-oriented programming

1.1 AOP definition

AOP: The abbreviation of Aspect Oriented Programming, which means aspect-oriented programming, a technology that achieves unified maintenance of program functions through pre-compilation and dynamic proxy during runtime. AOP is the continuation of OOP thought. AOP can be used to isolate various parts of business logic, thereby reducing the coupling degree between various parts of business logic, improving the reusability of programs, and improving the efficiency of development at the same time.

Why learn AOP?

The application scenarios of AOP are very extensive. During the interview process of some senior engineers or architects, it appears more frequently.

1.2 The development route of programming ideas

1、POP

POP: The abbreviation of Procedure Oriented Programming, that is, process-oriented programming, is a process-centric programming idea.

Process-oriented is to analyze the steps to solve the problem, and then use functions or methods to implement these steps step by step, and call functions or methods one by one when using them. This is process-oriented programming. In the beginning, it was all process-oriented programming. Process-oriented is the most practical way of thinking. Even if it is object-oriented programming, it also contains process-oriented programming ideas, because process-oriented is a basic programming way of thinking, and it considers how to realize the requirements from the actual point of view.

Insufficiency of POP: Process-oriented programming can only deal with some simple problems, but cannot deal with some complex problems. If the problem is very complicated, if you think about it all in terms of process, you will find that the process is very chaotic, and even the process cannot continue.

2、OOP

OOP: Acronym for Object Oriented Programming, that is, object-oriented programming.

Early computer programming was process-oriented because early programming was relatively simple. But with the development of time, more and more problems need to be dealt with, and the problems will become more and more complicated. At this time, process-oriented programming cannot be simply used, and object-oriented programming appears. In a computer, think of everything as a thing. Objects in the real world have some attributes and behaviors, which correspond to the attributes and methods in the computer.

Object-oriented programming is to decompose the things that constitute a problem into individual objects. The purpose of establishing an object is not to complete a step, but to describe the behavior of a certain thing in the entire problem-solving steps.

We use a building as an example to illustrate the shortcomings of OOP.

We compare the system to a building, classes or objects are bricks, bricks form a wall, multiple walls form a room, and multiple rooms form a building. This is like the function of a module is realized by multiple classes, the module forms a certain service, and multiple services form a complete system. The completion of a system development does not mean that it is really completed. There will definitely be changes in various requirements in the future. After the requirements change, the code must be modified. The code is in the class, which is equivalent to modifying the class. If it is a small-scale modification, the impact is not too great. If it is a large-scale modification, the impact will be relatively large. Even if each modification is small, the impact can be large if it is made frequently. It will cause system instability. We concluded that the class should be fixed and should not be modified frequently, or even not allowed to be modified. This is why there are so many design principles and design patterns. Most of the design patterns are designed to solve this kind of problem, that is, to extend the function without modifying the class.

Insufficiency of OOP: The generation of new requirements will lead to continuous modification of the program code, which may easily cause program instability.

If you know OOP very well, then we should know that from the perspective of object organization, the classification method is based on the inheritance relationship, which we call vertical. If you only use OOP thinking, it will bring two problems: 1. Common problems. 2. The problem of extension is that it is more difficult when the existing class needs to be extended.

The difference between OOP and POP:

In contrast to process-oriented, the object-oriented approach minimizes things into objects, including properties and methods. When the scale of the program is relatively small, process-oriented programming still has some advantages, because at this time the flow of the program is relatively easy to sort out. Take going to work in the morning as an example. The process is to get up, get dressed, brush your teeth, wash your face, and go to the company. Each step is completed in order. We only need to follow the steps to implement the methods step by step, and finally call the implemented methods in sequence. This is process-oriented development.

If object-oriented programming is used, we need to abstract an employee class, which has four methods of getting up, dressing, brushing teeth and washing face, and going to the company. However, in order to realize the need to go to work in the morning, the four methods must be called in sequence. At the beginning, we thought about the requirement according to the process-oriented thinking, and then abstracted several methods according to the object-oriented thinking. Finally, to realize this requirement, we still need to implement it in a process-oriented order.

The difference between object-oriented and process-oriented is only the difference in the way of thinking about problems. In the end, you will find that when you realize this requirement, even if you use object-oriented thinking to abstract the employee class, you still have to use process-oriented to realize this requirement in the end.

3、AOP

AOP: Abbreviation for Aspect Oriented Programming, that is, aspect-oriented programming. It is a supplement to OOP, a technology that dynamically adds unified functions to programs without modifying the original class.

OOP focuses on dividing the required functions into different and relatively independent and well-encapsulated classes, relying on inheritance and polymorphism to define the relationship between each other. AOP can separate common requirements functions from unrelated classes. Many classes share a behavior. Once there is a change, you don’t need to modify many classes, you only need to modify this one class.

What is the aspect in AOP? Aspects refer to cross-cutting concerns. Look at the picture below:

OOP is to modularize state and behavior. The picture above is a shopping mall system. We use OOP to vertically divide the system into order management, commodity management, and inventory management modules. In this system, we need to perform authorization verification. Orders, commodities, and inventory are all business logic functions, but these three modules all require some common functions, such as authorization verification, logging, and so on. It is impossible for us to write authorization verification in every module, and authorization verification does not belong to specific business. It is actually a functional module and spans multiple business modules. You can see that it is horizontal here, which is the so-called cut surface. In layman's terms, AOP is to extract common functions. If these common functions change in the future, we only need to modify the code of these common functions, and there is no need to change other places. The so-called aspect is to only focus on general functions, not business logic, and not modify the original class.

Advantages of AOP:

  • Extract general functions from business logic, improve code reusability, and facilitate later maintenance and expansion.
  • During software design, common functions (sections) are drawn out, which is conducive to the modularization of software design and reduces the complexity of software architecture.

Disadvantages of AOP:

  • AOP is a supplement to OOP thinking, it cannot exist alone. It is impossible to design a system using AOP alone. When designing a system, if the system is relatively simple, you can only use POP or OOP to design. If the system is complex, you need to use AOP thinking. First, use POP to sort out the entire business process, then organize classes and modules according to the POP process, and finally use AOP to extract general functions.

The difference between AOP and OOP:

  • Different goal-oriented: OOP is oriented to the noun field (abstracting a thing, such as students and employees, these are nouns). AOP is oriented to the field of verbs (such as authentication, logging, these are actions or behaviors).
  • The ideological structure is different: OOP is vertical (inheritance is the main line, so it is vertical). AOP is horizontal.
  • Focus on different aspects: OOP focuses on the division of business logic units, and AOP focuses on a certain step or stage in the business processing process.

The three ideas of POP, OOP, and AOP are complementary to each other. In the development process of a system, these three programming ideas are indispensable.

1.3 Realize AOP

We have explained some theoretical knowledge about AOP above, so how to implement it in the code?

There are two ways to implement AOP:

  • Static proxy implementation. The so-called static proxy means that we write the proxy object ourselves.
  • Dynamic proxy implementation. The so-called dynamic proxy is to generate a proxy object when the program is running.
1.3.1 Static proxy

Implementing static proxy requires the use of two design patterns: decorator pattern and proxy pattern.

Decorator pattern: Allows adding new functionality to an existing object without changing the structure of the existing object. It belongs to the structural design pattern, which is used as a kind of packaging of the existing class. First, a decoration class will be created to wrap the original class and provide additional functions while maintaining the integrity of the class. See the example below.

We first create a User class:

using` `System;``using` `System.Collections.Generic;``using` `System.Linq;``using` `System.Text;``using` `System.Threading.Tasks;` `namespace` `StaticDemo.Model``{``  ``public` `class` `User``  ``{``    ``public` `string` `Name { ``get``; ``set``; }``    ``public` `string` `Password { ``get``; ``set``; }``  ``}``}

Then we create an account service interface, which has a method for registering a user:

using` `StaticDemo.Model;` `namespace` `StaticDemo.Services``{``  ``/// <summary>``  ``/// 接口``  ``/// </summary>``  ``public` `interface` `IAccountService``  ``{``    ``/// <summary>``    ``/// 注册用户``    ``/// </summary>``    ``/// <param name="user"></param>``    ``void` `Reg(User user);``  ``}``}

Then create a class to implement the above interface:

using` `StaticDemo.Model;``using` `System;` `namespace` `StaticDemo.Services``{``  ``/// <summary>``  ``/// 实现IAccountService接口``  ``/// </summary>``  ``public` `class` `AccountService : IAccountService``  ``{``    ``public` `void` `Reg(User user)``    ``{``      ``// 业务代码 之前 或者之后执行一些其它的逻辑``      ``Console.WriteLine($``"{user.Name}注册成功"``);``    ``}``  ``}``}

We are creating a decorator class:

using` `StaticDemo.Model;``using` `StaticDemo.Services;``using` `System;` `namespace` `StaticDemo``{``  ``/// <summary>``  ``/// 装饰器类``  ``/// </summary>``  ``public` `class` `AccountDecorator : IAccountService``  ``{``    ``private` `readonly` `IAccountService _accountService;` `    ``public` `AccountDecorator(IAccountService accountService)``    ``{``      ``_accountService = accountService;``    ``}` `    ``public` `void` `Reg(User user)``    ``{``      ``Before();``      ``// 这里调用注册的方法,原有类里面的逻辑不会改变``      ``// 在逻辑前面和后面分别添加其他逻辑``      ``_accountService.Reg(user);``      ``After();``    ``}` `    ``private` `void` `Before()``    ``{``      ``Console.WriteLine(``"注册之前的逻辑"``);``    ``}` `    ``private` `void` `After()``    ``{``      ``Console.WriteLine(``"注册之后的逻辑"``);``    ``}``  ``}``}

We will find that the decorator class also implements the IAccountService interface. Finally we call in the Main method:

using` `StaticDemo.Model;``using` `StaticDemo.Services;``using` `System;` `namespace` `StaticDemo``{``  ``class` `Program``  ``{``    ``static` `void` `Main(``string``[] args)``    ``{``      ``// 实例化对象``      ``IAccountService accountService = ``new` `AccountService();``      ``// 实例化装饰器类,并用上面的实例给构造方法传值``      ``var account = ``new` `AccountDecorator(accountService);``      ``var user = ``new` `User { Name = ``"Rick"``, Password = ``"12345678"` `};``      ``// 调用装饰器类的注册方法,相当于调用实例化对象的注册方法``      ``account.Reg(user);` `      ``Console.ReadKey();``    ``}``  ``}``}

Running results:

Let's take a look at how to use the proxy mode to achieve.

Proxy mode: that is, one class represents the functionality of another class. We will create a proxy class, which is basically the same as the decorator class. Take a look at the code:

using` `StaticDemo.Model;``using` `StaticDemo.Services;``using` `System;` `namespace` `StaticDemo``{``  ``/// <summary>``  ``/// 代理类``  ``/// </summary>``  ``public` `class` `ProxyAccount : IAccountService``  ``{``    ``private` `readonly` `IAccountService _accountService;` `    ``/// <summary>``    ``/// 构造函数没有参数``    ``/// 直接在里面创建了AccountService类``    ``/// </summary>``    ``public` `ProxyAccount()``    ``{``      ``_accountService = ``new` `AccountService();``    ``}` `    ``public` `void` `Reg(User user)``    ``{``      ``before();``      ``_accountService.Reg(user);``      ``after();``    ``}` `    ``private` `void` `before()``    ``{``      ``Console.WriteLine(``"代理:注册之前的逻辑"``);``    ``}` `    ``private` `void` `after()``    ``{``      ``Console.WriteLine(``"代理:注册之后的逻辑"``);``    ``}``  ``}``}

Called in the Main method:

using` `StaticDemo.Model;``using` `StaticDemo.Services;``using` `System;` `namespace` `StaticDemo``{``  ``class` `Program``  ``{``    ``static` `void` `Main(``string``[] args)``    ``{``      ``#region 装饰器模式``      `` 实例化对象``      ``//IAccountService accountService = new AccountService();``      `` 实例化装饰器类,并用上面的实例给构造方法传值``      ``//var account = new AccountDecorator(accountService);``      ``//var user = new User { Name = "Rick", Password = "12345678" };``      `` 调用装饰器类的注册方法,相当于调用实例化对象的注册方法``      ``//account.Reg(user);``      ``#endregion` `      ``#region 代理模式``      ``var account = ``new` `ProxyAccount();``      ``var user = ``new` `User { Name = ``"Tom"``, Password = ``"12345678"` `};``      ``account.Reg(user);``      ``#endregion` `      ``Console.ReadKey();``    ``}``  ``}``}

Running results:
insert image description here
Some people may find that the decorator class is very similar to the proxy class, and the functions are exactly the same, only the constructor is different. So is there any difference between decorator pattern and proxy pattern? Some things seem to have little difference in form, but in fact they are very different. They are indeed the same in form, whether they are decorator classes or proxy classes, they must implement the same interface, but there are still differences when they are used.

The Decorator pattern is concerned with dynamically adding methods on an object, while the Proxy pattern is concerned with controlling access to the object. Simply put, using the proxy pattern, our proxy class can hide the specific information of a class. var account = new ProxyAccount(); Just look at this code and not the source code, so you don't know who is the proxy in it.

When using the proxy mode, we often create an instance of an object in the proxy class: _accountService = new AccountService(). And when we use the decorator pattern, we usually pass the original object as a parameter to the constructor of the decorator. To put it simply, when using the decorator pattern, we can clearly know who is decorating, and more importantly, the proxy class is hard-coded, and the relationship is determined at compile time. The decorator is determined at runtime.

1.3.2 Dynamic proxy

There are also two ways to implement dynamic proxy;

  • By way of code weaving. For example PostSharp third-party plugins. We know that the .NET program will eventually be compiled into an IL intermediate language. When compiling the program, PostSharp will dynamically modify the IL and add code in the IL. This is the way of code weaving.
  • Implemented by reflection. There are many methods implemented through reflection, and there are also many frameworks that implement AOP, such as Unity, MVC filter, Autofac, etc.

Let's first take a look at how to use PostSharp to implement dynamic proxy. PostSharp is a paid third-party plug-in.

First create a new console application, and then create an order business class:

using` `System;` `namespace` `PostSharpDemo``{``  ``/// <summary>``  ``/// 订单业务类``  ``/// </summary>``  ``public` `class` `OrderBusiness``  ``{``    ``public` `void` `DoWork()``    ``{``      ``Console.WriteLine(``"执行订单业务"``);``    ``}``  ``}``}

Then call it in the Main method:

using` `System;` `namespace` `PostSharpDemo``{``  ``class` `Program``  ``{``    ``static` `void` `Main(``string``[] args)``    ``{``      ``OrderBusiness order = ``new` `OrderBusiness();``      ``// 调用方法``      ``order.DoWork();``      ``Console.ReadKey();``    ``}``  ``}``}

Running results:

At this time, a new requirement is put forward, to add a log function to record the execution of the business. According to the previous method, a log helper class needs to be defined:

using` `System;``using` `System.IO;` `namespace` `PostSharpDemo``{``  ``public` `class` `LgoHelper``  ``{``    ``public` `static` `void` `RecoreLog(``string` `message)``    ``{``      ``string` `strPath = AppDomain.CurrentDomain.BaseDirectory+``"\log.txt"``;``      ``using``(StreamWriter sw=``new` `StreamWriter(strPath,``true``))``      ``{``        ``sw.WriteLine(message);``        ``sw.Close();``      ``}``    ``}``  ``}``}

If you don't use AOP, we need to instantiate the Loghelper object where the log is recorded, and then record the log:

using` `System;` `namespace` `PostSharpDemo``{``  ``/// <summary>``  ``/// 订单业务类``  ``/// </summary>``  ``public` `class` `OrderBusiness``  ``{``    ``public` `void` `DoWork()``    ``{``      ``// 记录日志``      ``LgoHelper.RecoreLog(``"执行业务前"``);``      ``Console.WriteLine(``"执行订单业务"``);``      ``LgoHelper.RecoreLog(``"执行业务后"``);``    ``}``  ``}``}

Let's run the program again and check the results:

let's look at the log content:

this modification can realize the function of recording logs. But the above method will modify the original existing code, which violates the principle of opening and closing. Moreover, adding logs is not a change in business requirements, and the business code should not be modified. The following uses AOP to achieve. First install PostSharp, search directly in NuGet, and then install it:

then define a LogAttribute class that inherits from OnMethodBoundaryAspect. This Aspect provides connection point methods such as entry and exit functions. In addition, "[Serializable]" must be set on Aspect, which is related to PostSharp's internal life cycle management of Aspect:

using` `PostSharp.Aspects;``using` `System;` `namespace` `PostSharpDemo``{``  ``[Serializable]``  ``[AttributeUsage(AttributeTargets.Method, AllowMultiple = ``false``, Inherited = ``true``)]``  ``public` `class` `LogAttribute: OnMethodBoundaryAspect``  ``{``    ``public` `string` `ActionName { ``get``; ``set``; }``    ``public` `override` `void` `OnEntry(MethodExecutionArgs eventArgs)``    ``{``      ``LgoHelper.RecoreLog(ActionName + ``"开始执行业务前"``);``    ``}` `    ``public` `override` `void` `OnExit(MethodExecutionArgs eventArgs)``    ``{``      ``LgoHelper.RecoreLog(ActionName + ``"业务执行完成后"``);``    ``}``  ``}``}

Then the Log feature is applied to the DoWork function:

using` `System;` `namespace` `PostSharpDemo``{``  ``/// <summary>``  ``/// 订单业务类``  ``/// </summary>``  ``public` `class` `OrderBusiness``  ``{``    ``[Log(ActionName =``"DoWork"``)]``    ``public` `void` `DoWork()``    ``{``      ``// 记录日志``      ``// LgoHelper.RecoreLog("执行业务前");``      ``Console.WriteLine(``"执行订单业务"``);``      ``// LgoHelper.RecoreLog("执行业务后");``    ``}``  ``}``}

After this modification, you only need to add a feature to the method, and the previous logging code can be commented out, so that the business logic code will not be modified any more. Run the program: look at the log: this realizes the AOP

function

.

We are looking at using Remoting to implement dynamic proxies.

First, create a User entity class:

namespace` `DynamicProxy.Model``{``  ``public` `class` `User``  ``{``    ``public` `string` `Name { ``get``; ``set``; }``    ``public` `string` `Password { ``get``; ``set``; }``  ``}``}

Then create an interface with a registration method:

using` `DynamicProxy.Model;` `namespace` `DynamicProxy.Services``{``  ``public` `interface` `IAccountService``  ``{``    ``void` `Reg(User user);``  ``}``}

Then create an implementation class for the interface:

using` `DynamicProxy.Model;``using` `System;` `namespace` `DynamicProxy.Services``{``  ``public` `class` `AccountService : MarshalByRefObject, IAccountService``  ``{``    ``public` `void` `Reg(User user)``    ``{``      ``Console.WriteLine($``"{user.Name}注册成功"``);``    ``}``  ``}``}

Then create a generic dynamic proxy class:

using` `System;``using` `System.Runtime.Remoting;``using` `System.Runtime.Remoting.Messaging;``using` `System.Runtime.Remoting.Proxies;` `namespace` `DynamicProxy``{``  ``public` `class` `DynamicProxy<T> : RealProxy``  ``{``    ``private` `readonly` `T _target;` `    ``// 执行之前``    ``public` `Action BeforeAction { ``get``; ``set``; }` `    ``// 执行之后``    ``public` `Action AfterAction { ``get``; ``set``; }` `    ``// 被代理泛型类``    ``public` `DynamicProxy(T target) : ``base``(``typeof``(T))``    ``{``      ``_target = target;``    ``}` `    ``// 代理类调用方法``    ``public` `override` `IMessage Invoke(IMessage msg)``    ``{``      ``var reqMsg = msg ``as` `IMethodCallMessage;``      ``var target = _target ``as` `MarshalByRefObject;` `      ``BeforeAction();``      ``// 这里才真正去执行代理类里面的方法``      ``// target表示被代理的对象,reqMsg表示要执行的方法``      ``var result = RemotingServices.ExecuteMessage(target, reqMsg);``      ``AfterAction();``      ``return` `result;``    ``}` `  ``}``}

We see that there are two generic delegates in this generic dynamic proxy class: BeforeAction and AfterAction. Pass in the proxy generic class through the constructor. Finally, call the Invoke method to execute the method of the proxy class.

Finally, we also need to create a proxy factory class to create proxy objects, and create dynamic proxy objects by calling dynamic proxies:

using` `System;` `namespace` `DynamicProxy``{``  ``/// <summary>``  ``/// 动态代理工厂类``  ``/// </summary>``  ``public` `static` `class` `ProxyFactory``  ``{``    ``public` `static` `T Create<T>(Action before, Action after)``    ``{``      ``// 实例化被代理泛型对象``      ``T instance = Activator.CreateInstance<T>();``      ``// 实例化动态代理,创建动态代理对象``      ``var proxy = ``new` `DynamicProxy<T>(instance) { BeforeAction = before, AfterAction = after };``      ``// 返回透明代理对象``      ``return` `(T)proxy.GetTransparentProxy();``    ``}``  ``}``}

We finally call in the Main method:

using` `DynamicProxy.Model;``using` `DynamicProxy.Services;``using` `System;` `namespace` `DynamicProxy``{``  ``class` `Program``  ``{``    ``static` `void` `Main(``string``[] args)``    ``{``      ``// 调用动态代理工厂类创建动态代理对象,传递AccountService,并且传递两个委托``      ``var acount = ProxyFactory.Create<AccountService>(before:() =>``      ``{``        ``Console.WriteLine(``"注册之前"``);``      ``}, after:() =>``      ``{``        ``Console.WriteLine(``"注册之后"``);``      ``});` `      ``User user = ``new` `User() ``      ``{``       ``Name=``"张三"``,``       ``Password=``"123456"``      ``};``      ``// 调用注册方法``      ``acount.Reg(user);` `      ``Console.ReadKey();``    ``}``  ``}``}

Program running result:

In this way, the dynamic proxy is realized by using Remoting.

2. Bytecode instrumentation technology (ASM)

2.1 Preface

In the hotfix multi-Dex loading solution, there is a problem of CLASS_ISPREVERIFIED for systems below 5.0, and a solution to this problem is to introduce a class in another dex into the construction method of the class through ASM instrumentation, so as to avoid being Mark CLASS_ISPREVERIFIED label.

2.2 ASM framework

ASM is a framework that can analyze and manipulate bytecode, through which bytecode content can be dynamically modified. Using ASM can realize non-buried statistics, performance monitoring, etc.

2.3 Definition of bytecode instrumentation

During the Android compilation process, insert custom bytecode into the bytecode.

2.4 Timing of staking

Android packaging needs to go through: java file – class file – dex file. Through the Transform API provided by Gradle, you can get the class file before compiling it into a dex file, and then modify the bytecode through ASM, that is, bytecode insertion.

2.5 Implementation

The following implements instrumentation by customizing the Gradle plug-in to process class files.

2.5.1 Custom Gradle plugin

The specific steps of customizing the Gradle plug-in will not be introduced in detail here. You can refer to a previous article or consult other materials by yourself.

2.5.2 Processing Classes

The plug-in is divided into plug-in part (src/main/groovy), ASM part (src/main/java) ASMPlugin

class inherits from Transform and implements Plugin interface, registers in apply method, calls back and processes class in transform.

class ASMPlugin extends Transform implements Plugin<Project> {
    @Override
    void apply(Project project) {
        def android = project.extensions.getByType(AppExtension)
        android.registerTransform(this)
    }
    @Override
    String getName() {
        return "ASMPlugin"
    }
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }
    @Override
    boolean isIncremental() {
        return false
    }
    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        //处理class
    }
}

The main logic processing is in the transform method

@Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        println('--------------------ASMPlugin transform start--------------------')
        def startTime = System.currentTimeMillis()
        Collection<TransformInput> inputs = transformInvocation.inputs
        TransformOutputProvider outputProvider = transformInvocation.outputProvider
        //删除旧的输出
        if (outputProvider != null) {
            outputProvider.deleteAll()
        }
        //遍历inputs
        inputs.each { input ->
            //遍历directoryInputs
            input.directoryInputs.each {
                directoryInput -> handleDirectoryInput(directoryInput, outputProvider)
            }
            //遍历jarInputs
            input.jarInputs.each {
                jarInput -> handleJarInput(jarInput, outputProvider)
            }
        }
        def time = (System.currentTimeMillis() - startTime) / 1000
        println('-------------------- ASMPlugin transform end --------------------')
        println("ASMPlugin cost $time s")
    }

Process class files and jar files in transform

    /**
     * 处理目录下的class文件
     * @param directoryInput
     * @param outputProvider
     */
    static void handleDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
        //是否为目录
        if (directoryInput.file.isDirectory()) {
            //列出目录所有文件(包含子文件夹,子文件夹内文件)
            directoryInput.file.eachFileRecurse {
                file ->
                    def name = file.name
                    if (isClassFile(name)) {
                        println("-------------------- handle class file:<$name> --------------------")
                        ClassReader classReader = new ClassReader(file.bytes)
                        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                        ClassVisitor classVisitor = new ActivityClassVisitor(classWriter)
                        classReader.accept(classVisitor, org.objectweb.asm.ClassReader.EXPAND_FRAMES)
                        byte[] bytes = classWriter.toByteArray()
                        FileOutputStream fileOutputStream = new FileOutputStream(file.parentFile.absolutePath + File.separator + name)
                        fileOutputStream.write(bytes)
                        fileOutputStream.close()
                    }
            }
        }
        def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
        FileUtils.copyDirectory(directoryInput.file, dest)
    }
    /**
     * 处理Jar中的class文件
     * @param jarInput
     * @param outputProvider
     */
    static void handleJarInput(JarInput jarInput, TransformOutputProvider outputProvider) {
        if (jarInput.file.getAbsolutePath().endsWith(".jar")) {
            //重名名输出文件,因为可能同名,会覆盖
            def jarName = jarInput.name
            def md5Name = DigestUtils.md5Hex(jarInput.file.absolutePath)
            if (jarName.endsWith(".jar")) {
                jarName = jarName.substring(0, jarName.length() - 4)
            }
            JarFile jarFile = new JarFile(jarInput.file)
            Enumeration enumeration = jarFile.entries()
            File tempFile = new File(jarInput.file.parent + File.separator + "temp.jar")
            //避免上次的缓存被重复插入
            if (tempFile.exists()) {
                tempFile.delete()
            }
            JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tempFile))
            //保存
            while (enumeration.hasMoreElements()) {
                JarEntry jarEntry = enumeration.nextElement()
                String entryName = jarEntry.name
                ZipEntry zipEntry = new ZipEntry(entryName)
                InputStream inputStream = jarFile.getInputStream(zipEntry)
                if (isClassFile(entryName)) {
                    println("-------------------- handle jar file:<$entryName> --------------------")
                    jarOutputStream.putNextEntry(zipEntry)
                    ClassReader classReader = new ClassReader(IOUtils.toByteArray(inputStream))
                    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                    ClassVisitor classVisitor = new ActivityClassVisitor(classWriter)
                    classReader.accept(classVisitor, org.objectweb.asm.ClassReader.EXPAND_FRAMES)
                    byte[] bytes = classWriter.toByteArray()
                    jarOutputStream.write(bytes)
                } else {
                    jarOutputStream.putNextEntry(zipEntry)
                    jarOutputStream.write(IOUtils.toByteArray(inputStream))
                }
                jarOutputStream.closeEntry()
            }
            jarOutputStream.close()
            jarFile.close()
            def dest = outputProvider.getContentLocation(jarName + "_" + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
            FileUtils.copyFile(tempFile, dest)
            tempFile.delete()
        }
    }
    /**
     * 判断是否为需要处理class文件
     * @param name
     * @return
     */
    static boolean isClassFile(String name) {
        return (name.endsWith(".class") && !name.startsWith("R$")
                && "R.class" != name && "BuildConfig.class" != name && name.contains("Activity"))
    }
折叠 

In handleDirectoryInput and handleJarInput, we call our own ClassVisitor defined in src/main/java,

class ActivityClassVisitor extends ClassVisitor implements Opcodes {
    private String mClassName;
    private static final String CLASS_NAME_ACTIVITY = "androidx/appcompat/app/AppCompatActivity";
    private static final String METHOD_NAME_ONCREATE = "onCreate";
    private static final String METHOD_NAME_ONDESTROY = "onDestroy";
    public ActivityClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }
    @Override
    public void visit(int version, int access, String name, String signature, String superName,
                      String[] interfaces) {
        mClassName = name;
        super.visit(version, access, name, signature, superName, interfaces);
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                                     String[] exceptions) {
        MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
        if (CLASS_NAME_ACTIVITY.equals(mClassName)) {
            if (METHOD_NAME_ONCREATE.equals(name)) {
                System.out.println("-------------------- ActivityClassVisitor,visit method:" + name +
                        " --------------------");
                return new ActivityOnCreateMethodVisitor(Opcodes.ASM5, methodVisitor);
            } else if (METHOD_NAME_ONDESTROY.equals(name)) {
                System.out.println("-------------------- ActivityClassVisitor,visit method:" + name +
                        " --------------------");
                return new ActivityOnDestroyMethodVisitor(Opcodes.ASM5, methodVisitor);
            }
        }
        return methodVisitor;
    }
}

Here to simplify the operation, only the onCreate and onDestroy methods of the Activity are processed. The specific MethodVisitor is called in the visitMethod method. If you don't know much about bytecode, you can install the ASM Bytecode Outline plug-in in Android Studio to help.

Specific use:

After installing the ASM Bytecode Outline, restart Android Studio, then right-click on the corresponding Java file, select Show Bytecode outline and

after a while, the corresponding bytecode will be generated, and select the ASMified label in the opened panel

public class ActivityOnCreateMethodVisitor extends MethodVisitor {
    public ActivityOnCreateMethodVisitor(int api, MethodVisitor mv) {
        super(api, mv);
    }
    @Override
    public void visitCode() {
         mv.visitLdcInsn("ASMPlugin");
        mv.visitLdcInsn("-------------------- MainActivity onCreate --------------------");
        mv.visitMethodInsn(INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;" +
                "Ljava/lang/String;)I", false);
        mv.visitInsn(POP);
        super.visitCode();
    }
    @Override
    public void visitInsn(int opcode) {
        super.visitInsn(opcode);
    }
}
public class ActivityOnDestroyMethodVisitor extends MethodVisitor {
    public ActivityOnDestroyMethodVisitor(int api, MethodVisitor mv) {
        super(api, mv);
    }
    @Override
    public void visitCode() {
        super.visitCode();
        mv.visitLdcInsn("ASMPlugin");
        mv.visitLdcInsn("-------------------- MainActivity onDestroy --------------------");
        mv.visitMethodInsn(INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;" +
                "Ljava/lang/String;)I", false);
        mv.visitInsn(POP);
    }
    @Override
    public void visitInsn(int opcode) {
        super.visitInsn(opcode);
    }
}

Perform specific operations in the visitCode and visitInsn methods.

In the process of class processing, various problems may occur, and the problems can be located by debugging the plug-in.

2.6 Referencing plugins

Refer to the plug-in in the app module, which will not be introduced in detail here, you can refer to the previous article

Run the application on the mobile phone, after opening, you can see the log output:

02-25 17:29:45.885 31237 31237 I ASMPlugin: -------------------- MainActivity onCreate --------------------
02-25 17:29:50.646 31237 31237 I ASMPlugin: -------------------- MainActivity onDestroy --------------------

3. JavaSSs

Operating classes through javassit is the most basic knowledge of various link tracking systems. Through the java agent mechanism, the monitoring performance of the code is not intrusive, and the active monitoring, active alarm, code time consumption record, etc. are realized...

3.1 Core classes

ClassPool: the map structure key is the class name and the value is CtClass

CtClass: Get the corresponding operation class object

CtField: constructor class field

CtMethod: operation method, which can be assigned to CtClass as a new method

CtConstructor: Assign the operation constructor to CtClass to become a new constructor

3.2 Practice
3.2.1 Generating class files
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("demo.generate.GenerateHello");
// 构造接口
ctClass.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});
// 构造字段
CtField ctField = new CtField(CtClass.intType, "id", ctClass);
ctField.setModifiers(AccessFlag.PUBLIC);
// 类上增加字段
ctClass.addField(ctField);
// 增加构造函数
CtConstructor constructor = CtNewConstructor.make("public GenerateHello(int pid) {this.id=pid;}", ctClass);
ctClass.addConstructor(constructor);
// 增加方法
CtMethod ctMethod = CtNewMethod.make("public void hello(String desc){System.out.println(desc);}", ctClass);
ctClass.addMethod(ctMethod);
ctClass.writeFile();
Field[] fields = ctClass.toClass().getFields();
System.out.println("属性名称: " + fields[0].getName() + "属性类型: " + fields[0].getType());

Generate the corresponding class file and view it through idea

package demo.generate;
public class GenerateHello implements Cloneable {
    public int id;
    public GenerateHello(int var1) {
        this.id = var1;
    }
    public void hello(String var1) {
        System.out.println(var1);
    }
}
3.2.2 Modify the java file

Point

package demo.extend;
/**
 * @author chenhao.ych
 * @date 2019-07-15
 */
public class Point {
    private int x;
    private int y;
    public Point() {
    }
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public void move(int dx, int dy) {
        this.x += dx;
        this.y += dy;
    }
}

modify file

 public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    ClassPool classPool = ClassPool.getDefault();
    CtClass ctClass = classPool.get("demo.extend.Point");
    CtMethod method = ctClass.getDeclaredMethod("move");
    method.insertBefore("{System.out.print("dx:" + $1);System.out.println("dy: " + $2);}");
    method.insertAfter("{System.out.println(this.x);System.out.println(this.y);}");
    ctClass.writeFile();
    Class clazz = ctClass.toClass();
    Method move = clazz.getMethod("move", new Class[]{int.class, int.class});
    Constructor constructor = clazz.getConstructor(new Class[]{int.class, int.class});
    move.invoke(constructor.newInstance(1, 2), 1, 2);
}

The modified class file is decompiled

package demo.extend;
public class Point {
    private int x;
    private int y;
    public Point() {
    }
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public void move(int dx, int dy) {
        System.out.print("dx:" + dx);
        System.out.println("dy: " + dy);
        this.x += dx;
        this.y += dy;
        Object var4 = null;
        System.out.println(this.x);
        System.out.println(this.y);
    }
}

Guess you like

Origin blog.csdn.net/m0_64420071/article/details/127638392