在了解AOP直線需要先了解下注釋,可以先看下我的另一篇文章
android注解的使用
AOP(Aspect Oriented Programming)是面向切面編程钮追,AOP和我們平時接觸的OOP編程是不同的編程思想柿祈,OOP是面向?qū)ο缶幊塘荒辏岢氖菍⒐δ苣K化,對象化惩系。而AOP的思想則是提倡針對同一類問題統(tǒng)一處理刻诊,當(dāng)然防楷,我們在實(shí)際編程過程中,不可能單純的AOP或者OOP的思想來編程则涯,很多時候复局,可能會混合多種編程思想。
AOP的定義
把某一方面的一些功能提取出來與一批對象進(jìn)行隔離粟判,提取之后我們就可以對某個單方面的功能進(jìn)行編程亿昏。
AOP的套路
把眾多方法中的所有共有代碼全部抽取出來,放置到某個地方集中管理档礁,然后在具體運(yùn)行時角钩,再由容器動態(tài)織入這些共有代碼的話,最起碼可以解決兩個問題:
1.1 Android程序員在編寫具體的業(yè)務(wù)邏輯處理方法時呻澜,只需關(guān)心核心的業(yè)務(wù)邏輯處理递礼,既提高了工作效率,又使代碼變更簡潔優(yōu)雅羹幸。
1.2 在日后的維護(hù)中由于業(yè)務(wù)邏輯代碼與共有代碼分開存放脊髓,而且共有代碼是集中存放的,因此使維護(hù)工作變得簡單輕松栅受。
1.3 面向切面編程AOP技術(shù)就是為解決這個問題而誕生的将硝,切面就是橫切面,代表的是一個普遍存在的共有功能屏镊,例如依疼,日志切面、權(quán)限切面及事務(wù)切面等而芥。
AOP的作用
一般來說律罢,主要用于不想侵入原有代碼的場景中,例如SDK需要無侵入的在宿主中插入一些代碼棍丐,做日志埋點(diǎn)弟翘、性能監(jiān)控、動態(tài)權(quán)限控制骄酗、甚至是代碼調(diào)試等等。
AOP的使用目前在android方面通常都是用eclipse的AspectJ
另外一個比較成功的使用AOP的庫是Jake大神的Hugo:
Jake大神的Hugo
[aspectj下載地址](http://www.eclipse.org/downloads/download.php?file=/tools/aspectj/aspectj-1.8.10.jar)
在網(wǎng)上還發(fā)現(xiàn)一個github的庫悦冀,據(jù)說竭誠了aspectj
https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
AspectJ 之 Join Points介紹
Join Points在AspectJ中是關(guān)鍵的概念趋翻。Join Points可以看做是程序運(yùn)行時的一個執(zhí)行點(diǎn),比如:一個函數(shù)的調(diào)用可以看做是個Join Points,相當(dāng)于代碼切入點(diǎn)盒蟆。但在AspectJ中踏烙,只有下面幾種執(zhí)行點(diǎn)是認(rèn)為是Join Points:
Join Points | 說明 | 實(shí)例 |
---|---|---|
method call | 函數(shù)調(diào)用 | 比如調(diào)用Log.e()师骗,這是一個個Join Point |
method execution | 函數(shù)執(zhí)行 | 比如Log.e()的執(zhí)行內(nèi)部,是一處Join Points讨惩。注意這里是函數(shù)內(nèi)部 |
constructor call | 構(gòu)造函數(shù)調(diào)用 | 和method call 類似 |
constructor execution | 構(gòu)造函數(shù)執(zhí)行 | 和method execution 類似 |
field get | 獲取某個變量 | 比如讀取DemoActivity.debug成員 |
field set | 設(shè)置某個變量 | 比如設(shè)置DemoActivity.debug成員 |
pre-initialization | Object在構(gòu)造函數(shù)中做的一些工作辟癌。 | - |
initialization | Object在構(gòu)造函數(shù)中做的工作。 | - |
static initialization | 類初始化 | 比如類的static{} |
handler | 異常處理 | 比如try catch 中荐捻,對應(yīng)catch內(nèi)的執(zhí)行 |
advice execution | 這個是AspectJ 的內(nèi)容 | - |
Pointcuts 介紹
一個程序會有多個Join Points,即使同一個函數(shù)黍少,也還分為call 和 execution 類型的Join Points,但并不是所有的Join Points 都是我們關(guān)心的处面,Pointcuts 就是提供一種使得開發(fā)者能夠值選擇所需的JoinPoints的方法厂置。
Advice介紹
Advice就是我們插入的代碼可以以何種方式插入,有Before 還有 After魂角、Around昵济。
下面看個例子:
/**
* 找到處理的切點(diǎn)
* * *(..) 可以處理所有的方法
*/
@Pointcut("execution(@com.liuy.architect_day02.CheckNet * *(..))")
public void checkNetBehavior() {
}
這里會分成好幾個部分野揪,我們依次來看:
- @Before: Advice, 也就是具體的插入點(diǎn)
- execution:處理Join Point的類型,例如call斯稳、execution
- (* android.app.Activity.on(..)): 這個是最重要的表達(dá)式,第一個表示返回值平挑,表示返回值為任意類型游添,后面這個就是典型的包名路徑,其中可以包含 *來進(jìn)行通配唆涝,幾個 *沒有區(qū)別唇辨。同時這里可以通過&&廊酣、||、赏枚!來進(jìn)行條件組合亡驰。()代表這個方法的參數(shù)饿幅,你可以指定類型凡辱,例如android.os.Bundle,或者 (..) 這樣來代表任意類型栗恩、任意個數(shù)的參數(shù)。
- public void checkNetBehavior: 實(shí)際切入的代碼乳乌。
Before 和 After 其實(shí)還是很好理解的,也就是在Pointcuts之前和之后汉操,插入代碼,那么Android呢磷瘤,從字面含義上來講,也就是在方法前后各插入代碼梭伐,他包含了 Before和 After 的全部功能仰担,代碼如下:
@(“execution(* com.example.andorid.MainActivity.testAOP()))”)
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
String key = proceedingJoinPoint.getSignature().toString();
Log.d(TAG,”onActivityMethodAroundFirst:”+key);
proceedingJoinPoint.proceed();
Log.d(TAG,”onActivityMethodAroundSecond:”+key);
}
以上代碼中,proceedingJoinPoint.proceed()代表執(zhí)行原始的方法摔蓝,在這之前、之后拌滋,都可以進(jìn)行各種邏輯處理猜谚。
自定義Pointcuts
自定義Pointcuts可以讓我們更加精準(zhǔn)的切入一個或多個指定的切入點(diǎn)。
首先我們要定義一個注解類
@Target(ElementType.METHOD) // Target 放在哪個位置
@Retention(RetentionPolicy.RUNTIME)// RUNTIME 運(yùn)行時 xUtils CLASS 代表編譯時期 ButterKnife SOURCE 代表資源
public @interface CheckNet { // @interface 注解
}
在需要插入代碼的地方加入這個注解魏铅,例如在MainActivity中加入:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@CheckNet
public void click(View view) {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
@CheckNet
public void postData() {
}
}
最后創(chuàng)建切入代碼,我這里的例子是判斷網(wǎng)絡(luò)是否可用的
@Aspect
public class SectionAspect {
/**
* 找到處理的切點(diǎn)
* * *(..) 可以處理所有的方法
*/
@Pointcut("execution(@com.darren.architect_day02.CheckNet * *(..))")
public void checkNetBehavior() {
}
/**
* 處理切面
*/
@Around("checkNetBehavior()")
public Object checkNet(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e("TAG", "checkNet");
// 做埋點(diǎn) 日志上傳 權(quán)限檢測(我寫的,RxPermission , easyPermission) 網(wǎng)絡(luò)檢測
// 網(wǎng)絡(luò)檢測
// 1.獲取 CheckNet 注解 NDK 圖片壓縮 C++ 調(diào)用Java 方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
CheckNet checkNet = signature.getMethod().getAnnotation(CheckNet.class);
if (checkNet != null) {
// 2.判斷有沒有網(wǎng)絡(luò) 怎么樣獲取 context?
Object object = joinPoint.getThis();// View Activity Fragment 览芳; getThis() 當(dāng)前切點(diǎn)方法所在的類
Context context = getContext(object);
if (context != null) {
if (!isNetworkAvailable(context)) {
// 3.沒有網(wǎng)絡(luò)不要往下執(zhí)行
Toast.makeText(context,"請檢查您的網(wǎng)絡(luò)",Toast.LENGTH_LONG).show();
return null;
}
}
}
return joinPoint.proceed();
}
/**
* 通過對象獲取上下文
*
* @param object
* @return
*/
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;
}
/**
* 檢查當(dāng)前網(wǎng)絡(luò)是否可用
*
* @return
*/
private static boolean isNetworkAvailable(Context context) {
// 獲取手機(jī)所有連接管理對象(包括對wi-fi,net等連接的管理)
ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
// 獲取NetworkInfo對象
NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
if (networkInfo != null && networkInfo.length > 0) {
for (int i = 0; i < networkInfo.length; i++) {
// 判斷當(dāng)前網(wǎng)絡(luò)狀態(tài)是否為連接狀態(tài)
if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
}
return false;
}
}
execution語法
語法結(jié)構(gòu):execution([修飾符] 返回值類型 方法名(參數(shù))⌒崩选[異常模式]) | 實(shí)例 |
---|---|
execution(public .(..)) | 所有的public方法 |
execution(* hello(..)) | 所有的hello()方法 |
execution(String hello(..)) | 所有返回值為String的hello方法沧竟。 |
execution(* hello(String)) | 所有參數(shù)為String類型的hello() |
execution(* hello(String..)) | 至少有一個參數(shù),且第一個參數(shù)類型為String的hello方法 |
execution(* com.aspect..*(..)) | 所有com.aspect包杈笔,以及子孫包下的所有方法 |
execution(* com...Dao.find*(..)) | com包下的所有一Dao結(jié)尾的類的一find開頭的方法 |
call和execution的區(qū)別
call為調(diào)用糕非,而execution為執(zhí)行敦第。
3 public class HelloWorld {
4 public static void main(int i){
5 System.out.println("in the main method i = " + i);
6 }
7
8 public static void main(String[] args) {
9 main(5);
10 }
我們攔截了參數(shù)為:int的main方法店量【铣剩 這里用到了一內(nèi)置的對象:thisJoinPoint,他表示當(dāng)前jionPoint. 跟我們在java中的this 其實(shí)是差不多的,如果你不明白蚁吝,那么你多運(yùn)行一下,好好體會一下怀伦。getSourceLocation()表示源代碼的位置:
public aspect HelloAspect {
pointcut HelloWorldPointCut() : call(* main(int));
before() : HelloWorldPointCut(){
System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
}
}
我們運(yùn)行一下HelloWorld.java山林。
打印結(jié)果為
Entering : HelloWorld.java:9
in the main method i = 5
9是行號
接下來我們把call 換成execution打印
public aspect HelloAspect {
pointcut HelloWorldPointCut() : execution(* main(int));
before() : HelloWorldPointCut(){
System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
}
}
Entering : HelloWorld.java:4
in the main method i = 5
結(jié)果為4
從上面的結(jié)果可以看出,call是調(diào)用的地方桑孩,execution是執(zhí)行的地方框冀。
thisJoinPoint.getSourceLocation() 這段代碼將會在我們以后的Demo中經(jīng)常用到。這是一個跟蹤調(diào)試的好辦法明也。
within 和 withincode
within
還是上面的例子,如果別的類中也有main方法绣硝?應(yīng)該怎么辦帆吻?你首先想到的肯定是修改pointcut,指定到我們的HelloWorld類猜煮。 這當(dāng)然是可以的淑蔚,假設(shè):現(xiàn)在還有5個類愕撰,也有main方法醋寝,也需要攔截带迟。那你這解決辦法肯定就不行了。(當(dāng)然你也可以用 || 來組合他們)仓犬。這個時候就用到了我們的within了。代碼如下
pointcut HelloWorldPointCut() : execution(* main(..)) && !within(HelloAspectDemo);
before() : HelloWorldPointCut(){
System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
withincode
withincode與within相似窘面,不過withcode()接受的signature是方法叽躯,而不是類。用法点骑,意思都差不多,只不過是使用場合不同鲸鹦。
public class MainActivity extends AppCompatActivity {
final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
register1();
register2();
register3();
}
public void registerTest() {
Log.e(TAG, "execute registerTest");
}
public void register1(){
registerTest();
}
public void register2(){
registerTest();
}
public void register3(){
registerTest();
}
}
以上三個register方法都調(diào)用了registerTest()方法跷跪,如果這個時候想調(diào)用register3的registerTest()的方法,需要如下操作
@Pointcut("(call(* *..registerTest()))&&withincode(* *..register2())")
public void invokeregisterTestInregister2() {
}
@Before("invokeregisterTestInregister2()")
public void beforeInvokeregisterTestInregister2(JoinPoint joinPoint) throws Throwable {
Log.e(TAG, "method:" + getMethodName(joinPoint).getName());
}
private MethodSignature getMethodName(JoinPoint joinPoint) {
if (joinPoint == null) return null;
return (MethodSignature) joinPoint.getSignature();
}
輸出如下
execute registerTest
method:registerTest
execute registerTest
execute registerTest