Hilt对象注入 | javassist插桩 研究

  • Hilt对象注入
  • javassist字节码插桩
    • 创建buildSrc的module
    • 重写Transform
      • 熟悉TransformInvocation
  • 遇到报错
    • FileSystemException
      • 解决方案
    • Hilt NoClassDefFoundError
      • 解决方案
    • GradleException: 'buildSrc'
      • 解决方案
    • Android studio Connection refused: connect
      • 解决方案
  • Groovy语言与Java相较

Hilt对象注入

使用IOC框架的开发思想就是,创建对象不再new,而是通过IOC容器帮助我们来实现对象的实例化并赋值使用。这样对象实例的创建变的容易管理,并能降低对象耦合度。
使用场景上,模板代码创建实例,局部或全局对象共享。

IOC框架下有三种注入方式:
view注入: 如ButterKnife
参数注入: 如Arouter
对象注入: 如Dagger2,Hilt

在Hilt应用到项目前,进行必不可少的配置:
1,project工程的build.gradle引入gradle插件

dependencies {// Hiltclasspath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}

2,然后在将要应用到的app-module模块中将build.gradle引入

/**build.gradle*/
apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'kotlin-kapt'compileOptions {sourceCompatibility = 1.8targetCompatibility = 1.8
}
kotlinOptions {jvmTarget = "1.8"
}dependencies {// Hiltimplementation "com.google.dagger:hilt-android:2.28-alpha"kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
}

3,接下来是应用程序中使用注解配置。

@HiltAndroidApp
public class MainApplication extends Application
@AndroidEntryPoint
class MainActivity : AppCompatActivity()
/**  MainModule.kt  */
@Module
@InstallIn(ApplicationComponent::class)
abstract class MainModule {//    @ActivityScoped Activity作用域内单例// @Singleton 全局单例@Binds@Singletonabstract fun bindService(impl:LogPrintServiceImpl):ILogPrintService//    @Provides
//    fun bindService():ILogPrintService {//        return LogPrintServiceImpl(context)
//    }
}interface ILogPrintService {fun logPrint()
}class LogPrintServiceImpl @Inject constructor(@ApplicationContext val context:Context):ILogPrintService{override fun logPrint() {Toast.makeText(context, "~IOC依赖注入-对象注入方式~", Toast.LENGTH_SHORT).show()}
}

最后,应用到项目中,使用@Inject注解即可获得由Hilt注入的对象实例,基于注解的依赖注入框架,使得对象实例的创建更为简单.

/**  MainActivity.kt  */
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {@set:Injectvar iLogPrintService:ILogPrintService?=nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)iLogService?.logPrint()}
}

MainActivity.kt和MainApplication.java由Hilt生成的java代码,在路径app/build/generated/source/kapt/debug下面

  • 在Hilt依赖注入编译时生成的Hilt_MainActivity.java类,是对象注入的入口类。
  • 在Hilt依赖注入编译时生成的Hilt_MainApplication.java类,是依赖注入的入口类。
  • 注解@HiltAndroidApp负责创建ApplicationComponent组件对象,在编译时会将父类(如这里会将Application替换成Hilt_MainApplication)替换成Hilt_***
  • 注解@HiltEntryPoint负责创建ActivityComponent组件对象,在编译时会将父类(如这里会将AppCompatActivity替换成Hilt_MainActivity)替换成Hilt_***
  • 然后跟进Hilt编译生成的抽象类中会发现,其实内部则是封装了dagger2的实现方式,来实现Hilt的依赖注入。

javassist字节码插桩

使用字节码插桩的技术,可以向Activity下任何方法中插入代码块。因为通过该技术,工程内源码和以jar(aar)参与编译之后的.class文件都能够被修改。
自定义插件开发有以下三种模式:

自定义插件类型 自定义说明
buildSrc 创建Java or Kotlin Library的module,会将插件的源码放到buildSrc/src/main/groovy目录下,且仅在本工程中可见。该方式适用于逻辑较复杂的插件定义。
jar包 创建独立的groovy或java项目,并把项目打包成jar发布到托管平台,以供使用。一个jar包可包含多个插件入口~
buildscript 将自定义的插件源码写在build.gradle的buildscript闭包中,适用于逻辑简单定义。因为定义在这里仅对当前build.gradle所属module可见。

