前言
最近的工作內(nèi)容主要其實(shí)并不是說主攻插樁,但是這一次使用Lancet
插樁給項(xiàng)目本來帶來了極大的收益已亥,這和工程的設(shè)計相關(guān),當(dāng)初的設(shè)計就是在對抖音中一個原有組件盡可能小的修改情況下静陈,完成我新功能的接入院溺,方案從SPI
--> 主工程Lancet
--> Lancet
下沉到一個自定義組件中,一次次嘗試確實(shí)也是領(lǐng)會這個黑科技的恐怖之處了鹉勒。
先了解以下當(dāng)時的場景:
先比較一期和二期的優(yōu)勢和劣勢:實(shí)踐發(fā)現(xiàn)一期最后相較于二期的優(yōu)勢僅僅只有不影響主工程帆锋,而劣勢主要表現(xiàn)在三個方面:
-
api
改動時,impl
和組件
需要聯(lián)動修改禽额。 - 當(dāng)時的環(huán)境決定锯厢,使用
SPI
方案時,會導(dǎo)致大量的本不需要過早獲取的數(shù)據(jù)被獲取了脯倒,導(dǎo)致運(yùn)行時工程性能降低实辑,另外還有反射在損耗性能。
但是二期方案也存在劣勢藻丢,我們也說了影響主工程剪撬,而且說Lancet
的生效時機(jī)需要進(jìn)行把握,不可能讓他全局生效因?yàn)楸旧砭褪翘囟ㄇ闆r下悠反,全局時會影響編譯速度残黑,另外這在后期的維護(hù)上成本也有一定的增加。
以上的總結(jié)最后引出了方案三斋否,不影響主工程梨水,并且不需要把握生效時機(jī),只需要某組件給出Hook
點(diǎn)如叼,就可以輕松完成工作冰木。
本文只探討怎么去實(shí)現(xiàn)
AscpectJ
這一類AOP方案的方法穷劈。
熱門的插樁方案探索
瀏覽了一下Github
上比較熱門的插樁方案,看到普遍進(jìn)行使用的就是AspectJ
還有Lancet
社证,而作為AspectJ
他的延伸中的拓展庫AspectJX
宜肉,因?yàn)楸容^好的兼容性而受到廣泛使用。
AspectJX
的使用方法
AspectJX
是基于gradle android
插件1.5及以上版本設(shè)計使用的。
插件引入
// root -> build.gradle
dependencies {
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
}
// app -> build.gradle
apply plugin: 'android-aspectjx'
復(fù)制代碼
如何使用
這里用的是一個他的權(quán)限請求庫Android_Permission_AspectjX
,注意使用過程中發(fā)現(xiàn)一個Bug
扁眯,給作為基類的Activity
套上注解時并不會生效施敢,基類的方法是沒問題的。
// 1\. app --> build.gradle
compile 'com.firefly1126.permissionaspect:permissionaspect:1.0.1'
// 2\. 自定義Application
onCreate(){
PermissionCheckSDK.init(Application);
}
// 3\. 使用注解的方式添加權(quán)限@NeedPermission
@NeedPermission(permissions = {Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public class BActivity extends Activity {}
//作用于類的方法
@NeedPermission(permissions = {Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
private void startBActivity(String name, long id) {
startActivity(new Intent(MainActivity.this, BActivity.class));
}
復(fù)制代碼
非常簡單的使用了兩個注解就已經(jīng)完成權(quán)限的申請愚屁。
這個庫的一些坑
這樣就已經(jīng)完成庫的導(dǎo)入了梦谜,但是查閱一些度娘的資料會發(fā)現(xiàn)這樣的問題發(fā)生庫的沖突。比如與支付寶sdk
發(fā)生沖突捏肢,以下是一段用于復(fù)現(xiàn)代碼辩棒。
PayTask alipay = new PayTask(this);
復(fù)制代碼
這是由于AspectJX
本身造成的窘俺,默認(rèn)會處理所有的二進(jìn)制代碼文件和庫对途,為了提升編譯效率及規(guī)避部分第三方庫出現(xiàn)的編譯兼容性問題,AspectJX
提供include
,exclude
命令來過濾需要處理的文件及排除某些文件(包括class
文件及jar
文件)怀愧。當(dāng)然為了解決這樣的問題,開發(fā)者也提供了解決方案扛拨,也就是白名單耘分。
aspectjx {
//排除所有package路徑中包含`android.support`的class文件及庫(jar文件)
exclude 'android.support'
// exclude '*'
// 關(guān)閉AspectJX功能,默認(rèn)開啟
enabled false
}
復(fù)制代碼
Lancet
的使用
文章只做涉略绑警,更為具體的使用請查看倉庫:github.com/eleme/lance…
- 插件引入
// root --> build.gradle
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'me.ele:lancet-plugin:1.0.6'
}
// build.gralde
apply plugin: 'me.ele.lancet'
dependencies {
compileOnly 'me.ele:lancet-base:1.0.6'
}
復(fù)制代碼
-
Lancet
的使用
public class LancetHooker {
@Insert(value = "eat", mayCreateSuper = true)
@TargetClass(value = "com.example.lancet.Cat", scope = Scope.SELF)
public void _eat() {
((Cat)This.get()).bark();
//這里可以使用 this 訪問當(dāng)前 Cat 類的成員求泰,僅用于Insert 方式的非靜態(tài)方法的Hook中.(暫時)
System.out.println(">>>>>>>" + this);
Origin.callVoid();
}
@Insert(value = "bark", mayCreateSuper = true)
@TargetClass(value = "com.example.lancet.Cat", scope = Scope.SELF)
public void _bark(){
System.out.println("調(diào)用了bark");
Origin.callVoid();
}
}
復(fù)制代碼
當(dāng)定義了Hook
點(diǎn),并且在編譯時被搜索到计盒,最后編譯完成之后的效果就會為如下所示渴频。
public class Cat {
class _lancet {
private _lancet() {
}
// 比如調(diào)用原本調(diào)用bark的方法,會重寫為調(diào)用com_example_lancet_LancetHooker__bark
// 如果內(nèi)部存在Origin.Call()這一類的方法時北启,會對原本的方法在自己的調(diào)用點(diǎn)上進(jìn)行過程
@Insert(mayCreateSuper = true, value = "bark")
@TargetClass(scope = Scope.SELF, value = "com.example.lancet.Cat")
static void com_example_lancet_LancetHooker__bark(Cat cat) {
System.out.println("調(diào)用了bark");
cat.bark$___twin___();
}
@Insert(mayCreateSuper = true, value = "eat")
@TargetClass(scope = Scope.SELF, value = "com.example.lancet.Cat")
static void com_example_lancet_LancetHooker__eat(Cat cat) {
cat.bark();
PrintStream printStream = System.out;
printStream.println(">>>>>>>" + cat);
cat.eat$___twin___();
}
}
public void bark() {
_lancet.com_example_lancet_LancetHooker__bark(this);
}
public void eat() {
_lancet.com_example_lancet_LancetHooker__eat(this);
}
/* access modifiers changed from: private */
public void eat$___twin___() {
System.out.println("貓吃老鼠");
}
public String toString() {
return "貓";
}
/* access modifiers changed from: private */
public void bark$___twin___() {
System.out.println("貓叫了叫");
}
}
復(fù)制代碼
可以發(fā)現(xiàn)它的做法是對源代碼進(jìn)行修改卜朗,而修改的方式是建設(shè)一個靜態(tài)內(nèi)部類,和對應(yīng)的內(nèi)部方法咕村,通過重新設(shè)置調(diào)用鏈來進(jìn)行結(jié)果的完成场钉,那AspectJ
呢,他是否是通過這樣的方式來進(jìn)行完成的呢懈涛?
AspectJ
是如果實(shí)現(xiàn)的逛万?
權(quán)限的申請只通過幾個注解就能夠完成,那他是怎么做的呢批钠?我們可以通過jadx-gui
來反編譯代碼進(jìn)行查看泣港。
因?yàn)锳spectJX默認(rèn)對所有文件生效,所以是否添加注解都會被劫持价匠,除非使用上文中的開白名單
public final class MainActivity extends BaseActivity {
private static final /* synthetic */ JoinPoint.StaticPart ajc$tjp_0 = null;
private HashMap _$_findViewCache;
/* compiled from: MainActivity.kt */
public class AjcClosure1 extends AroundClosure {
public AjcClosure1(Object[] objArr) {
super(objArr);
}
public Object run(Object[] objArr) {
Object[] objArr2 = this.state;
MainActivity.onCreate_aroundBody0((MainActivity) objArr2[0], (Bundle) objArr2[1], (JoinPoint) objArr2[2]);
return null;
}
}
static {
ajc$preClinit();
}
private static /* synthetic */ void ajc$preClinit() {
Factory factory = new Factory("MainActivity.kt", MainActivity.class);
ajc$tjp_0 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, (Signature) factory.makeMethodSig("4", "onCreate", "com.example.stub.MainActivity", "android.os.Bundle", "savedInstanceState", "", "void"), 12);
}
public void _$_clearFindViewByIdCache() {
HashMap hashMap = this._$_findViewCache;
if (hashMap != null) {
hashMap.clear();
}
}
public View _$_findCachedViewById(int i) {
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View view = (View) this._$_findViewCache.get(Integer.valueOf(i));
if (view != null) {
return view;
}
View findViewById = findViewById(i);
this._$_findViewCache.put(Integer.valueOf(i), findViewById);
return findViewById;
}
static final /* synthetic */ void onCreate_aroundBody0(MainActivity ajc$this, Bundle savedInstanceState, JoinPoint joinPoint) {
super.onCreate(savedInstanceState);
ajc$this.setContentView((int) R.layout.activity_main);
}
/* access modifiers changed from: protected */
public void onCreate(Bundle savedInstanceState) {
JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, (Object) this, (Object) this, (Object) savedInstanceState);
PermissionAspect.aspectOf().adviceOnActivityCreate(new AjcClosure1(new Object[]{this, savedInstanceState, makeJP}).linkClosureAndJoinPoint(69648));
}
}
復(fù)制代碼
通過編譯后的源碼查看可以發(fā)現(xiàn)当纱,你所寫的代碼已經(jīng)被通過一些特殊的方式來進(jìn)行了修改,所以我們就應(yīng)該有了自己的目標(biāo)了踩窖,注解 + 自動化代碼修改完成任務(wù)坡氯。
如何完成自動化代碼修改
這里我們首先需要借用的能力是Gradle Transform Api
中的遍歷,而這個功能在你創(chuàng)建一個Android
工程的時候Android Studio
已經(jīng)自然而然給你集成了這一項(xiàng)能力洋腮。
這個
Api
的能力只有在Gradle Version 1.5+
的時候才開放
那它的運(yùn)作方式是怎么樣的呢箫柳?小二,上圖啥供。
上述本是Apk
完整的打包流程悯恍,但是如果使用了Transform Api
將會多出我們紅框中的部分。當(dāng)然如果三方的.class Files
的文件內(nèi)存在注解也是可能會被抓住的伙狐。所以這里我們知道了一個目標(biāo)是被編譯過后的.class
文件們涮毫,而代碼的修改邏輯肯定是和我們的希望實(shí)現(xiàn)的邏輯有關(guān)的瞬欧。
看過了上面反編譯出來的一個代碼修改模式,我們可以先思考一下這種代碼修改可以如何去進(jìn)行罢防。比如說
public void fun(Login login){
login.on();
}
復(fù)制代碼
但是我們想直接劫持這樣的方法艘虎,因?yàn)檫@個方法它只做了一個登陸操作,但是我想做身份驗(yàn)證呢咒吐?如果代碼中只有一處還好說野建,但是如果多處呢?可能我的代碼就變成了如下
public void fun(Login login){
if(login.check()) login.on();
else login.close()
}
復(fù)制代碼
上述代碼還是比較簡單的恬叹,但是有些時候這種邏輯的重復(fù)書寫是時常存在的候生,而且隨著代碼容量的增加而導(dǎo)致維護(hù)難度提高,如果有一天身份驗(yàn)證方法變了绽昼,那就涼透了唯鸭。這就是插樁經(jīng)常會被用到的地方 —— AOP
面向切面,在代碼實(shí)現(xiàn)時绪励,你需要干的事情是給對應(yīng)的方法加上一個注解肿孵,處理邏輯統(tǒng)一完成。
插樁實(shí)現(xiàn)
第一個環(huán)節(jié):如何將插樁的能力植入
這里真的真的看了很多網(wǎng)上資料疏魏,質(zhì)量參差不齊停做,花了整整一天時間,終于把整個東西跑起來了?? ?? ?? 大莫,下面文章內(nèi)將給出我認(rèn)為最簡便的創(chuàng)建工程的方案蛉腌。
如果只是想要本地測試的話,這里給出的是最簡便的方案只厘,使用buildSrc
(大小寫也要一致哦烙丛!)來作為Android Library
的名字可以省去99%
的麻煩。
最后會在文末給一個可以用于發(fā)版使用的實(shí)現(xiàn)方案介紹羔味。
那要先進(jìn)入第一步河咽,插件的使用。
為了能夠引入Gradle
的能力赋元,請將倉庫內(nèi)的build.gradle
的內(nèi)容修改成如下的形式忘蟹。
apply plugin: 'groovy'
dependencies {
implementation gradleApi()//gradle sdk
implementation 'com.android.tools.build:gradle:3.5.4'
implementation 'com.android.tools.build:gradle-api:3.5.4'
//ASM依賴
implementation 'org.ow2.asm:asm:8.0'
implementation 'org.ow2.asm:asm-util:8.0'
implementation 'org.ow2.asm:asm-commons:8.0'
}
repositories {
google()
jcenter()
}
復(fù)制代碼
上述內(nèi)容完成sync
以后,就需要生成一個插件能夠進(jìn)行使用搁凸。
/**
* Create by yiyonghao on 2020-08-08
* Email: yiyonghao@bytedance.com
*/
public class AsmPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
System.out.println("=========== doing ============");
}
}
復(fù)制代碼
并且在主工程的app --> build.gradle
中添加語句apply plugin: com.example.buildsrc.AsmPlugin(包名.插件名)
媚值。
很多工程說用
Groovy
來做,其實(shí)沒有必要护糖,直接Java
就可以了褥芒。
如果到這一步,在build過程中能夠打印出=========== doing ============
這個數(shù)據(jù)嫡良,說明插件已經(jīng)生效锰扶,那現(xiàn)在就要進(jìn)入下一步献酗,如何完成代碼的插樁了。
在不引入ASM
之前少辣,整體Gradle Transform API
為我們提供了什么樣的能力呢凌摄?先明確目標(biāo)涡驮,如果想要代碼的插樁狈惫,我們一定要進(jìn)行下面這樣的幾個步驟:
- 源碼文件獲瓤攴铩(可能是
.class
,也可能是.jar
) - 文件修改
源碼文件獲取
為了獲取文件的路徑忙干,我們使用的能力就是Gradle Transform API
所提供的Transform
類,其中的transform()
方法中的變量其實(shí)已經(jīng)自動為我們提供了很多他自身所具備的能力浪藻,就比如說文件遍歷捐迫。
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
//消費(fèi)型輸入,可以從中獲取jar包和class文件夾路徑爱葵。需要輸出給下一個任務(wù)
Collection<TransformInput> inputs = transformInvocation.getInputs();
//OutputProvider管理輸出路徑施戴,如果消費(fèi)型輸入為空,你會發(fā)現(xiàn)OutputProvider == null
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
for (TransformInput input : inputs) {
for (JarInput jarInput : input.getJarInputs()) {
File dest = outputProvider.getContentLocation(
jarInput.getFile().getAbsolutePath(),
jarInput.getContentTypes(),
jarInput.getScopes(),
Format.JAR);
}
for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
File dest = outputProvider.getContentLocation(directoryInput.getName(),
directoryInput.getContentTypes(), directoryInput.getScopes(),
Format.DIRECTORY);
transformDir(directoryInput.getFile(), dest);
}
}
}
復(fù)制代碼
通過如上的方式萌丈,就可以掃到我們的文件了赞哗,那就應(yīng)該要接入第二個步驟,如何進(jìn)行文件的修改辆雾?
文件修改
在上文中我從來沒有提及過Gradle Transform API
關(guān)于修改代碼的邏輯肪笋,這是為什么呢?
還不是因?yàn)樗⒉惶峁┻@樣專項(xiàng)的功能度迂,所以這里就要引入我們經(jīng)常聽說的大將ASM
來完成字節(jié)碼的修改了藤乙。這里開始將注意點(diǎn)放置到我們的兩個類AsmClassAdapter
和AsmMethodVisitor
還有AsmTransform.weave()
。
關(guān)于ASM
最最最最常涉及的是下面幾個核心類惭墓。
當(dāng)然我現(xiàn)在給出的Demo
中有兩個類坛梁,AsmClassAdapter
就是繼承了ClassVisitor
用來訪問Class
也就是我們的一個個類,而AsmMethodVisitor
就是通過ClassVisitor
的數(shù)據(jù)傳遞然后用于訪問類中存在的方法的腊凶。
private static void weave(String inputPath, String outputPath) {
try {
// 划咐。。吭狡。尖殃。。
// 而文件結(jié)構(gòu)的訪問通過ASM基于的能力來進(jìn)行識別
ClassReader cr = new ClassReader(is);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
AsmClassAdapter adapter = new AsmClassAdapter(cw);
cr.accept(adapter, 0);
// 划煮。送丰。。弛秋。器躏。
} catch (IOException e) {
e.printStackTrace();
}
}
復(fù)制代碼
其實(shí)本質(zhì)上就是ASM
對一個文件進(jìn)行分析操作以后俐载,讓我們只關(guān)注想要插入什么,以什么樣的方法去進(jìn)行插入登失,然后他會使用對應(yīng)的方案對字節(jié)碼進(jìn)行整改遏佣。
AsmClassAdapter
和AsmMethodVisitor
的簡單實(shí)現(xiàn)
public class AsmClassAdapter extends ClassVisitor implements Opcodes {
public AsmClassAdapter(ClassVisitor classVisitor) {
super(ASM7, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return (mv == null) ? null : new AsmMethodVisitor(mv); // 1 -->
}
}
復(fù)制代碼
而MethodVisitor
方法對于我們而言,就是對方法的一個插樁方案揽浙。
public class AsmMethodVisitor extends MethodVisitor{
public AsmMethodVisitor(MethodVisitor methodVisitor) {
super(ASM7, methodVisitor);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
//方法執(zhí)行之前打印
mv.visitLdcInsn(" before method exec");
mv.visitLdcInsn(" [ASM 測試] method in " + owner + " ,name=" + name);
mv.visitMethodInsn(INVOKESTATIC,
"android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false);
mv.visitInsn(POP);
// 原有方法
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
//方法執(zhí)行之后打印
mv.visitLdcInsn(" after method exec");
mv.visitLdcInsn(" method in " + owner + " ,name=" + name);
mv.visitMethodInsn(INVOKESTATIC,
"android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false);
mv.visitInsn(POP);
}
}
復(fù)制代碼
你可以實(shí)現(xiàn)更多類似這樣的方法状婶。而這樣做過之后,我們是否已經(jīng)完成了所謂了字節(jié)碼的修改了呢馅巷?
第二步:文件覆蓋
可能你跑不通膛虫,這里直接給出一個答案,并沒有完成5鲡稍刀!我們我們雖然會所把字節(jié)碼修改了,但是你是否有完成文件的覆蓋呢敞曹?
所以你能夠在Demo
中發(fā)現(xiàn)存在這樣的代碼账月,比如:
-
weave()
方法
private static void weave(String inputPath, String outputPath) {
try {
// 存在新文件的創(chuàng)建
FileInputStream is = new FileInputStream(inputPath);
ClassReader cr = new ClassReader(is);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
AsmClassAdapter adapter = new AsmClassAdapter(cw);
cr.accept(adapter, 0);
FileOutputStream fos = new FileOutputStream(outputPath);
fos.write(cw.toByteArray());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
復(fù)制代碼
-
FileUtils.copyFile(jarInput.getFile(), dest);
存在jar
包的位置遷移,這都是為了將新的代碼進(jìn)行存儲
完成到這里澳迫,我們在去看一下最后生成的代碼到底是什么樣的局齿。(文件路徑:app --> build --> intermediates --> transform --> 包名 --> debug --> 一直到你的文件)比如說我本地生成的MainActivity.java
。
public class MainActivity extends AppCompatActivity {
public MainActivity() {
Log.i(" before method exec", " [ASM 測試] method in androidx/appcompat/app/AppCompatActivity ,name=<init>");
super();
Log.i(" after method exec", " method in androidx/appcompat/app/AppCompatActivity ,name=<init>");
}
protected void onCreate(Bundle savedInstanceState) {
Log.i(" before method exec", " [ASM 測試] method in androidx/appcompat/app/AppCompatActivity ,name=onCreate");
super.onCreate(savedInstanceState);
Log.i(" after method exec", " method in androidx/appcompat/app/AppCompatActivity ,name=onCreate");
Log.i(" before method exec", " [ASM 測試] method in com/example/asm/MainActivity ,name=setContentView");
this.setContentView(2131361820);
Log.i(" after method exec", " method in com/example/asm/MainActivity ,name=setContentView");
Log.i(" before method exec", " [ASM 測試] method in android/util/Log ,name=e");
Log.e("aa", "aa");
Log.i(" after method exec", " method in android/util/Log ,name=e");
}
}
復(fù)制代碼
如果說你覺得好麻煩啊纲刀,那你也可以使用一個插件ASM Bytecode Outline
的工具來完成插樁后代碼的查看
每一個方法最后都被我們插入了我們要插入的代碼项炼,那ok,說明離我們通過注解來進(jìn)行插樁的目標(biāo)已經(jīng)邁出了一大步示绊。
如何通過注解完成
既然要用注解來完成事件锭部,那這個時候我們就創(chuàng)建一個注解,但是請注意其中的@Retention
注解寫法面褐,是需要在編譯期的時候進(jìn)行生效的拌禾。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ASM {}
復(fù)制代碼
然后你可以在MainActivity.java
中加入方法,并加上這個注解展哭。那接下來的事情是什么呢湃窍?想必就是掃到這個注解了,也就是使用了visitAnnotation()
的方法匪傍。
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return super.visitAnnotation(descriptor, visible);
}
復(fù)制代碼
但是縱觀繼承過來的方法您市,很顯然并不能說它本身并不能去修改這個注解所對應(yīng)的方法,所以我們最后的妥協(xié)只能是通過加入標(biāo)示符號役衡,當(dāng)要進(jìn)行方法插入的時候告訴visitMethodInsn()
我這段代碼他是需要去進(jìn)行插入的茵休。
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
if(ANNOTATION_TRACK_METHOD.equals(descriptor)) isMatch = true;
return super.visitAnnotation(descriptor, visible);
}
復(fù)制代碼
而visitMethodInsn()
這個方法在插入之前需要先進(jìn)行判定,如此需要才進(jìn)行插樁。以下就是插樁之后的結(jié)果:
public class MainActivity extends AppCompatActivity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131361820);
Log.e("aa", "aa");
}
@Cat
public void fun() {
Log.d("tag", "onCreate start");
Log.d("tag", "onCreate end");
}
@ASM
public void fun1() {
}
}
復(fù)制代碼
發(fā)布一個可以給別人用的插件
這個時候你不要在去在意Module
的名字了榕莺,定義你想要的名字俐芯。為了方便起見,可以選擇先拷貝一份之前buildSrc
中寫好的代碼钉鸯。既然是要發(fā)布吧史,那我們首先要干的事情就是使用Gradle
進(jìn)行upload
操作了。
// 在你新設(shè)置的Module --> build.gradle中加入以下代碼唠雕,你可以diy
uploadArchives {
repositories.mavenDeployer {
repository(url: uri('../repo'))
pom.groupId = 'com.example.asm'
pom.artifactId = 'asm_plugin'
pom.version = '1.0.0'
}
}
復(fù)制代碼
但是這個時候發(fā)布了并且在主工程進(jìn)行引入的話贸营,其實(shí)還是找不到我們的Plugin
插件的。
因?yàn)樗€需要一步操作及塘,創(chuàng)建如下的目錄莽使,這是為了讓我們發(fā)布的文件能夠被發(fā)現(xiàn)
implementation-class = com.example.asm_plugin.AsmPlugin // 插件在包中位置給出
復(fù)制代碼
最后在root --> build.gralde
中引入repo
锐极,就可以像buildSrc
一樣生效了笙僚。
buildscript {
repositories {
google()
jcenter()
maven {
url uri("repo")
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.4'
classpath 'com.example.asm:asm_plugin:1.0.0'
}
}
復(fù)制代碼
覺得有用的話,點(diǎn)個贊吧~~也歡迎在下方評論灵再,提出你的見解肋层。
作者:ClericYi
鏈接:https://juejin.im/post/6863276629029126158