最近寒休假期中,这二十七八天的,寻思着也不能浪费光阴啊,正好把以前写小玩意给捣鼓捣鼓。主要用到aspectj和apt,详细就不开展了,网上资料还是很多,推荐下面的博客 https://www.jianshu.com/p/dca3e2c8608a?from=timeline
Android AOP就是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。 aop的重要性这里就不在累述,搞软件都知道是个啥回事。 (你看我多懒,连介绍都是复制的,哈哈哈哈)
话不多说,代码先走一波,功能点不多,主要还是学习。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "wuzhi";
@Bean
public User user;
@Bean
public Book book;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IocFactory.bind(this);
Log.d(TAG, "model user: "+user +" ,book :"+book);
// background();
TryCatch();
}
@TryCatch
public void TryCatch() {
String aa = null;
int l = aa.length();
}
@Background(delay = 1000)
public void background() {
String name = Thread.currentThread().getName();
Log.d(TAG, name);
}
@UiThread(delay = 800)
public void UiThread(String name) {
String thName = Thread.currentThread().getName();
Log.d(TAG, name + " " + thName);
}
@TimeLog
public void TimeLogTest() {
try {
Thread.sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "Test");
}
public void httpTest() {
HttpQuery query = new HttpQuery(this, ApiFactory_.searchIssuesMap("android"));
// HttpQuery 对象包含网络请求参数 ,调用HttpRequest 时候,aop回在环绕通知中,根据注解网络请求参数,执行网络请求,在调用原始函数,返回网络结果
HttpRequest(query, 1);
}
@HttpRequest(url = ApiFactory_._searchIssues)
public void HttpRequest(HttpQuery query, int flag) {
Search search = query.getModel();
Log.d(TAG, "search "+ flag+" :"+ search);
}
@CacheAsync
public void saveMemoryCache() {
Log.d(TAG, "Test");
MemCache memCache2 = new MemCache("654321");
queryMemoryCache(memCache2);
}
@CacheAsync
public void queryMemoryCache(MemCache memCache) {
Object object = memCache.getCache();
Log.d(TAG, "MemoryCache :" + object);
}
@Receive(tag = "sticky", thread = Receive.BG)
public void onReceiveStickyEvent(User event) {
String name = Thread.currentThread().getName();
// ui2.btGridview.setText(event+"1");
Log.i("s", "x");
}
@Receive(tag = "sticky", type = STICKY)
public void onReceiveStickyEvent3(User event) {
String name = Thread.currentThread().getName();
// ui2.btCustomview.setText(event+"2");
}
@Receive(tag = "sticky1", type = STICKY)
public void onReceiveStickyEvent2(User event) {
String name = Thread.currentThread().getName();
// ui2.btCustomview.setText(event.getName()+"xxxxxxxxx");
}
@Override
protected void onDestroy() {
super.onDestroy();
OkBus.getInstance().unRegister(this);
}
}
@TryCatch 功能:异常处理。由于写代码难免粗心,虽然很想避免,但是不怕一万就怕万一啊,管他三七二十一,try先走一个,写多也觉得恶心,怎么办,aop aspectj来一个。由aop aspectj在@Around 环绕通知时,统一加上try代码块,proceed()执行原生方法时,内部抛出异常,也不会导致程序崩溃。
/**
* Created by admin on 14/03/2017. 异常处理
*/
@Aspect
public class TryCatchAOP {
private static final String TAG = "cn.jesse.aop";
@Around("execution(@cn.jesse.apilib.annotation.aop.TryCatch * *(..))")
public void tryCatchMethodTriggered(ProceedingJoinPoint joinPoint) throws Throwable{
try{
joinPoint.proceed();
}catch (Exception e){
String targetClassName = joinPoint.getTarget().getClass().getName();
String signatureName = joinPoint.getSignature().getName();
String log="TryCatch ["+targetClassName + "" + signatureName+" Exception : "+e.getMessage()+"]";
Log.e(TAG, log);
}
}
}
我们来看看是如何实现的,反编译apk之后,我们来看着四个代码片段。
1 )片段1
2 )片段2
3)片段3
4)片段4
我们可以发现原生函数被aop改写,构造TryCatchAOP 对象 ,执行aop切入代码,并生成内部类AjcClosure1作为入参回调执行 真正的TryCatch函数代码。
@Background 功能:目标函数将在子线程中调用 delay : 延迟多少时间执行
@Background(delay = 1000)
public void background() {
String name = Thread.currentThread().getName();
Log.d(TAG, name);
}
@UiThread 功能:目标函数将在UI线程中调用 delay : 延迟多少时间执行
@UiThread(delay = 800)
public void UiThread(String name) {
String thName = Thread.currentThread().getName();
Log.d(TAG, name + " " + thName);
}
相同的功能最初是在androidannotations框架,第一次见到还有这功能,只能一句卧槽,ZTM简洁优雅,没有丝毫多余的代码,就一个注解完事,不过她的实现原理是来自APT ,比如你写个MainActivity ,她会生成一个MainActivity_的子类,然后在AndroidManifest注册的也是这个子类,子类重写父类的函数,然后在其中插入这些Thread 、Handler之类的代码,super一下父类的函数,卧槽,MainActivity完全没有多余的操作,不过后来谷歌推出了AS,不知道是不是嫌弃as太卡了,居然把实时编译的功能给取消了,这就有点头疼了,写个相应的类,还要手动编译下,也是蛮头疼的,后面也就放弃了。
其余的aop注解功能代码不复杂,我就不在累述。
由于我是个比较懒的人,既然可以在函数之前和之后做点啥,那网络请求能不能结合aop做点啥,答案是可以的。不过在这之前,我们还要搞清几个问题。
1、网络请求的参数从哪里传递
这里我想到一个是注解的参数,还有一个函数入参,既然aspectj可以得到函数的名称、入参,返回值啥的,我能不能专门弄个类,用来传递网络参数与返回结果。
2 、如何关联网络请求框架
如何让aop知道,a函数需要请求用户登录接口,b函数需要请求注册接口,既然可以传递参数了,那我能不能对网络请求接口做个标识符,用户登录接口为1,注册接口为2,既然可以传递参数给aop 了,那我就在aop 中根据获得的标识符,判断去请求1还是2。
但是这是个麻烦,你不可能写个接口,然后自己加一个标识符吧,根据标识符去执行请求也需要手动加个判断,这也太麻烦了,小半天的功夫就写这些标识了,不行不行,怎么办,这时候我就想到apt,用apt来生成这些标识符,妥妥的。
3、第三步就简单了,编写网络请求,然后根据apt生成标识符和判断,用 aspectj根据标识判断函数,去执行具体网络请求。
我们先看如何使用,这里我用的是网络框架是 retrofit2 ,
public void httpTest() {
HttpQuery query = new HttpQuery(this, ApiFactory_.searchIssuesMap("android"));
// HttpQuery 对象包含网络请求参数 ,调用HttpRequest 时候,aop回在环绕通知中,根据注解网络请求参数,执行网络请求,在调用原始函数,返回网络结果
HttpRequest(query, 1);
}
@HttpRequest(url = ApiFactory_._searchIssues)
public void HttpRequest(HttpQuery query, int flag) {
Search search = query.getModel();
Log.d(TAG, "search "+ flag+" :"+ search);
}
HttpQuery 对象用于传递网络请求的参数和返回结果,@ApiFactory 用来生成标识符和借口请求代码,我们先来看看这个apt用这个注解生成了啥代码。
@ApiFactory为给每一个接口生成一个标识 ,这里searchIssues为1,然后由distributeHttp函数负责根据标识,去调用searchIssues去执行retrofit2中的searchIssues网络接口。
/**
* @ API工厂 此类由apt自动生成 */
public final class ApiFactory_ implements ApiFactoryInterface {
public static final int _searchIssues = 1;
public static final String q = "q";
/**
* @此方法由apt自动生成 */
public Object distributeHttp(int url, HashMap map) {
switch(url){
case _searchIssues: return searchIssues(map);
} return null;
}
/**
* @此方法由apt自动生成 */
public static Object searchIssues(HashMap param) {
java.lang.String qpar=(java.lang.String)param.get(q);
com.app.model.Search returnObj=null;
try{
returnObj= Api.getInstance().service.searchIssues(qpar).execute().body();
}catch (Exception e){e.printStackTrace();
return null;
}
return returnObj;
}
/**
* @此方法由apt自动生成 */
public static HashMap searchIssuesMap(String qpar) {
HashMap map=new HashMap();
map.put(q,qpar);
return map;
}
/**
* @此方法由apt自动生成 */
public String getHttpCacheKey(int urlCode) {
switch(urlCode){
case _searchIssues: return "search/issues_searchIssues";
} return null;
}
}
我们在看看aop里面的代码,代码不多。
public class HttpRequestController implements ControllerI {
private static final String TAG = "cn.jesse.aop";
private ApiFactoryInterface apiFactoryInterface;
@Override
public void proceedWork(final ProceedingJoinPoint joinPoint) {
try {
final String targetClassName = joinPoint.getTarget().getClass().getName();
final String signatureName = joinPoint.getSignature().getName();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
final HttpRequest httpRequest = method.getAnnotation(HttpRequest.class);
final long startTime = System.nanoTime();
final Object[] args = joinPoint.getArgs();
Runnable httpThreadRun = new Runnable() {
@Override
public void run() {
threadHttpProceed(joinPoint, httpRequest, args, targetClassName, signatureName, startTime);
}
};
FutureTask futureTask = new FutureTask(httpThreadRun, null);
ThreadPoolManager.getInstance().execte(futureTask);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
/**
* 子线程访问网络
* @param joinPoint
* @param httpRequest
* @param args
* @param targetClassName
* @param signatureName
* @param startTime
*/
private void threadHttpProceed(ProceedingJoinPoint joinPoint, HttpRequest httpRequest, Object[] args, String targetClassName, String signatureName, long startTime) {
try {
HttpQuery httpQuery=null;
int index=0;
for(int i=0;i
日志输出如下,至此整个流程走通,获得函数注解HttpRequest中url的值,然后去调用ApiFactory_ 中distributeHttp函数请求网络,返回网络请求结果,保存在HttpQuery对象中,joinPoint.proceed(args) 执行原生函数。打完收工。
还有一些其他的aop功能,这里就不多解释了,实现原理都是一样的,当然aop的作用这里只是九牛一毛,就如同博客中所言,AOP还可以做很多事,弥补OOP的不足,把所有跨对象的横切面关注点的功能都可以提取出来用AOP去实现 ,好处显而易见,将来要改的地方永远只有一处,而不是像OOP那样牵扯很多模块很多代码很多类。
当然还有一些apt的功能,比如基于apt的零反射的OkBus ,比如IOC,这里只注入对象,没有写控件的注入,我自己写了一个as的插件,专门用于控件生成,类似于ButterKnife 的插件https://www.cnblogs.com/springl/p/9617199.html 只不过我扩展了一下,增加了FreeMarker,可以自行配置注解和生成的代码。
说道这里当时正好在AS研究插件,用于根据配置的代码模板在as中生成相对于的Activity、Adapter这些基础代码模板,类似于as自带的模板功能,详情可以看这里https://www.jianshu.com/p/607baf7f9899 ,最开始是在ec上捣鼓,基于FreeMarker用apt把代码生成到src目录中,还没写完,得,谷歌就推出了as,还能咋办,只能跟着大佬的步伐啊,后面发现as有自带的模板扩展功能,不过一次只能生成一个业务块,也没法扩展更多的功能,然后就仿写了as模板的ui界面、语法和标签作用,加入了一些自己的功能,有时间下次再拿出来马克马克。
当然代码至此还没有结束,且听下回分解。也祝大家团子节快乐。