原文鏈接:https://blog.csdn.net/cuterabbitbaby/article/details/80559989
IoC 是編程原則 - 不是特定的產(chǎn)品, 不是具體實(shí)現(xiàn)方式, 當(dāng)然也和具體編程語言無關(guān)
在傳統(tǒng)編程范式中, 程序調(diào)用可重用的庫
在 IoC 原則下, 程序接受通用框架的調(diào)用控制 - 框架調(diào)用程序代碼
-
與 IoC 原則相關(guān)的概念包括:
-
IoC 的設(shè)計(jì)目的包括:
將執(zhí)行任務(wù)和任務(wù)實(shí)現(xiàn)解耦
讓模塊專注于設(shè)計(jì)任務(wù)
模塊僅依賴于設(shè)計(jì)契約而無需關(guān)注其他系統(tǒng)如何工作
避免模塊替換時(shí)的副作用
IOC和APO區(qū)別
簡單來講兩者一個(gè)是思想一個(gè)是原則牵舵,我們運(yùn)用AOP思想抽離東西之后,又運(yùn)用IOC原則添加其復(fù)用性,減少過度依賴俘枫。
AspectJ
使用場景基于AOP的動(dòng)態(tài)權(quán)限管理囱晴、基于AOP的業(yè)務(wù)數(shù)據(jù)埋點(diǎn)箱季、基于AOP的性能監(jiān)測系統(tǒng)等等裸删。
配置中Gradle說明:http://www.reibang.com/p/c66f4e3113b3
使用說明:https://blog.csdn.net/innost/article/details/49387395
術(shù)語
Joinpoint(連接點(diǎn)) 所謂連接點(diǎn)是指那些被攔截到的點(diǎn)
Pointcut(切入點(diǎn)) 所謂切入點(diǎn)是指我們要對(duì)哪些 Joinpoint 進(jìn)行攔截的定義
Advice(通知/增強(qiáng)) 所謂通知是指攔截到 Joinpoint 之后所要做的事情就是通知
Introduction(引介) 引介是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運(yùn)行期為類 動(dòng)態(tài)地添加一些方法或 Field
Target(目標(biāo)對(duì)象) 代理的目標(biāo)對(duì)象
Weaving(織入) 是指把增強(qiáng)應(yīng)用到目標(biāo)對(duì)象來創(chuàng)建新的代理對(duì)象的過程. AspectJ 采用編譯期織入和類裝在期織入
Proxy(代理) 一個(gè)類被 AOP 織入增強(qiáng)后,就產(chǎn)生一個(gè)結(jié)果代理類
Aspect(切面) 是切入點(diǎn)和通知(引介)的結(jié)合
Join Points | 說明 | 示例 |
---|---|---|
method call | 函數(shù)調(diào)用 | 比如調(diào)用Log.e()换帜,這是一處JPoint |
method execution | 函數(shù)執(zhí)行 | 比如Log.e()的執(zhí)行內(nèi)部楔壤,是一處JPoint。注意它和method call的區(qū)別惯驼。method call是調(diào)用某個(gè)函數(shù)的地方蹲嚣。而execution是某個(gè)函數(shù)執(zhí)行的內(nèi)部。 |
constructor call | 構(gòu)造函數(shù)調(diào)用 | 和method call類似 |
constructor execution | 構(gòu)造函數(shù)執(zhí)行 | 和method execution類似 |
field get | 獲取某個(gè)變量 | 比如讀取DemoActivity.debug成員 |
field set | 設(shè)置某個(gè)變量 | 比如設(shè)置DemoActivity.debug成員 |
pre-initialization | Object在構(gòu)造函數(shù)中做得一些工作祟牲。 | 很少使用 |
initialization Object | 在構(gòu)造函數(shù)中做得工作 | 很少使用 |
static initialization | 類初始化 | 比如類的static{} |
handler | 異常處理 | 比如try catch(xxx)中隙畜,對(duì)應(yīng)catch內(nèi)的執(zhí)行 advice execution 這個(gè)是AspectJ的內(nèi)容,稍后再說 |
advice execution | 這個(gè)是AspectJ的內(nèi)容说贝,稍后再說 |
Advice分類(通知類型)
Before
前置通知, 在目標(biāo)方法執(zhí)行之前執(zhí)行通知方法(應(yīng)用:各種校驗(yàn))After
后置通知, 目標(biāo)方法執(zhí)行后執(zhí)行通知方法(應(yīng)用:清理現(xiàn)場)Around
環(huán)繞通知, 在通知方法中執(zhí)行目標(biāo)方法執(zhí)行, 控制目標(biāo)方法執(zhí)行時(shí)機(jī), (應(yīng)用:十分強(qiáng)大议惰,可以做任何事情)AfterReturning
后置返回通知, 目標(biāo)方法正常返回返回后執(zhí)行通知,所以可以獲得方法的返回值乡恕。(應(yīng)用:常規(guī)數(shù)據(jù)處理)AfterThrowing
異常通知, 目標(biāo)方法執(zhí)行后拋出異常時(shí)執(zhí)行通知 (應(yīng)用:包裝異常信息)
切入點(diǎn)指示符
切入點(diǎn)表達(dá)式可以通過&&言询、||和俯萎!進(jìn)行組合,也可以通過名字引用切入點(diǎn)表達(dá)式
通過組合倍试,可以建立更加復(fù)雜的切入點(diǎn)表達(dá)式
1. 選擇特定類型的連接點(diǎn)讯屈,如:execution蛋哭,get县习,set,call谆趾,handler
-
下面是一個(gè)Method Signature的execution
@Pointcut("execution(@com.dn_alan.myapplication.annotation.BehaviorTrace * *(..))||withincode()") public void methodAnnottatedWithBehaviorTrace() { Log.d("alan","methodAnnottatedWithBehaviorTrace BehaviorTraceAspect"); }
完整的表達(dá)式:@注解 訪問權(quán)限 返回值的類型 包名.函數(shù)名(參數(shù))
1. @注解和訪問權(quán)限(public/private/protect躁愿,以及static/final)屬于可選項(xiàng)。
如果不設(shè)置它們沪蓬,則默認(rèn)都會(huì)選擇彤钟。以訪問權(quán)限為例,如果沒有設(shè)置訪問權(quán)限作為條件跷叉,那么public逸雹,private,protect及static云挟、final的函數(shù)都會(huì)進(jìn)行搜索梆砸。
2. 返回值類型就是普通的函數(shù)的返回值類型。
如果不限定類型的話园欣,就用通配符表示
3. 包名.函數(shù)名用于查找匹配的函數(shù)帖世。
可以使用通配符,包括*和..以及+號(hào)沸枯。其中*號(hào)用于匹配除.號(hào)之外的任意字符日矫,而..則表示任意子package,+號(hào)表示子類绑榴。
比如:
java.*.Date:可以表示java.sql.Date哪轿,也可以表示java.util.Date
Test:可以表示T estBase,也可以表示TestDervied
java..*:表示java任意子類
java..*Model+:表示Java任意package中名字以Model結(jié)尾的子類翔怎,比如TabelModel缔逛,TreeModel
等
4. 最后來看函數(shù)的參數(shù)。
參數(shù)匹配比較簡單姓惑,主要是參數(shù)類型褐奴,比如:
(int, char):表示參數(shù)只有兩個(gè),并且第一個(gè)參數(shù)類型是int于毙,第二個(gè)參數(shù)類型是char
(String, ..):表示至少有一個(gè)參數(shù)敦冬。并且第一個(gè)參數(shù)類型是String,后面參數(shù)類型不限唯沮。在參數(shù)匹配中脖旱,
..代表任意參數(shù)個(gè)數(shù)和類型
(Object ...):表示不定個(gè)數(shù)的參數(shù)堪遂,且類型都是Object,這里的...不是通配符萌庆,而是Java中代表不定參數(shù)的意思 -
Constructorsignature和Method Signature類似溶褪,只不過構(gòu)造函數(shù)沒有返回值,而且函數(shù)名必須叫new践险。
比如:public *..TestDerived.new(..):- public:選擇public訪問權(quán)限
*..代表任意包名 - TestDerived.new:代表TestDerived的構(gòu)造函數(shù)
(..):代表參數(shù)個(gè)數(shù)和類型都是任意
- public:選擇public訪問權(quán)限
-
再來看Field Signature和Type Signature猿妈,
- Field Signature標(biāo)準(zhǔn)格式:
@注解 訪問權(quán)限 類型 類名.成員變量名
其中,@注解和訪問權(quán)限是可選的
類型:成員變量類型巍虫,代表任意類型
類名.成員變量名:成員變量名可以是彭则,代表任意成員變量
比如,
set(inttest..TestBase.base):表示設(shè)置TestBase.base變量時(shí)的JPoint - Type Signature:直接上例子
staticinitialization(test..TestBase):表示TestBase類的static block
handler(NullPointerException):表示catch到NullPointerException的JPoint占遥。
- Field Signature標(biāo)準(zhǔn)格式:
2:確定連接點(diǎn)的范圍俯抖,如:within,withincode
關(guān)鍵詞 | 說明 | 示例 |
---|---|---|
within(TypePattern) | TypePattern標(biāo)示package或者類瓦胎。TypePatter可以使用通配符 | 表示某個(gè)Package或者類中的所有JPoint芬萍。比如within(Test):Test類中(包括內(nèi)部類)所有JPoint。 |
withincode(Constructor Signature| Method Signature) | 表示某個(gè)構(gòu)造函數(shù)或其他函數(shù)執(zhí)行過程中涉及到的JPoint | 比如withinCode(* TestDerived.testMethod(..))表示testMethod涉及的JPoint,withinCode( *.Test.new(..))表示Test構(gòu)造函數(shù)涉及的JPoint |
cflow(pointcuts) | cflow是call flow的意思cflow的條件是一個(gè)pointcut | 比如cflow(call TestDerived.testMethod):表示調(diào)用TestDerived.testMethod函數(shù)時(shí)所包含的JPoint搔啊,包括testMethod的call這個(gè)JPoint本身 |
cflowbelow(pointcuts) | cflow是call flow的意思柬祠。 | 比如 cflowblow(call TestDerived.testMethod):表示調(diào)用TestDerived.testMethod函數(shù)時(shí)所包含的JPoint,不包括testMethod的call這個(gè)JPoint本身 |
this(Type) | JPoint的this對(duì)象是Type類型坯癣。(其實(shí)就是判斷Type是不是某種類型瓶盛,即是否滿足instanceof Type的條件) | JPoint是代碼段(不論是函數(shù),異常處理示罗,static block)惩猫,從語法上說,它都屬于一個(gè)類蚜点。如果這個(gè)類的類型是Type標(biāo)示的類型轧房,則和它相關(guān)的JPoint將全部被選中。 示例的testMethod是TestDerived類绍绘。所以 this(TestDerived)將會(huì)選中這個(gè)testMethod JPoint |
target(Type) | JPoint的target對(duì)象是Type類型 | 和this相對(duì)的是target奶镶。不過target一般用在call的情況。call一個(gè)函數(shù)陪拘,這個(gè)函數(shù)可能定義在其他類厂镇。比如testMethod是TestDerived類定義的。那么 target(TestDerived)就會(huì)搜索到調(diào)用testMethod的地方左刽。但是不包括testMethod的execution JPoint |
args(TypeSignature) | 用來對(duì)JPoint的參數(shù)進(jìn)行條件搜索的 | 比如args(int,..)捺信,表示第一個(gè)參數(shù)是int,后面參數(shù)個(gè)數(shù)和類型不限的JPoint欠痴。 |
- within(com.itheima.aop..*)
- this(com.itheima.aop.user.UserDAO)
- target(com.itheima.aop.user.UserDAO)
- args(int,int)
- bean('userServiceId')
代碼
世界上沒有憑空調(diào)用迄靠,不管你是動(dòng)態(tài)代理或者注解或者AspectJ 都是有注入口存在秒咨。
APJ還對(duì)我們主動(dòng)調(diào)用的方法做增加,并在靜態(tài)代碼塊中做了初始化掌挚。我們調(diào)用這些有標(biāo)識(shí)符的方法雨席,它就會(huì)主動(dòng)調(diào)用我們的加了Aspect標(biāo)識(shí)的類中符合邏輯的方法,這樣我們就可以抽離出共性邏輯吠式,做一個(gè)單獨(dú)的模塊陡厘。
另外withincode這個(gè)標(biāo)識(shí)符有問題,省略缺少奇徒,各種寫法都嘗試了都不行雏亚,偶爾還爆空指針缨硝,從1.9降到1.8也不行摩钙,不知道網(wǎng)上的人是怎么寫出來了的。
@Aspect
public class BehaviorTraceAspect {
//定義切面的規(guī)則
//1查辩、就再原來的應(yīng)用中那些注解的地方放到當(dāng)前切面進(jìn)行處理
@Pointcut("execution(@com.example.apjdemo.annotation.BehaviorTrace * *(..))"
// + "&& args() "
// + "&& args(android.view.View) "
// + //"&& within(MainActivity)"
+"&& !withincode(@com.example.apjdemo.annotation.BehaviorTrace void com.example.apjdemo.MainActivity.mAudio())"
+"")
public void methodAnnottatedWithBehaviorTrace() {
}
@Pointcut("execution(@com.example.apjdemo.annotation.BehaviorTrace * *(..)) " +
"&& methodAnnottatedWithBehaviorTrace() " +
"&& !within(MainActivity)")
public void all() {
}
//2胖笛、對(duì)進(jìn)入切面的內(nèi)容如何處理
//@Before 在切入點(diǎn)之前運(yùn)行
// @After("methodAnnottatedWithBehaviorTrace()")
//@Around 在切入點(diǎn)前后都運(yùn)行
@Around("methodAnnottatedWithBehaviorTrace()||all()")
public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
String value = methodSignature.getMethod().getAnnotation(BehaviorTrace.class).value();
long begin = System.currentTimeMillis();
Object result = joinPoint.proceed();
SystemClock.sleep(100);
long duration = System.currentTimeMillis() - begin;
Log.e("pp", String.format("%s功能:%s類的%s方法執(zhí)行了,用時(shí)%d ms",
value, className, methodName, duration));
return result;
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BehaviorTrace {
String value();
}
@BehaviorTrace("語音消息")
public void mShake(View view) {
Log.e("mShake","mShake");
}
//語音消息
public void mAudio(View view) {
Log.e("mAudio","mAudio");
mShake(view);
kewudewithin();
}
//視頻通話
public void mVideo(View view) {
Log.e("mVideo","mVideo");
kewudewithincode();
}
public void kewudewithin(){
Log.e("kewudewithin","kewudewithin");
bumingsuoyi();
}
public void kewudewithincode() {
Log.e("kewudewithincode","kewudewithincode");
bumingsuoyi();
}
@BehaviorTrace("bumingsuoyi")
public void bumingsuoyi() {
}
當(dāng)期兩個(gè)項(xiàng)目對(duì)比下gradle配置
動(dòng)態(tài)代理
摘至作者:jxiang112http://www.reibang.com/p/85d181d7d09a
流程總結(jié):
校驗(yàn)代理類方法調(diào)用處理程序h不能為空
動(dòng)態(tài)生成代理類class文件格式字節(jié)流
通過loader加載創(chuàng)建代表代理類的class對(duì)象
根據(jù)代理類的構(gòu)造器創(chuàng)建代理類宜岛,
根據(jù)代理類接口先得到代理類的類全限名长踊、方法列表、異常列表通過ProxyClassFactory內(nèi)部調(diào)用了native層方法
返回動(dòng)態(tài)創(chuàng)建生成的代理類
- Proxy.java
static final 私有內(nèi)部類 ProxyClassFactory和KeyFactory
getProxyClass0,內(nèi)有判斷實(shí)現(xiàn)的接口數(shù)量不能超過65535就是Short類型的最大值
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
Proxy.newProxyInstance()->getProxyClass0(loader, intfs)
->proxyClassCache.get(loader, interfaces)->WeakCache.get(K key, P parameter)
- WeakCache.java
new KeyFactory(), new ProxyClassFactory() 一個(gè)是key的生成器扒寄,一個(gè)value的生成器
Factory是WeakCache的final內(nèi)部類實(shí)現(xiàn)了Supplier接口
對(duì)象初始化時(shí)就會(huì)有個(gè)緩存map
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
= new ConcurrentHashMap<>();
WeakCache.get()方法:
supplier實(shí)際就是Factory
獲取當(dāng)前key對(duì)應(yīng)的cacheKey的緩存supplier的map胖喳,如果沒有創(chuàng)建map并放入緩存map
從緩存supplier的map中獲取supplier
factory = null
while循環(huán) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
如果factory = null
factory = new Factory(key, parameter, subKey, valuesMap);
如果supplier = null
把factory放入cacheKey對(duì)應(yīng)的緩存supplier的map
supplier = factory;
}
Factory.get->value = Objects.requireNonNull(valueFactory.apply(key, parameter))->valueFactory構(gòu)造方法傳入 new ProxyClassFactory()->ProxyClassFactory.apply(ClassLoader loader, Class<?>[] interfaces)
- ProxyClassFactory.java
- 根據(jù)代理類接口先得到代理類的類全限名雾狈、方法列表、異常列表
- 根據(jù)步驟1中的類全限名阱佛、方法列表、異常列表戴而、接口列表生成class文件格式的字節(jié)流凑术,其中方法的實(shí)現(xiàn)會(huì)最終調(diào)用InvoationHanlder的invoke方法
@FastNative private static native Class<?> generateProxy(
String name, Class<?>[] interfaces,ClassLoader loader, Method[] methods, Class<?>[][] exceptions);
- 使用類加載器加載步驟2中的字節(jié)流,創(chuàng)建生成動(dòng)態(tài)代理類對(duì)象
- 使用步驟3中創(chuàng)建生成的代理類對(duì)象
使用
IMy iMy = new MyImpl();
MyProxy myProxy = new MyProxy(iMy);
myProxy.ganni();
IMe iMe = new MeImpl();
MeProxy meProxy = new MeProxy(iMe);
meProxy.dani();
DynamicProxy dynamicProxy = new DynamicProxy(meProxy);
IMe iMeProxy = (IMe) Proxy.newProxyInstance(getClassLoader(), new Class[]{IMe.class}, dynamicProxy);
iMeProxy.dani();
DynamicProxy dynamicProxy2 = new DynamicProxy(myProxy);
IMy iMyProxy = (IMy) Proxy.newProxyInstance(getClassLoader(), new Class[]{IMy.class}, dynamicProxy2);
iMyProxy.ganni();
public interface IMe {
String dani();
}
public interface IMy {
String ganni();
}
public class MeImpl implements IMe {
@Override
public String dani() {
return null;
}
}
public class MeProxy implements IMe{
private final IMe mIme;
public MeProxy(IMe iMe) {
mIme = iMe;
}
@Override
public String dani() {
da110();
return mIme.dani();
}
private void da110(){
Log.e("MeProxy","woda110");
}
}
public class MyImpl implements IMy {
@Override
public String ganni() {
return "laiba";
}
}
public class MyProxy implements IMy {
private final IMy mIMy;
public MyProxy(IMy iMy) {
mIMy = iMy;
}
@Override
public String ganni() {
da110();
return mIMy.ganni();
}
private void da110(){
Log.e("MyProxy","woda110");
}
}
public class DynamicProxy implements InvocationHandler {
private final Object mobject;
public DynamicProxy(Object object) {
mobject = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e("MyProxy",proxy.getClass().getName());
Log.e("MyProxy",mobject.getClass().getName());
return method.invoke(mobject,args);
}
private void da110(){
Log.e("MyProxy","wotisuoyouren da110");
}
}
AutoService
利用@AutoService(IFunction.class)通過ServiceLoader.load(IFunction::class.java)找到所有實(shí)現(xiàn)類
有人說可以用來打包http://www.reibang.com/p/a94e6a32f10f
這里主要介紹@AutoService(Processor.class)就是APT
APT&&Javapoet
中間還加如了javapoet
-
注解處理Moduel
/** * 這個(gè)類就是APT */ @AutoService(Processor.class) public class MyAnotationCompile extends AbstractProcessor { //1.定義一個(gè)用于生成文件的對(duì)象 Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); filer = processingEnvironment.getFiler(); } //2.需要確定當(dāng)前APT處理所有模塊中哪些注解 //可以用注解標(biāo)識(shí)@SupportedAnnotationTypes() @Override public Set<String> getSupportedAnnotationTypes() { Set<String> set = new HashSet<>(); set.add(MyAnntation.class.getCanonicalName()); return set; } //3.支持的JDK的版本 //沒有會(huì)報(bào)錯(cuò) @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } /** * 在這個(gè)方法中所意,我們?nèi)ド蓪?shí)現(xiàn)類 * @param set * @param roundEnvironment * @return */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MyAnntation.class); Map<String, List<VariableElement>> map = new HashMap<>(); for (Element element : elementsAnnotatedWith) { VariableElement variableElement = (VariableElement) element; String activityName = variableElement.getEnclosingElement().getSimpleName().toString(); List<VariableElement> elementList = map.get(activityName); if (elementList == null) { elementList = new ArrayList<>(); map.put(activityName, elementList); } elementList.add(variableElement); } if (map.size() > 0) { Writer writer = null; Iterator<String> iterator = map.keySet().iterator(); Writer writer1 = null; while (iterator.hasNext()) { String activityName = iterator.next(); List<VariableElement> elementList = map.get(activityName); TypeElement enclosingElement = (TypeElement) elementList.get(0).getEnclosingElement(); String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString(); for (VariableElement variableElement : elementList) { String variableName = variableElement.getSimpleName().toString(); String name = variableElement.getAnnotation(MyAnntation.class).value(); // try { // JavaFileObject classFile = filer.createSourceFile(packageName+"." + name); // writer1 = classFile.openWriter(); // writer1.write("package "+packageName+"." + name); // } catch (IOException e) { // e.printStackTrace(); // } finally { // try { // writer1.close(); // } catch (IOException e) { // e.printStackTrace(); // } // } MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") .build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(main) .build(); JavaFile javaFile = JavaFile.builder(packageName+"." + name, helloWorld) .build(); try { javaFile.writeTo(filer); } catch (IOException e) { e.printStackTrace(); } finally { } } } } return true; // //獲取控件的名字 // String variableName=variableElement.getSimpleName().toString(); // //獲取ID // int id=variableElement.getAnnotation(BindView.class).value(); // //獲取控件的類型 // TypeMirror typeMirror=variableElement.asType(); // 如果APT過程中生成的類也需要進(jìn)行注解處理的話則需要返回false淮逊,方便再一次執(zhí)行。 // 上層是 processImpl方法 } }
implementation project(path: ':libannotation') implementation "com.squareup:javapoet:1.11.1" annotationProcessor'com.google.auto.service:auto-service:1.0-rc4' compileOnly'com.google.auto.service:auto-service:1.0-rc4'
-
注解module(libannotation):
@Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface MyAnntation { String value(); }
-
appmodule:
public class MainActivity extends AppCompatActivity { @MyAnntation("nihaoa") View view; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
implementation project(path: ':libannotation') implementation "com.squareup:javapoet:1.11.1" annotationProcessor project(path: ':libanotationcompile')