版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
【移動應用開發(fā)技術】Android中怎么利用ASM實現(xiàn)自動埋點
這篇文章給大家介紹Android中怎么利用ASM實現(xiàn)自動埋點,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。1、AOP的概念其實這已經(jīng)涉及到AOP(AspectOrientedProgramming),即面向切面編程,在編譯期間對代碼進行動態(tài)管理,以達到統(tǒng)一維護的目的。AOP切面舉個栗子,Android開發(fā)我們都知道,在項目越來越大的時候,應用可能被分解為多個模塊,如果你要往所有模塊的方法里頭加一句‘我是大傻叼'的Toast,那是不是得跪。所以最好的方式是想辦法在編譯的時候拿到所有方法,往方法里頭懟一個Toast,這樣還不會影響到運行期間性能。2、TransformAndroid打包流程如圖所示是Android打包流程,.java文件->.class文件->.dex文件,只要在紅圈處攔截住,拿到所有方法進行修改完再放生就可以了,而做到這一步也不難,Google官方在AndroidGradle的1.5.0版本以后提供了TransfromAPI,允許第三方Plugin在打包dex文件之前的編譯過程中操作.class文件,我們做的就是實現(xiàn)Transform進行.class文件遍歷拿到所有方法,修改完成對原文件進行替換。/**
*
自動埋點追蹤,遍歷所有文件更換字節(jié)碼
*/
public
class
AutoTransform
extends
Transform
{
@Override
String
getName()
{
return
"AutoTrack"
}
@Override
Set<QualifiedContent.ContentType>
getInputTypes()
{
return
TransformManager.CONTENT_CLASS
}
@Override
Set<QualifiedContent.Scope>
getScopes()
{
return
TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean
isIncremental()
{
return
false
}
@Override
public
void
transform(
@NonNull
Context
context,
@NonNull
Collection<TransformInput>
inputs,
@NonNull
Collection<TransformInput>
referencedInputs,
@Nullable
TransformOutputProvider
outputProvider,
boolean
isIncremental)
throws
IOException,
TransformException,
InterruptedException
{
//此處會遍歷所有文件
/**遍歷輸入文件*/
inputs.each
{
TransformInput
input
->
/**
*
遍歷jar
*/
input.jarInputs.each
{
JarInput
jarInput
->
...
}
/**
*
遍歷目錄
*/
input.directoryInputs.each
{
DirectoryInput
directoryInput
->
...
}
}
}3、Gradle插件實現(xiàn)通過Transform提供的api可以遍歷所有文件,但是要實現(xiàn)Transform的遍歷操作,得通過Gradle插件來實現(xiàn),關于Gradle插件的知識可以看相關博客,也可以直接看博主的項目Luffy。編寫Gradle插件可能需要一點Goovy知識,具體編寫直接用java語言寫也可以,Goovy是完全兼容java的,只截取插件入口部分實現(xiàn)PluginEntry.groovyclass
PluginEntry
implements
Plugin<Project>
{
@Override
void
apply(Project
project)
{
...
//使用Transform實行遍歷
def
android
=
project.extensions.getByType(AppExtension)
registerTransform(android)
...
}
def
static
registerTransform(BaseExtension
android)
{
AutoTransform
transform
=
new
AutoTransform()
android.registerTransform(transform)
}4、字節(jié)碼編寫完成上面的操作以后就剩下一件事了,那就是拿到.class文件了,大家都知道.class文件是字節(jié)碼格式的,操作起來難度是相當于大的,所以需要一個字節(jié)碼操作庫來減輕難度,那就是ASM了。4.1、ASM簡介ASM可以直接產(chǎn)生二進制的class文件,也可以在增強既有類的功能。Javaclass被存儲在嚴格格式定義的.class文件里,這些類文件擁有足夠的元數(shù)據(jù)來解析類中的所有元素:類名稱、方法、屬性以及Java字節(jié)碼(指令)。4.2、具體使用ASMASM框架中的核心類有以下幾個:ClassReader:該類用來解析編譯過的class字節(jié)碼文件。ClassWriter:該類用來重新構建編譯后的類,比如說修改類名、屬性以及方法,甚至可以生成新的類的字節(jié)碼文件。ClassVisitor:主要負責“拜訪”類成員信息。其中包括標記在類上的注解,類的構造方法,類的字段,類的方法,靜態(tài)代碼塊。AdviceAdapter:實現(xiàn)了MethodVisitor接口,主要負責“拜訪”方法的信息,用來進行具體的方法字節(jié)碼操作。ClassVisitor的全部方法如下,按一定的次序來遍歷類中的成員。ClassVisitor的全部方法如下,按一定的次序來遍歷類中的成員。ClassVisitor全部api在ClassVisitor中根據(jù)你的條件進行判斷,滿足條件的類才會修改其中方法,比如要統(tǒng)計點擊事件的話,需要實現(xiàn)View$OnClickListener接口的類才會遍歷其中的方法進行操作。class
AutoClassVisitor
extends
ClassVisitor
{
AutoClassVisitor(final
ClassVisitor
cv)
{
super(Opcodes.ASM4,
cv)
}
@Override
void
visit(int
version,
int
access,
String
name,
String
signature,
String
superName,
String[]
interfaces)
{
//進行需要滿足類的條件過濾
...
super.visit(version,
access,
name,
signature,
superName,
interfaces)
}
@Override
void
visitInnerClass(String
name,
String
outerName,
String
innerName,
int
access)
{
//
內(nèi)部類信息
...
super.visitInnerClass(name,
outerName,
innerName,
access)
}
@Override
MethodVisitor
visitMethod(int
access,
String
name,
String
desc,
String
signature,
String[]
exceptions)
{
//
拿到需要修改的方法,執(zhí)行修改操作
MethodVisitor
methodVisitor
=
cv.visitMethod(access,
name,
desc,
signature,
exceptions)
MethodVisitor
adapter
=
null
...
adapter
=
new
AutoMethodVisitor(methodVisitor,
access,
name,
desc)
...
return
methodVisitor
}
@Override
void
visitEnd()
{
//類中成員信息遍歷介紹
...
super.visitEnd()
}
}在MethodVisitor中根據(jù)對已經(jīng)拿到的方法進行修改了。MethodVisitor
adapter
=
new
AutoMethodVisitor(methodVisitor,
access,
name,
desc)
{
boolean
isAnnotation
=
false
@Override
protected
void
onMethodEnter()
{
super.onMethodEnter()
//進入方法時可以插入字節(jié)碼
...
}
@Override
protected
void
onMethodExit(int
opcode)
{
super.onMethodExit(opcode)
//退出方法前可以插入字節(jié)碼
...
}
/**
*
需要通過注解的方式加字節(jié)碼才會重寫這個方法來進行條件過濾
*/
@Override
AnnotationVisitor
visitAnnotation(String
des,
boolean
visible)
{
...
return
super.visitAnnotation(des,
visible)
}
}5、實戰(zhàn)演練以上就是總體的思路了,現(xiàn)在就通過Luffy根據(jù)具體需求實戰(zhàn)一下,比如說在onClick方法點擊的耗時(自動埋點也是一樣的道理,只不過換了插樁的方法)。5.1、插件配置先打包一下插件到本地倉庫進行引用,在項目的根build.gradle加入插件的依賴dependencies
{
classpath
'com.xixi.plugin:plugin:1.0.1-SNAPSHOT'
}在app的build.gradle中apply
plugin:
'apk.move.plugin'
xiaoqingwa{
name
=
"小傻逼"
isDebug
=
true
//具體配置
matchData
=
[
//是否使用注解來找對應方法
'isAnotation':
false,
//方法的匹配,可以通過類名或者實現(xiàn)的接口名匹配
'ClassFilter':
[
['ClassName':
null,
'InterfaceName':null,
'MethodName':null,
'MethodDes':null]
],
//插入的字節(jié)碼,方法的執(zhí)行順序visitAnnotation->onMethodEnter->onMethodExit
'MethodVisitor':{
MethodVisitor
methodVisitor,
int
access,
String
name,
String
desc
->
MethodVisitor
adapter
=
new
AutoMethodVisitor(methodVisitor,
access,
name,
desc)
{
boolean
isAnnotation
=
false
@Override
protected
void
onMethodEnter()
{
super.onMethodEnter()
//使用注解找對應方法的時候得加這個判斷
}
@Override
protected
void
onMethodExit(int
opcode)
{
super.onMethodExit(opcode)
//使用注解找對應方法的時候得加這個判斷
}
/**
*
需要通過注解的方式加字節(jié)碼才會重寫這個方法來進行條件過濾
*/
@Override
AnnotationVisitor
visitAnnotation(String
des,
boolean
visible)
{
return
super.visitAnnotation(des,
visible)
}
}
return
adapter
}
]
}要是使用演示的話,因為還沒上傳到jcenter庫,所以只能本地倉庫打包插件,記得要先把依賴都注釋掉,插件打包完成后再啟用,不然會編譯不過去的。xiaoqingwa{}里頭的配置信息先不用管,等會會講到,主要是為了能夠不修改插件進行動態(tài)更換插樁的方法。5.2、應用測試插件配置好了之后就可以測試一下效果了,先寫一個耗時統(tǒng)計的工具類TimeCache.java/**
*
Author:xishuang
*
Date:2018.01.10
*
Des:計時類,編譯器加入指定方法中
*/
public
class
TimeCache
{
public
static
Map<String,
Long>
sStartTime
=
new
HashMap<>();
public
static
Map<String,
Long>
sEndTime
=
new
HashMap<>();
public
static
void
setStartTime(String
methodName,
long
time)
{
sStartTime.put(methodName,
time);
}
public
static
void
setEndTime(String
methodName,
long
time)
{
sEndTime.put(methodName,
time);
}
public
static
String
getCostTime(String
methodName)
{
long
start
=
sStartTime.get(methodName);
long
end
=
sEndTime.get(methodName);
long
dex
=
end
-
start;
return
"method:
"
+
methodName
+
"
cost
"
+
dex
+
"
ns";
}
}大概思路就是使用HashMap來臨時保存對應方法的時間,退出方法時獲取時間差。在一個方法的前后插入時間統(tǒng)計的方法,這個具體的過程要怎么操作呢,因為class文件是字節(jié)碼格式的,ASM也是進行字節(jié)碼操作,所以必須先把插入的代碼轉(zhuǎn)換成字節(jié)碼先。這里推薦一個字節(jié)碼查看工具JavaBytecodeEditor,導入.class文件就可以看到對應字節(jié)碼了。比如我們要插入的代碼如下:private
void
countTime()
{
TimeCache.setStartTime("newFunc",
System.currentTimeMillis());
TimeCache.setEndTime("newFunc",
System.currentTimeMillis());
Log.d("耗時",
TimeCache.getCostTime("newFunc"));
}先把.java文件編譯成.class文件,用JavaBytecodeEditor打開插入代碼的字節(jié)碼然后根據(jù)其用ASM提供的Api一一對應的把代碼填進來加到onMethodEnter和onMethodExit中。//方法前加入
methodVisitor.visitMethodInsn
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J",
false)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"com/xishuang/plugintest/TimeCache",
"setStartTime",
"(Ljava/lang/String;J)V",
false)
//方法后加入
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J",
false)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"com/xishuang/plugintest/TimeCache",
"setEndTime",
"(Ljava/lang/String;J)V",
false)
methodVisitor.visitLdcInsn("耗時")
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"com/xishuang/plugintest/TimeCache",
"getCostTime",
"(Ljava/lang/String;)Ljava/lang/String;",
false)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"android/util/Log",
"d",
"(Ljava/lang/String;Ljava/lang/String;)I",
false)在app的build.gradle中配置得到的字節(jié)碼,最后設置一下過濾條件,最終的代碼如下:build.gradlexiaoqingwa{
name
=
"小傻逼"
isDebug
=
true
//具體配置
matchData
=
[
//是否使用注解來找對應方法
'isAnotation':
false,
//方法的匹配,可以通過類名或者實現(xiàn)的接口名匹配
'ClassFilter':
[
['ClassName':
'com.xishuang.plugintest.MainActivity',
'InterfaceName':
'android/view/View$OnClickListener',
'MethodName':'onClick',
'MethodDes':'(Landroid/view/View;)V']
],
//插入的字節(jié)碼,方法的執(zhí)行順序visitAnnotation->onMethodEnter->onMethodExit
'MethodVisitor':{
MethodVisitor
methodVisitor,
int
access,
String
name,
String
desc
->
MethodVisitor
adapter
=
new
AutoMethodVisitor(methodVisitor,
access,
name,
desc)
{
boolean
isAnnotation
=
false
@Override
protected
void
onMethodEnter()
{
super.onMethodEnter()
//使用注解找對應方法的時候得加這個判斷
//
if
(!isAnnotation){
//
return
//
}
methodVisitor.visitMethodInsn(INVOKESTATIC,
"com/xishuang/plugintest/MainActivity",
"notifyInsert",
"()V",
false)
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J",
false)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"com/xishuang/plugintest/TimeCache",
"setStartTime",
"(Ljava/lang/String;J)V",
false)
}
@Override
protected
void
onMethodExit(int
opcode)
{
super.onMethodExit(opcode)
//使用注解找對應方法的時候得加這個判斷
//
if
(!isAnnotation){
//
return
//
}
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J",
false)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"com/xishuang/plugintest/TimeCache",
"setEndTime",
"(Ljava/lang/String;J)V",
false)
methodVisitor.visitLdcInsn("耗時")
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"com/xishuang/plugintest/TimeCache",
"getCostTime",
"(Ljava/lang/String;)Ljava/lang/String;",
false)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"android/util/Log",
"d",
"(Ljava/lang/String;Ljava/lang/String;)I",
false)
}
/**
*
需要通過注解的方式加字節(jié)碼才會重寫這個方法來進行條件過濾
*/
@Override
AnnotationVisitor
visitAnnotation(String
des,
boolean
visible)
{
//
if
(des.equals("Lcom/xishuang/annotation/AutoCount;"))
{
//
println
"注解匹配:"
+
des
//
isAnnotation
=
true
//
}
return
super.visitAnnotation(des,
visible)
}
}
return
adapter
}
]
}'isAnotation'表示是否使用注解的方式找到對應方法,這里false,因為我們現(xiàn)在是通過具體類信息來判斷的。'ClassFilter'表示過濾條件,其中'ClassName'和'InterfaceName'用于判斷哪些類中的方法可以遍歷其中的方法進行匹配修改,不滿足的話就不會進行方法名匹配了,這些感興趣的童鞋都可以改插件自定義擴展。'MethodName'和'MethodDes'是方法名和方法描述符,可以唯一確定一個方法名,滿足類過濾條件的就會進行方法匹配,例如我們要統(tǒng)計的點擊事件onClick(Viewv)。意思就是繼承自android/view/View$OnClickListener的類或者類名是'com.xishuang.plugintest.MainActivity'就可以進行方法的遍歷,然后方法滿足onClick(Viewv)就會進行代碼插入操作。設置完之后rebuild一下就可以了,可以通過日志看下具體信息,isDebug=true可以開啟日志打印。日志通過日志可以看到我們設置的字節(jié)碼確實插樁成功,現(xiàn)在再看一下編譯后的文件驗證一下,具體位置是:app\build\intermediates\transforms\AutoTrack\debug\folders編譯后的.class文件其中的notifyInsert()是我用來彈Toast額外調(diào)試用的,請忽略。在手機上點擊一下按鈕測試一下,發(fā)現(xiàn)確實記錄下點擊的耗時時間,完成。
5.3、注解匹配除了以上的方式來查找修改的方法之外,還可以通過注解來查找,切換很簡單,只需要改一下app的build.gradle文件就可以了,項目中也有栗子,添加了一個注解類。/**
*
Author:xishuang
*
Date:2018.1.9
*
Des:時間統(tǒng)計注解
*/
@Target(ElementType.METHOD)
public
@interface
AutoCount
{
}然后在對應的方法上添加你自定義的注解@AutoCount
private
void
onClick()
{
try
{
Thread.sleep(1000);
}
catch
(InterruptedException
e)
{
e.printStackTrace();
}
}
@AutoCount
@Override
public
void
onClick(View
v)
{
if
(v.getId()
==
R.id.button)
{
Toast.makeText(this,
"我是按鈕",
Toast.LENGTH_SHORT).show();
}
}修改一下build.gradle中的配置文件xiaoqingwa{
name
=
"小傻逼"
isDebug
=
true
//具體配置
matchData
=
[
//是否使用注解來找對應方法
'isAnotation':
true,
//方法的匹配,可以通過類名或者實現(xiàn)的接口名匹配
'ClassFilter':
[
['ClassName':
'com.xishuang.plugintest.MainActivity',
'InterfaceName':
'android/view/View$OnClickListener',
'MethodName':'onClick',
'MethodDes':'(Landroid/view/View;)V']
],
//插入的字節(jié)碼,方法的執(zhí)行順序visitAnnotation->onMethodEnter->onMethodExit
'MethodVisitor':{
MethodVisitor
methodVisitor,
int
access,
String
name,
String
desc
->
MethodVisitor
adapter
=
new
AutoMethodVisitor(methodVisitor,
access,
name,
desc)
{
boolean
isAnnotation
=
false
@Override
protected
void
onMethodEnter()
{
super.onMethodEnter()
//使用注解找對應方法的時候得加這個判斷
if
(!isAnnotation){
return
}
methodVisitor.visitMethodInsn(INVOKESTATIC,
"com/xishuang/plugintest/MainActivity",
"notifyInsert",
"()V",
false)
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J",
false)
methodVisitor.visitMethodInsn(INVOKESTATIC,
"com/xishuang/plugintest/TimeCache",
"setStartTime",
"(Ljava/lang/String;J)V",
false)
}
@Override
protected
void
onMethodExit(in
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年個人房產(chǎn)租賃管理服務協(xié)議
- 2025年鐵件掛件行業(yè)深度研究分析報告
- 2025年度綠色能源信托資金借款合同協(xié)議2篇
- 《消防安全教育普及》課件
- 2025年個人門面房租賃合同包含租賃保證金及返還流程2篇
- 2025年湖南長城銀河科技有限公司招聘筆試參考題庫含答案解析
- 2025年消防演練場地搭建與實施合同范本2篇
- 2025個人股份無償轉(zhuǎn)讓與公司戰(zhàn)略調(diào)整服務協(xié)議4篇
- 2025年廣東潮州潮安區(qū)商業(yè)總公司招聘筆試參考題庫含答案解析
- 2025年貴州湄潭湄江工業(yè)投資集團招聘筆試參考題庫含答案解析
- 《鐵路軌道維護》課件-更換道岔尖軌作業(yè)
- 股份代持協(xié)議書簡版wps
- 職業(yè)學校視頻監(jiān)控存儲系統(tǒng)解決方案
- 《銷售心理學培訓》課件
- 智能養(yǎng)老院視頻監(jiān)控技術方案
- 2024年安徽省公務員錄用考試《行測》真題及解析
- 你比我猜題庫課件
- 豐順縣鄉(xiāng)鎮(zhèn)集中式飲用水水源地基礎狀況調(diào)查和風險評估報告
- 無人駕駛航空器安全操作理論復習測試附答案
- 2024年山東省青島市中考語文試卷(附答案)
- 職業(yè)技術學?!犊缇畴娮由虅瘴锪髋c倉儲》課程標準
評論
0/150
提交評論