AOP是面向切面编程的简称
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术,个人感觉是将“某一段代码”放到某个切口的地方,这样做的目的是统一管理这个“某一段代码”
推荐几篇不错的文章
https://www.jianshu.com/p/f1770b9dce27
https://www.cnblogs.com/HanJunJun-blog/p/10650509.html
https://www.cnblogs.com/yxx123/p/6665736.html
下面举两个例子 都用到AOP的思想
Application监听所有Activity 的生命周期
public class MainApplication extends Application {
private final static String TAG = "MainApplication";
@Override
public void onCreate() {
super.onCreate();
listenActivityLifecycleCallbacks();
}
private void listenActivityLifecycleCallbacks() {
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
Log.e(TAG, "onActivityCreated: " + activity.getPackageName() + "." + activity.getLocalClassName());
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
Log.e(TAG, "onActivityStarted: " + activity.getPackageName() + "." + activity.getLocalClassName());
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
Log.e(TAG, "onActivityResumed: " + activity.getPackageName() + "." + activity.getLocalClassName());
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
Log.e(TAG, "onActivityPaused: " + activity.getPackageName() + "." + activity.getLocalClassName());
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
Log.e(TAG, "onActivityStopped: " + activity.getPackageName() + "." + activity.getLocalClassName());
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
Log.e(TAG, "onActivitySaveInstanceState: " + activity.getPackageName() + "." + activity.getLocalClassName());
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
Log.e(TAG, "onActivityDestroyed: " + activity.getPackageName() + "." + activity.getLocalClassName());
}
});
}
}
这里在Application中切入Activity的各种生命周期 可以对各个Activity生命周期进行统一的处理
下面我们想象一个应用场景:
界面上有6个button,我们期望没有网络的时候 所有按钮都无法点击,我们应该如何做呢?在所有的button的点击事件都监听网络状态?
现在我们可以使用AOP的思想完成这个需求。下面我演示2中方案实现
IOC监听网络 拦截点击事件
关于IOC如果不是很了解 可以参考我之前的文章 这里我只给出实现
https://blog.csdn.net/u011109881/article/details/113762180
https://blog.csdn.net/u011109881/article/details/113854660
CheckNetThenClick.java :定义注解
@Target(ElementType.METHOD)
public @Retention(RetentionPolicy.RUNTIME)
@interface CheckNetThenClick {
//value代表可以该注解可以添加一个参数
int value();
}
ViewFinder.java : 工具类 用于查找view
/**
* Created by hjcai on 2021/2/5.
* <p>
* 主要是调用findViewById
*/
class ViewFinder {
private Activity mActivity;
public ViewFinder(Activity activity) {
this.mActivity = activity;
}
public View findViewById(int viewId) {
return mActivity.findViewById(viewId);
}
}
Utils.java 工具类2 用于判断网络状态
注意这里网络判断使用的是较新的接口(Android 9) 不适应低版本
public class Utils {
private static final String TAG = "Utils";
/**
* 检查当前网络是否可用
*/
public static boolean isNetworkAvailable(Context context) {
boolean connected = false;
// 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
StringBuilder sb = new StringBuilder();
if (connectivityManager != null) {
// 获取NetworkInfo对象
Network[] networks = connectivityManager.getAllNetworks();
//用于存放网络连接信息
for (int i = 0; i < networks.length; i++) {
//获取ConnectivityManager对象对应的NetworkInfo对象
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(networks[i]);
sb.append(networkInfo.getTypeName() + " connect is " + networkInfo.isConnected());
if (networkInfo.isConnected()) {
connected = true;
}
}
}
Log.e(TAG, "isNetworkAvailable: " + sb);
Log.e(TAG, "connected: " + connected);
return connected;
}
}
ViewUtils.java: IOC 核心实现 利用反射给view添加点击事件 并在点击事件实际调用前统一判断网络状态
如果网络没有链接 阻断执行
public class ViewUtils {
private static final String TAG = "ViewUtils";
//Activity绑定
public static void injectActivity(Activity activity) {
injectActivity(new ViewFinder(activity), activity);
}
private static void injectActivity(ViewFinder finder, Object object) {
injectActivityEvent(finder, object);
}
private static void injectActivityEvent(ViewFinder finder, Object object) {
//1 反射 获取class里面所有属性
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();//获取Activity的所有方法包括私有和共有
for (Method method : methods) {
//2 遍历fields 找到添加了注解CheckNetThenClick的method
CheckNetThenClick check = method.getAnnotation(CheckNetThenClick.class);
if (check != null) {
//3 获取注解里面的id值
int viewId = check.value();
View view = finder.findViewById(viewId);//相当于调用Activity.findViewById
if (view != null) {
// 4. 给每个view设置点击事件
view.setOnClickListener(new DeclaredOnClickListener(method, object));
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
// mObject是当前activity
private Object mObject;
// mMethod是添加了CheckNetThenClick的方法
private Method mMethod;
public DeclaredOnClickListener(Method method, Object object) {
this.mObject = object;
this.mMethod = method;
}
@Override
public void onClick(View v) {
try {
// 所有方法都可以 包括私有共有
mMethod.setAccessible(true);
// 反射执行方法
// 当View被点击时执行
if (!Utils.isNetworkAvailable(v.getContext())){
Toast.makeText(v.getContext(),"没有网络!",Toast.LENGTH_SHORT).show();
// 阻断执行
return;
}
mMethod.invoke(mObject, v);//通过反射 调用mObject的声明了CheckNetThenClick注解了的方法 参数为v
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, null);
Log.e(TAG, "onClick: xxxx ");
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
}
应用:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="check network1"/>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="check network2"/>
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="check network3"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.injectActivity(MainActivity.this);
}
@CheckNetThenClick(R.id.button1)
public void network1() {
Toast.makeText(MainActivity.this,"访问网络1!",Toast.LENGTH_SHORT).show();
}
@CheckNetThenClick(R.id.button2)
public void network2() {
Toast.makeText(MainActivity.this,"访问网络2!",Toast.LENGTH_SHORT).show();
}
@CheckNetThenClick(R.id.button3)
public void network3() {
Toast.makeText(MainActivity.this,"访问网络3!",Toast.LENGTH_SHORT).show();
}
}
AOP: 牛刀初试
准备工作:
1.从官网下载AspectJ 并安装
https://www.eclipse.org/aspectj/downloads.php
我下载的是目前最新的版本 aspectj-1.9.6.jar
2.新建Android项目
3.从安装路径下拷贝aspectjrt.jar到libs
路径大致为 <安装路径>\lib
拷贝完毕右键 Add as library
4.编辑module下的build.gradle
主要增加AspectJ的引用 分为两大块
// start import aspectJ
// part1
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.9.6'
classpath 'org.aspectj:aspectjweaver:1.9.6'
}
}
// end import aspectJ
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.d02aop"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
// start import aspectJ
// part 2
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
log.debug "ajc args: " + Arrays.toString(args)
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
// end import aspectJ
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation files('libs\\aspectjrt.jar')
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
下面就可以正式使用AspectJ了
1.布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:onClick="before"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="before"/>
<Button
android:onClick="after"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="after"/>
<Button
android:onClick="around"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="around"/>
</LinearLayout>
2.在MainActivity定义被AspectJ切入的方法
// 注意包的位置
package com.example.d02aop;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.example.d02aop.aop.AOPCheckNet;
import com.example.d02aop.ioc.CheckNetThenClick;
import com.example.d02aop.ioc.ViewUtils;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "hjcai";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void before(View view) {
Log.e(TAG, "before: ");
}
public void after(View view) {
Log.e(TAG, "after: ");
}
public void around(View view) {
Log.e(TAG, "around: ");
}
}
3.定义AspectJ各个切点
// 注意包的位置
package com.example.d02aop.aspectjtest;
import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* Created by hjcai on 2021/7/9.
*/
@Aspect
public class AspectJTest {
private static final String TAG = "AspectJTest";
// 1. @Before 插入点/切点 这里指 调用函数之前
// 2. execution 处理Join Point的类型,例如call、execution
// 3. * com.example.d02aop.MainActivity.before(..)
// 第一个*表示返回值,*表示返回值为任意类型
// 4. com.example.d02aop.MainActivity.before 是典型的包名路径,其中可以包含 * 来进行通配 还可以通过&&、||、!来进行条件组合
// 比如如果写成com.example.d02aop.MainActivity.* 那么MainActivity下所有的方法调用前都会调用onMethodBefore
// 注意 如果AspectJTest与目标方法同目录 可以省略包名写成 @Before("execution(* before(..))")
// (..) ()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者(..)这样来代表任意类型、任意个数的参数
// public void onMethodBefore(JoinPoint joinPoint) throws Throwable:实际切入的代码
@Before("execution(* com.example.d02aop.MainActivity.before(..))")
public void onMethodBefore(JoinPoint joinPoint) throws Throwable {
Log.d(TAG, "printLog for before");
}
@After("execution(* com.example.d02aop.MainActivity.after(..))")
public void onMethodAfter(JoinPoint joinPoint) throws Throwable {
Log.d(TAG, "printLog for after");
}
@Around("execution(* com.example.d02aop.MainActivity.around(..))")
public Object onMethodAround(ProceedingJoinPoint joinPoint) throws Throwable {
Log.d(TAG, "printLog for around");
// 1.判断有没有网络
Object object = joinPoint.getThis();// View Activity Fragment:getThis() 当前切点方法所在的类 是activity或者fragment或者view
Context context = getContext(object);// 获取上下文 用于判断网络状态
if (context != null) {
if (!isNetworkAvailable(context)) {
// 2.没有网络不要往下执行
Toast.makeText(context, "请检查您的网络", Toast.LENGTH_SHORT).show();
return null;// 阻断执行
}
}
return joinPoint.proceed();
}
/**
* 通过对象获取上下文
*/
private Context getContext(Object object) {
if (object instanceof Activity) {
return (Activity) object;
} else if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
return fragment.getActivity();
} else if (object instanceof View) {
View view = (View) object;
return view.getContext();
}
return null;
}
/**
* 检查当前网络是否可用
*/
public static boolean isNetworkAvailable(Context context) {
boolean connected = false;
// 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
StringBuilder sb = new StringBuilder();
if (connectivityManager != null) {
// 获取NetworkInfo对象
Network[] networks = connectivityManager.getAllNetworks();
//用于存放网络连接信息
for (int i = 0; i < networks.length; i++) {
//获取ConnectivityManager对象对应的NetworkInfo对象
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(networks[i]);
sb.append(networkInfo.getTypeName() + " connect is " + networkInfo.isConnected());
if (networkInfo.isConnected()) {
connected = true;
}
}
}
Log.d(TAG, "isNetworkAvailable: " + sb);
Log.d(TAG, "connected: " + connected);
return connected;
}
}
4.最终效果
// click before
2021-07-12 20:40:43.541 13631-13631/com.example.d02aop D/AspectJTest: printLog for before
2021-07-12 20:40:43.541 13631-13631/com.example.d02aop E/hjcai: before:
// click after
2021-07-12 20:40:45.140 13631-13631/com.example.d02aop E/hjcai: after:
2021-07-12 20:40:45.140 13631-13631/com.example.d02aop D/AspectJTest: printLog for after
// click around when there is no network
2021-07-12 20:40:46.765 13631-13631/com.example.d02aop D/AspectJTest: printLog for around
2021-07-12 20:40:46.766 13631-13631/com.example.d02aop D/AspectJTest: isNetworkAvailable:
2021-07-12 20:40:46.766 13631-13631/com.example.d02aop D/AspectJTest: connected: false
// click around button when network is available
2021-07-12 20:40:58.189 13631-13631/com.example.d02aop D/AspectJTest: printLog for around
2021-07-12 20:40:58.193 13631-13631/com.example.d02aop D/AspectJTest: isNetworkAvailable: MOBILE connect is true
2021-07-12 20:40:58.193 13631-13631/com.example.d02aop D/AspectJTest: connected: true
2021-07-12 20:40:58.193 13631-13631/com.example.d02aop E/hjcai: around:
可以看到Around并不是包围的意思 而是比起before 和 after around有拦截的效果
5.Aspect J大致原理
AspectJ的原理是什么呢 他为什么能在函数调用之前 或之后插入一段代码呢?原理比较简单,就是拷贝。AspectJ使用自己的编译器编译class文件时 在加入了before的AspectJ的函数前自动拷贝一段代码 在加入了after的AspectJ的函数后拷贝一段代码 Around稍微复杂一些 但是大致原理和Before类似
我们可以使用反编译来验证这一点
将上面我们的代码反编译一下得到MainActivity的部分代码:
private static final String TAG = "hjcai";
static {
ajc$preClinit();
}
public void after(View paramView) {
JoinPoint joinPoint = Factory.makeJP(ajc$tjp_4, this, this, paramView);
try {
Log.e("hjcai", "after: ");
return;
} finally {
AspectJTest.aspectOf().onMethodAfter(joinPoint);//注意这里
}
}
public void around(View paramView) {
// 这里不能直接看出around的原理 大致的原理就是在函数之前判断条件是否满足 如果满足 继续执行 否则 中断执行
JoinPoint joinPoint = Factory.makeJP(ajc$tjp_5, this, this, paramView);
around_aroundBody7$advice(this, paramView, joinPoint, AspectJTest.aspectOf(), (ProceedingJoinPoint)joinPoint);
}
public void before(View paramView) {
JoinPoint joinPoint = Factory.makeJP(ajc$tjp_3, this, this, paramView);
AspectJTest.aspectOf().onMethodBefore(joinPoint);//注意这里
Log.e("hjcai", "before: ");
}
AOP实战:利用AspectJ实现网络监听 拦截点击事件
AOPCheckNet.java :声明注解
@Target(ElementType.METHOD) // Target 放在哪个位置
@Retention(RetentionPolicy.RUNTIME)// RUNTIME 运行时 xUtils CLASS 代表编译时期 ButterKnife SOURCE 代表资源
public @interface AOPCheckNet { // @interface 注解
}
SectionAspect.java: 查找AOPCheckNet所在的方法 用于在该方法头部插入网络判断的代码
@Aspect
public class SectionAspect {
private static final String TAG = "SectionAspect";
/**
* 找到处理的切点
* * *(..) 可以处理所有的方法,不管方法有多少参数
*/
@Pointcut("execution(@com.example.d02aop.aop.AOPCheckNet * *(..))")
public void needCheckNetwork() {
}
/**
* 处理切面
*/
@Around("needCheckNetwork()")
public Object checkNet(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e(TAG, "checkNet");
// AOP用于做埋点 可以做日志上传 权限检测 网络检测等
// 1.获取 AOPCheckNet 注解所在的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AOPCheckNet checkNet = signature.getMethod().getAnnotation(AOPCheckNet.class);
if (checkNet != null) {
// 2.判断有没有网络
Object object = joinPoint.getThis();// View Activity Fragment:getThis() 当前切点方法所在的类 是activity或者fragment或者view
Context context = getContext(object);// 获取上下文 用于判断网络状态
if (context != null) {
if (!isNetworkAvailable(context)) {
// 3.没有网络不要往下执行
Toast.makeText(context, "请检查您的网络", Toast.LENGTH_SHORT).show();
return null;// 阻断执行
}
}
}
return joinPoint.proceed();
}
/**
* 通过对象获取上下文
*/
private Context getContext(Object object) {
if (object instanceof Activity) {
return (Activity) object;
} else if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
return fragment.getActivity();
} else if (object instanceof View) {
View view = (View) object;
return view.getContext();
}
return null;
}
/**
* 检查当前网络是否可用
*/
public static boolean isNetworkAvailable(Context context) {
boolean connected = false;
// 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
StringBuilder sb = new StringBuilder();
if (connectivityManager != null) {
// 获取NetworkInfo对象
Network[] networks = connectivityManager.getAllNetworks();
//用于存放网络连接信息
for (int i = 0; i < networks.length; i++) {
//获取ConnectivityManager对象对应的NetworkInfo对象
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(networks[i]);
sb.append(networkInfo.getTypeName() + " connect is " + networkInfo.isConnected());
if (networkInfo.isConnected()) {
connected = true;
}
}
}
Log.e(TAG, "isNetworkAvailable: " + sb);
Log.e(TAG, "connected: " + connected);
return connected;
}
}
使用AspectJ的注解
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:onClick="click1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AOP check network1"/>
<Button
android:onClick="click2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AOP check network2"/>
<Button
android:onClick="click3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AOP check network3"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@AOPCheckNet
public void click1(View view) {
Toast.makeText(MainActivity.this,"click1 访问网络1!",Toast.LENGTH_SHORT).show();
}
@AOPCheckNet
public void click2(View view) {
Toast.makeText(MainActivity.this,"click2 访问网络2!",Toast.LENGTH_SHORT).show();
}
@AOPCheckNet
public void click3(View view) {
Toast.makeText(MainActivity.this,"click3 访问网络3!",Toast.LENGTH_SHORT).show();
}
}
完工!