创建buildSrc的module

module创建完成后(从工程的settings.gradle中删除include ‘:buildSrc’),替换配置当前buildSrc的build.gradle

// buildSrc/build.gradle
apply plugin: 'groovy'repositories {google()jcenter()
}
dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])//引入android plugin.相当于使用jetpack库implementation 'com.android.tools.build:gradle:3.4.2'//gradle api,相当于android sdkimplementation gradleApi()//groovy库implementation localGroovy()implementation 'org.javassist:javassist:3.27.0-GA'
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"

之后新建自定义gradle插件的目录、包名,详尽规范如截图

自定义插件目录 目录说明
main/groovy 这一级目录是groovy文件夹目录,下一级则是创建包名 。在已创建包名下必须创建groovy文件。
main/resources 自定义插件注册所在的资源目录。
META-INF/gradle-plugins 在该目录下定义插件名称,并注册插件。(如okpatch.properties,okpatch是插件名称)

注册插件代码

implementation-class =org.bagou.xiangs.plugin.OkPatchPlugin

自定义Transform并注册该Transform实现类

package org.bagou.xiangs.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.ProjectConfigurationExceptionclass OkPatchPlugin implements Plugin<Project> {@Overridevoid apply(Project project) {// 该方法是在配置执行时就会调用的。if (!project.plugins.hasPlugin("com.android.application")) {// 如果不是主工程模块,则抛出异常throw new ProjectConfigurationException("plugin:com.android.application must be apply", null)}// 注册自定义的Transform实现类project.android.registerTransform(new OkPatchPluginTransform(project))}
}

接下来自定义gradle插件过程,就只剩下了如何自定义重写Transform。在自定义并重写Transform中,即是我们实现如何修改class文件字节码。修改且编译完成后,在应用的项目模块的build.gradle中引入并使用。

apply plugin: 'okpatch'

重写Transform

重写Transform过程中出现了几个类,需要熟悉他们的作用。如下面代码

// 自定义类OkPatchPluginTransform,继承并重写Transform中的相关方法
// getName()、getInputTypes()、getScopes()、isIncremental()
class OkPatchPluginTransform extends Transform {... 略@Overridevoid transform(@NonNull TransformInvocation transformInvocation)throws TransformException, InterruptedException, IOException {// 重写transform方法,可实现对字节码进行插桩}
}

熟悉TransformInvocation

代码中方法的形参TransformInvocation是非常关键的接口。该接口中定义了两个方法

  • Collection<TransformInput> getInputs();
  • TransformOutputProvider getOutputProvider();

第一个方法可获得TransformInput接口,它是对输入文件的一个抽象。其中封装了JarInput和DirectoryInput,

  • Collection<JarInput> getJarInputs();
  • Collection<DirectoryInput> getDirectoryInputs();

第二个方法可获得TransformOutputProvider,并通过该类可获得如下结果,

  • 在已指定范围、内容类型和格式集合的内容位置。
  • 如果Format格式值是DIRECTORY,则获得的结果是源码文件所在的目录地址。
  • 如果Format格式值是Jar,则获得的结果是要创建的jar文件所在的目录地址。
TransformInput中相关类 说明
JarInput 指的是参与编译的所有本地或者远程Jar包和aar包中文件。
DirectoryInput 指的是参与编译的当前工程下的所有目录下的源码文件。

在继承重写Transform时,需要指定处理字节码的范围。即只能在某个作用域内获得并处理字节码文件。

class OkPatchPluginTransform extends Transform {...略@OverrideSet<? super QualifiedContent.Scope> getScopes() {// 该transform 工作的作用域// 源码中:Set<Scope> SCOPE_FULL_PROJECT =//    Sets.immutableEnumSet(//           Scope.PROJECT,//           Scope.SUB_PROJECTS,//           Scope.EXTERNAL_LIBRARIES);return TransformManager.SCOPE_FULL_PROJECT // 复合作用域,是一个Set类型}...略
}
作用域类型 说明
PROJECT 仅处理当前项目下的文件。
SUB_PROJECTS 仅处理子项目下的文件。
EXTERNAL_LIBRARIES 仅处理外部的依赖库。
PROVIDED_ONLY 仅处理本地或远程以provided形式引入的依赖库。
TESTED_CODE 仅处理测试代码。

继承并重写类Transform中方法的groovy文件源码,对于插桩逻辑的实现将体现在下面源码中,

一个CtClass对象通过writeFile()、toClass()、toBytecode()方法被转换成class文件,
那么Javassist就会将CtClass对象冻结起来,防止该CtClass对象被修改,
因为一个类只能被JVM加载一次。

/// 自定义gradle插件,实现Transform,完成插桩功能。自定义gradle插件执行优先级先于系统gradle插件!
// OkPatchPluginTransform.groovy
package org.bagou.xiangs.pluginimport com.android.annotations.NonNull
import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import javassist.ClassPool
import javassist.CtClass
import javassist.bytecode.ClassFile
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.gradle.api.Projectimport java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
/**在实现Transform类时,使用到的类要注意导包是否正确。*/
class OkPatchPluginTransform extends Transform {@OverrideString getName() {return "OkPatchPluginTransform" // 命名不重名于其他gradle即可}@OverrideSet<QualifiedContent.ContentType> getInputTypes() {// 表示接收到的输入数据类型return TransformManager.CONTENT_CLASS}@OverrideSet<? super QualifiedContent.Scope> getScopes() {// 该transform 工作的作用域return TransformManager.SCOPE_FULL_PROJECT}@Overrideboolean isIncremental() {// 是否增量变编译return false}private classPool = ClassPool.getDefault()OkPatchPluginTransform(Project project){// 将android.jar包添加到classPool中,以便能直接找到android相关的类classPool.appendClassPath(project.android.bootClasspath[0].toString())// 通过importPackage方式,以便由classPool.get(包名)直接获取实例对象// 且通过这种方式,相当于一次导包。在后面若要构建类,可免于写全类名classPool.importPackage("android.os.Bundle")classPool.importPackage("android.widget.Toast")classPool.importPackage("android.app.Activity")classPool.importPackage("android.util.Log")}@Overridevoid transform(@NonNull TransformInvocation transformInvocation)throws TransformException, InterruptedException, IOException {// 向工程中所有Activity的onCreate方法中打桩插入一段代码// 对项目中参与编译的.class,以及jar中的.class都做插桩处理def outputProvider = transformInvocation.outputProvider// transformInvocation.inputs,返回transform输入或输出的TransformInput容器// 然后通过TransformInput容器的迭代遍历,得到TransformInput实例。// 接下来可由TransformInput实例获得DIRECTORY和JAR格式的输入文件集合transformInvocation.inputs.each {_inputs->// 对_inputs中directory目录下的class进行遍历「DIRECTORY格式的输入文件集合」_inputs.directoryInputs.each { directory->handleDirectoryInputs(directory.file)def dest = outputProvider.getContentLocation(directory.name, directory.contentTypes,directory.scopes, Format.DIRECTORY)// 将修改过的字节码文件拷贝到原源码所在目录FileUtils.copyDirectory(directory.file, dest)}// 对_inputs中jar包下的class进行遍历「JAR格式的输入文件集合」_inputs.jarInputs.each {jar->def jarOutputFile = handleJarInputs(jar.file)def jarName = jar.namedef md5 = DigestUtils.md5Hex(jar.file.absolutePath)if (jarName.endsWith(".jar")) {jarName = jarName.substring(0,jarName.length()-4)}def dest = outputProvider.getContentLocation(md5+jarName,jar.contentTypes,jar.scopes,Format.JAR)// 将修改过的字节码文件拷贝到和原jar同级别所在目录FileUtils.copyFile(jarOutputFile, dest)}}classPool.clearImportedPackages()}// 处理 directory目录下的class文件void handleDirectoryInputs(File fileDir) {// required: 添加file地址到classPoolclassPool.appendClassPath(fileDir.absolutePath)if (fileDir.isDirectory()) { // 如果fileDir是文件目录fileDir.eachFileRecurse {file->def filePath = file.absolutePathif (ifModifyNeed(filePath)) {//判断是否满足class修改条件// 为兼容jar包下class修改共用方法modifyClass(**),将file转化为FileInputStreamFileInputStream fis = new FileInputStream(file)def ctClass = modifyClass(fis)ctClass.writeFile(fileDir.name) // 修改完成后再写回去ctClass.detach()}}}}// 处理 jar包下的class文件File handleJarInputs(File file) {// required: 添加file地址到classPoolclassPool.appendClassPath(file.absolutePath)JarFile jarInputFile = new JarFile(file) // 经过JarFile转换后,可获取jar包中子文件def entryFiles = jarInputFile.entries()File jarOutputFile = new File(file.parentFile, "temp_"+file.name)if (jarOutputFile.exists()) jarOutputFile.delete()JarOutputStream jarOutputStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(jarOutputFile)))while (entryFiles.hasMoreElements()) {def nextEle = entryFiles.nextElement()def nextEleName = nextEle.namedef jarEntry = new JarEntry(nextEleName)jarOutputStream.putNextEntry(jarEntry)def jarInputStream = jarInputFile.getInputStream(nextEle)if (!ifModifyNeed(nextEleName)) {//判断是否满足class修改条件jarOutputStream.write(IOUtils.toByteArray(jarInputStream))jarInputStream.close()continue}println('before....handleJarInputs-modifyClass')CtClass ctClass = modifyClass(jarInputStream)def bytecode = ctClass.toBytecode()ctClass.detach()jarInputStream.close()jarOutputStream.write(bytecode)jarOutputStream.flush()}jarInputFile.close()jarOutputStream.closeEntry()jarOutputStream.flush()jarOutputStream.close()return jarOutputFile}// class文件处理方法-共用CtClass modifyClass(InputStream fis) {// 通过输入流 获取 javassist 中的CtClass对象ClassFile classFile = new ClassFile(new DataInputStream(new BufferedInputStream(fis)))def ctClass = classPool.get(classFile.name)// 一个CtClass对象通过writeFile()、toClass()、toBytecode()方法被转换成class文件,// 那么Javassist就会将CtClass对象冻结起来,防止该CtClass对象被修改。if (ctClass.isFrozen())ctClass.defrost()// 开始执行修改逻辑// onCreate方法的参数 override fun onCreate(savedInstanceState: Bundle?)def bundle = classPool.get("android.os.Bundle")//获取到onCreate方法参数println(bundle)CtClass[] params = Arrays.asList(bundle).toArray() // 转化为反射入参数组def method = ctClass.getDeclaredMethod("onCreate", params)def message = "字节码插桩内容:"+classFile.nameprintln('字节码插桩内容:'+message)method.insertBefore("android.widget.Toast.makeText(this, "+"\""+ message +"\""+", android.widget.Toast.LENGTH_SHORT).show();")//给每个方法的最后一行添加代码行method.insertAfter("Log.d(\"MainActivity\", \"override fun onCreate方法后......\");")//给每个方法的最后一行添加代码行return ctClass}boolean ifModifyNeed(String filePath) {return (filePath.contains("org/bagou/xiangs")&& filePath.endsWith("Activity.class")&& !filePath.contains("R.class")&& !filePath.contains('$')&& !filePath.contains('R$')&& !filePath.contains("BuildConfig.class"))}}

javassist字节码处理经典使用参考文章

遇到报错

在尝试使用’org.javassist:javassist:3.27.0-GA’进行自定义gradle插件时,遇到问题。

FileSystemException

当前报错的项目工程中
gradle插件是:classpath 'com.android.tools.build:gradle:3.4.2'
gradle版本是 :distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

Caused by: java.util.concurrent.ExecutionException:
java.nio.file.FileSystemException:
D:\android-studio\包名\build\intermediates\runtime_library_classes\debug\classes.jar:
另一个程序正在使用此文件,进程无法访问。

解决方案

删除(终止)占用该classes.jar文件的进程。报错时,在任务管理页面截图的详细信息中java.exe进程有三个。然后全部删除,并重新构建工程,构建成功后显示两个java.exe进程。

在IOC框架研究使用DI(依赖注入),Hilt 进行对象注入时。

IOC框架下有三种注入方式:
view注入: 如ButterKnife
参数注入: 如Arouter
对象注入: 如Dagger2,Hilt

Hilt NoClassDefFoundError

当前报错的项目工程中
gradle插件是:classpath 'com.android.tools.build:gradle:3.4.2'
gradle版本是 :distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

2022-05-18 09:40:42.941 27105-27105/? E/AndroidRuntime: FATAL EXCEPTION: mainProcess: 包名, PID: 27105java.lang.NoClassDefFoundError: Failed resolution of: Lorg/包名/MainActivity_GeneratedInjector;at 包名.Hilt_MainActivity.inject(Hilt_MainActivity.java:53)包名.Hilt_MainActivity.onCreate(Hilt_MainActivity.java:28)at 包名.MainActivity.onCreate(MainActivity.java:32)

解决方案

本地修改当前gradle版本号到distributionUrl=file:///C:/Users/Administrator/.gradle/wrapper/dists/gradle-6.4.1-all.zip
(这个报错出现在gradle未下载完全导致)或者使用vpn执行构建下载distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

然后请确认以下自己AS下Settings和Project Structure的配置,


GradleException: ‘buildSrc’

* Exception is:
org.gradle.api.GradleException: 'buildSrc' cannot be used as a project name as it is a reserved nameat org.gradle.initialization.DefaultSettingsLoader.lambda$validate$0(DefaultSettingsLoader.java:146)at org.gradle.initialization.DefaultSettingsLoader.validate(DefaultSettingsLoader.java:142)at org.gradle.initialization.DefaultSettingsLoader.findSettingsAndLoadIfAppropriate

解决方案

在创建buildSrc该module时,ide会自动将该module引入到setttings.gradle中,因此会报上面错误。在settings.gradle中删除include ':buildSrc’即可。

Android studio Connection refused: connect

解决方案

第一步关掉AS的代理,选中no proxy。


第二步删除.gradle目录下的gradle.properties,并重新构建。即可~

当有意无意中配置过一次代理后,AS就会(我这里是默认的安装目录/.gradle下)生成一个代理文件,而后studio每次编译都会去读取该文件。

Groovy语言与Java相较

  • Groovy语言是基于JVM虚拟机的动态语言,Java是静态语言,Groovy完全兼容Java。
  • Groovy def关键字,def关键字用于定义Groovy中的无类型变量或动态返回类型的函数。
  • Groovy语法上分号不是必须的(该特点和kotlin一样),Java分号是必须的。
  • Groovy语法上单引号和双引号都能定义一个字符串,单引号不能对字符串中表达式做运算,双引号可以。Java单引号定义字符,双引号定义字符串。
  • Groovy语言声明一个List集合使用中括号,Java声明List集合使用大括号。(Groovy访问元素list[0]如范围索引1..3,-1表示右侧第一个等,Java访问元素list.get(0))
  • Groovy语言在声明map时使用中括号(访问map[key]、map.key,遍历map.each{item->...}),Java使用大括号。
  • Groovy语法在执行调用一个方法时,括号可以不写。Java则是必须的。
  • Groovy语法的return不是必须的,这个和kotlin一样。
  • Groovy语法的闭包有话说(与kotlin 如出一辙)
/** Groovy闭包的演变过程 */
def closureMethod () {def list = [1,2,3,4,5,6]// 呆板写法list.each({println it}) list.each({ // 格式化下println it})// 演进 - Groovy规定,若方法中最后一个参数是闭包,可放到方法外面list.each(){println it}// 再次演变 - 方法后的括号能省略list.each{println it}
}

(在gradle文件中)Groovy语言在定义一个任务时,(脚本即代码,代码也是脚本)

// build.gradle
// 每个任务task,都是project的一个属性
task customTask1 {doFirst {println 'customTask1方法第一个执行到的方法'def date = new Date()def datef = date.format('yyy-MM-dd)println "脚本即代码,代码也是脚本。当记得这一点才能时刻使用Groovy、Java和Gradle的任何语法和API来完成你想要做的事情。像这里,当前已格式化的日期:${datef}"}doLast {println 'customTask1方法最后一个执行到的方法'}
}tasks.create ('customTask2') {doFirst {println 'customTask2方法第一个执行到的方法'}doLast {println 'customTask2方法最后一个执行到的方法'}
}
// 通过任务名称访问方法(其实就是动态赋一个新的原子方法)
customTask2.doFirst {print '查看在project中是否有task=customTask2 = 'println project.hasProperty('customTask2')
}

(在gradle文件中)Groovy创建任务的方式大概有5种


/**我们创建Task任务都会成为Project的一个属性,属性名就是任务名*/
task newOwnerTask5
// 扩展任务属性
newOwnerTask5.description = '扩展任务属性-描述'
newOwnerTask5.group = BasePlugin.BUILD_GROUP
newOwnerTask5.doFirst {println "我们创建Task任务都会成为Project的一个属性,属性名就是任务名"
}
tasks['newOwnerTask5'].doFirst {println "任务都是通过TaskContanier创建的,TaskContanier是我们创建任务的集合,在Project中我们可以用通过tasks属性访问TaskContanier。所以可以以访问集合元素方式访问已创建的任务。"
}
// 第一种:直接以一个任务名称,作为创建任务的方式
def Task newOwnerTask1 = task(newOwnerTask1)
newOwnerTask1.doFirst {println '创建方法的原型为:Task task(String name) throws InvalidUserDataException'
}// 第二种:以一个任务名+一个对该任务配置的map对象来创建任务 [和第一种大同小异]
def Task newOwnerTask2 = task(newOwnerTask2, group:BasePlugin.BUILD_GROUP)
newOwnerTask2.doFirst {println '创建方法的原型为:Task task(String name, Map<String,?> arg) throws InvalidUserDataException'println "任务分组:${newOwnerTask2.group}"
}
// 第三种:以一个任务名+闭包配置
task newOwnerTask3 {description '演示任务的创建'doFirst {println "任务的描述:${description}"println "创建方法的原型:Task task(String name, Closure closure)"}
}
// 第四种:tasks是Project对象的属性,其类型是TaskContainer,
// 因此下面的创建方式tasks可替换为TaskContainer来创建task任务
//【这种创建方式,发生在Project对象源码中创建任务对象】
tasks.create("newOwnerTask4") {description '演示任务的创建'doFirst {println "任务的描述:${description}"println "创建方法的原型:Task create(String name, Closure closure) throws InvalidUserDataException"}
}
// 白送一种任务创建形式
task (helloFlag).doLast {println '<< 作为操作符,在gradle的Task上是doLast方法的短标记形式'
}

字节码插桩(javassist)之插入代码块|IOC框架(Hilt)之对象注入~研究相关推荐

  1. 看完这一篇,你也可以自如地掌握字节码插桩

    /   今日科技快讯   / 近日,一些国家的黑客频繁对俄罗斯发动网络攻击,以阻止它们正常运行.未来几天,俄罗斯可能与全球互联网断开.针对网络威胁,俄罗斯政府准备启动自己的"大局域网&quo ...

  2. Android AOP之字节码插桩

    背景   本篇文章基于<网易乐得无埋点数据收集SDK>总结而成,关于网易乐得无埋点数据采集SDK的功能介绍以及技术总结后续会有文章进行阐述,本篇单讲SDK中用到的Android端AOP的实 ...

  3. 美团热修复Robust源码庖丁解牛(第一篇字节码插桩)

    如果你想对java编译后的class文件做一些手脚的话,市面上有供你选择的asm.javassist.aspectJ(aop面向切面编程)等等,一般修改class文件的用途有你想统计一些东西,例如ap ...

  4. Android 字节码插桩全流程解析

    在Android进阶宝典 – Handler应用于线上卡顿监控中,我简单介绍了一下关于ASM实现字节码插桩来实现方法耗时的监控,但是当时只是找了一个特定的class文件,针对某个特定的方法进行插桩,但 ...

  5. Android程序员的硬通货——ASM字节码插桩

    作者:享学课堂Lance老师 转载请声明出处! 一.什么是插桩 QQ空间曾经发布的<热修复解决方案>中利用 Javaassist库实现向类的构造函数中插入一段代码解决 CLASS_ISPR ...

  6. 字节码插桩之Java Agent

    字节码插桩之Java Agent 本篇文章将详细讲解有关Java Agent的知识,揭开它神秘的面纱,帮助开发人员了解它的黑魔法,帮助我们完成更多业务需求 What is Java Agent Jav ...

  7. 调研字节码插桩技术,用于系统监控设计和实现

    作者:小傅哥 博客:https://bugstack.cn ❝ 沉淀.分享.成长,让自己和他人都能有所收获!???? ❞ 目录 一.来自深夜的电话! 二.准备工作 三.使用 AOP 做个切面监控 1. ...

  8. 【字节码插桩】AOP 技术 ( “字节码插桩“ 技术简介 | AspectJ 插桩工具 | ASM 插桩工具 )

    文章目录 一." 字节码插桩 " 技术简介 二.AspectJ 插桩工具 三.ASM 插桩工具 一." 字节码插桩 " 技术简介 性能优化 , 插件化 , 热修 ...

  9. 【网上的都不靠谱?还是得改源码】用Javasisst的字节码插桩技术,彻底解决Gson转Map时,Int变成double问题...

    一.探究原由 首先申明一下,我们要解决的问题有两个: Json串转Map时,int变double问题 Json串转对象时,对象属性中的Map,int变double问题 然后,我们来了解一下,Gson实 ...

最新文章

  1. 媒体智能应用落地靠5G,视频社交需要想象力
  2. Mac怎么刷新DNS缓存
  3. div中插入图片_Web前端开发基础知识,设置网页背景图,如何在网页中插入图片...
  4. 【数据结构与算法】之深入解析“字符串相乘”的求解思路与算法示例
  5. 电子计算机的大脑核心是什么,戴君惕《人脑与电脑》初中说明文阅读题及答案...
  6. matlab unicode,MATLAB中的汉字编码
  7. 【LeetCode】【HOT】206. 反转链表(迭代/递归)
  8. mfc按钮名称怎么换行_MFC中解决回车键关闭窗口的一般方法
  9. 数字彩色电视摄像机结构
  10. 物联网领域不断扩展,ATT很“兴奋”
  11. 3ds Max学习指南,基本知识与基本操作,常用快捷键汇总
  12. SuperMap iClient3D for WebGL制作立体地图
  13. 破解虚拟机ESXi服务器密码,esxi虚机Windows server 2012忘记密码解决办法
  14. transformer:self-attention,muti-head attention,positional encoding
  15. win10系统改win7设置bios方法图文教程
  16. 会员管理小程序实战开发教程(六)-会员查询功能
  17. mac80211/cfg80211模块编译安装
  18. python信息检索系统_GitHub - Uyouii/SearchingSystem: python实现的基于倒排索引和向量空间模型实现的信息检索系统...
  19. 4800余网站涉“黄”被封 新浪搜狐腾讯关栏目
  20. 机器学习吴恩达课程总结(五)

热门文章

  1. css情景动画,CSS3 白天/黑夜场景轮回动画
  2. root cause java.lang.LinkageError: loader constraint violation: loader (instanc
  3. 一文搞懂Oracle字符集
  4. 剑指Offer面试题:31.两个链表的第一个公共节点
  5. 【English】十一、一般疑问句
  6. tkinter--画布
  7. Weex Android 动画揭秘
  8. Windows 10操作系统配置L2TP方法
  9. 耳机四根线的图解_type c数据线拆解及接线图文详解
  10. python爬取当当图片和信息