Android AOP:aspectjx

加入引用

在整個項目的 build.gradle 中,添加?

classpath "com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10"

?可以看到測試demo的 gradle 版本是很低的。

基于 github 上的文檔,可以看到原版只支持到 gradle 4.4 。后續需要使用社區版的 aspectjx

GitHub - HujiangTechnology/gradle_plugin_android_aspectjx: A Android gradle plugin that effects AspectJ on Android project and can hook methods in Kotlin, aar and jar file.

然后在App 目錄下的 build.gradle?中加入plugin 標記即可。

apply plugin: 'android-aspectjx'

還可以指定需要生效的位置,塊放在最后即可。

include 生效的包名

exclude 排除的包名


使用

基于 @Aspect 注解,LogAspect? 不需要在任何地方調用,自動會織入。

package com.aaaa.testplayer;import android.util.Log;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;/*** 切入點學習:* 所有類切入點:  (* com.aaaa.testplayer..*(..))* 1. execution(...):* 這是一個切點表達式,用于匹配方法執行的連接點。* <p>* 2. * (返回值類型):* 這個星號表示“任意返回類型”。也就是說,這個切點將匹配任何返回類型的方法,無論是 void、int、String 等。* <p>* 3. com.aaaa.testplayer..* (聲明類型模式):* com.aaaa.testplayer: 這是你的包名。* ..: 這個符號表示“零個或多個子包”。它允許在 com.aaaa.testplayer 包及其所有子包中匹配類。例如,它會匹配:* com.aaaa.testplayer.MyClass* com.aaaa.testplayer.subpackage.AnotherClass* *: 這個星號表示“任意類名”。它允許匹配該包下的所有類。* <p>* 4. ( .. ) (參數模式):* .. 代表“零個或多個參數”。這意味著該切點會匹配任何數量的參數,包括沒有參數的情況。比如:* myMethod()* myMethod(int a)* myMethod(String a, int b, double c)* <p>* <p>* 另一種實現:** @Pointcut("execution(* com.aaaa.testplayer.*.*(..))")* 匹配的是 com.example.service 包下的所有類和方法。* *.*(..) 的意思是匹配該包下任何類中的任何方法,不論返回類型、方法名稱或參數類型。*//**** JoinPoint :* 1. 獲取上下文信息:通過 JoinPoint,你可以獲取關于當前連接點的信息,比如方法名、類名、參數等等。* 2. 不能控制流程:JoinPoint 只能用來查看信息,而不能控制方法的執行。** ProceedingJoinPoint:* 1. 控制方法執行:你可以使用 proceed() 方法來繼續執行原始方法,或者選擇不執行它。* 2. 獲取上下文信息:像 JoinPoint 一樣,ProceedingJoinPoint 也可以訪問連接點的信息,但它還額外提供了一些可以控制的功能。*** JoinPoint 的使用,具體可以參考:beforeAdvice** ProceedingJoinPoint主要多了 proceed()方法,配合 @Around 環繞,可以控制方法是否執行** *//*** 無法動態修改已編譯的系統類!!!** 關于 throws Throwable 。可以使用 try/catch 組合來代替,主要取決于是否要向上傳遞 error* *//*** @Before: 在目標方法執行之前。* @After: 在目標方法執行之后,無論結果如何。* @AfterReturning: 在目標方法成功返回之后。* @AfterThrowing: 在目標方法拋出異常之后。* @Around: 可自定義目標方法的執行過程,包括前置和后置操作。* */
@Aspect
public class LogAspect {/*** @Before 在指定位置之前進行觸發* ************************************************************************/// 在指定的 class 方法運行前,進行log輸出
//    @Before("execution(* com.aaaa.testplayer.MainActivity.onCreate(..))")
//    public void beforeOnCreate() {
//        Log.e("aaaaa","MainActivity onCreate called");
//    }/*** 切入點學習:*  1. * android.util.Log.d(String, String):*      * 表示“任意返回類型”,在這里它表示 Log.d 方法可以是任何返回類型(由于 Log.d 返回 int 作為日志行 ID,但我們通常不關心返回值)。**  2. android.util.Log.d: 這是我們要匹配的具體方法,即 Android 的 Log 類的 d 方法,表示 "debug" 日志。*      (String, String): 表示該方法接受兩個 String 類型的參數。也就是說,它只會匹配傳遞兩個 String 參數的 Log.d 方法調用。**  3.&& args(tag, msg):*      && 是邏輯與操作符,表示該切點表達式的兩個部分必須同時滿足。*      args(tag, msg): 這個部分用于提取通過方法參數傳入的數據。tag 和 msg 是這兩個參數的名稱,可以在通知方法中使用。這允許你在切面中引用這些參數,使得你能夠對子日志信息進行處理或記錄。**   如果 不加 && args(tag, msg),那么下面就無法獲取整個參數!!!* */// 在指定的方法運行前,進行log輸出
//    @Before("call(* android.util.Log.d(String, String)) && args(tag, msg)")
//    public void replaceLog(String tag, String msg) {
//        // 將所有 Log.d 前增加為 Log.e
//        Log.e(tag, "[Replaced] " + msg);
//    }// 在所有方法執行前被調用,并打印出正在執行的方法名稱。
//    @Before("execution(* com.aaaa.testplayer..*(..))")
//    public void beforeAdvice(JoinPoint joinPoint) {
//        // 獲取方法名
//        String methodName = joinPoint.getSignature().getName();
//        // 可以用來獲得當前執行的方法所在的類的全名
//        String methodDeclaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
//        // 用于在運行時獲取方法的聲明類對象,從而可以調用該類的方法、訪問其字段等。
//        Class<?> declaringType = joinPoint.getSignature().getDeclaringType();
//        // 注意這里因為劫持了所有方法所以要檢查 methodName, 不然會死循環
//        if (declaringType == MainActivity.class && methodName == "onCreate") {
//            MainActivity mainActivity = (MainActivity) joinPoint.getTarget(); // 獲取目標對象
//            try {
//                // 調用 showMsg 方法
//                mainActivity.showMsg("This is a message from the aspect!");
//            } catch (Exception e) {
//                Log.e("AspectError", "Error calling showMsg method", e);
//            }
//        }
//
//        // 獲取目標對象
//        Object targetObject = joinPoint.getTarget();
//
//        // 獲取代理對象
//        Object proxyObject = joinPoint.getThis();
//
//        // 獲取參數
//        // 可以獲取到入參,例如在 public void onTest1(View view) 中可以獲取到入參  android.widget.Button{f2aba96 VFED..C.. .F....ID 0,0-300,90}
//        Object[] args = joinPoint.getArgs();
//
//        // 打印信息到 Logcat
//        Log.e("aaaaa", "Method name: " + methodName);
//        Log.e("aaaaa", "methodDeclaringTypeName name: " + methodDeclaringTypeName);
//        Log.e("aaaaa", "目標對象 Target object: " + targetObject.getClass().getSimpleName());
//        Log.e("aaaaa", "代理對象 Proxy object: " + proxyObject.getClass().getSimpleName());
//        Log.e("aaaaa", "Arguments: " + Arrays.toString(args));
//        if (args.length > 0) {
//            for (Object arg : args) {
//                if (arg == null){
//                    continue;
//                }
//                Log.e("Aspect", "Argument type: " + arg.getClass().getSimpleName() + ", value: " + arg);
//            }
//        }
//
//        // 連接點描述
//        Log.e("aaaaa", ("JoinPoint description: " + joinPoint.toString()));
//
//        // 獲取源位置(文件名和行號)
//        SourceLocation location = joinPoint.getSourceLocation();
//        Log.e("aaaaa", "Source location: " + location.getFileName() + ":" + location.getLine());
//    }/*** @Around 在運行中觸發* ************************************************************************//*** 1. @Around 通知* 允許你完全控制目標方法的執行,包括是否調用原方法(通過 joinPoint.proceed())。** 2.不調用 proceed()* 這里我們直接返回 Log.e 的結果,不執行 joinPoint.proceed(),從而完全跳過原始 Log.d 的執行。** 3.返回值處理* Log.d 和 Log.e 都返回 int(日志的優先級/類型),因此直接返回 Log.e 的返回值是類型安全的。如果調用代碼依賴返回值,也能保持一致。** 4.參數注入* 通過 args(tag, msg) 將原始參數注入方法,你可以在新邏輯中復用或修改它們。** @Around 在性能上與 @Before 差異不大,但避免了冗余的原方法調用。* */// 將全局的log.d 調用替換為 Log.e@Around("call(* android.util.Log.d(String, String)) && args(tag, msg)")public Object replaceLog(ProceedingJoinPoint joinPoint, String tag, String msg) throws Throwable {// 方法執行前的邏輯
//        System.out.println("Before method: " + joinPoint.getSignature().getName());// 這里決定了是否要調用原有的參數。如果調用這句,約等于前面的 before ,不過會先打出log.d
//        joinPoint.proceed();// 方法執行后的邏輯
//        System.out.println("After method: " + joinPoint.getSignature().getName());// 直接調用 Log.e 替換 Log.d,并阻止原方法執行return Log.e(tag, "[Replaced] " + msg);}/*** @After* 目標方法執行后執行,無論該方法是否拋出異常。能夠用于執行一些清理工作、記錄日志等* 無法直接獲取方法的返回值,沒有 JoinPoint* ************************************************************************************ */// Log 方法調用后輸出日志。 這里用 println 避免死循環
//    @After("call(* android.util.Log.*(String, String))")
//    public void afterAdvice() {
//        System.out.println("Log method execution");
//    }/*** @AfterReturning* 在目標方法成功返回后執行,可以獲取返回值。適合用于對成功結果的處理。* ************************************************************************************ */// 指定方法運行完成后獲取返回值
//    @AfterReturning(pointcut = "execution(* com.aaaa.testplayer.MainActivity.getMsg())", returning = "result")
//    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
//        Log.e("aaaaa","AfterReturning Method executed: " + joinPoint.getSignature().getName());
//        Log.e("aaaaa","AfterReturning Returned value: " + result);
//
//        // 獲取方法參數
//        Object[] args = joinPoint.getArgs();
//        System.out.println("MAfterReturning ethod arguments: ");
//        for (Object arg : args) {
//            Log.e("aaaaa","AfterReturning Argument: " + arg);
//        }
//    }/*** @AfterThrowing* 當目標方法拋出異常時執行。可以用來進行異常處理或日志記錄。* ************************************************************************************ */// 定義切點,匹配所有拋出異常的方法
//    @AfterThrowing(pointcut = "execution(* com.aaaa.testplayer..*(..))", throwing = "ex")
//    public void logAfterThrowing(Exception ex) {
//        Log.e("aaaaa", "發現exception !!! " + ex.getMessage());
//        // 這里可以添加其他處理邏輯,比如發送通知、記錄到數據庫等
//    }
}

反編譯后可以看到對應的位置前新增了代碼?

多個切點的處理:

可以預先定義 Pointcut ,也可以對定義的 Pointcut 進行復用。


關于第三方庫?

aspectjx 可以對引入的三方庫也生效,但是對已經編譯好的 Android 系統類(例如 TextView)無法進行動態修改。這里使用三方庫?autosize 來示例。

因為?autosize 在部分設備上顯示有問題,需要調試。autosize 庫默認對日志進行 debug 級別輸出,但是在某些設備上,系統只輸出 info 級別及以上的 log,導致?autosize 日志全無,這里借助工具將?me.jessyan.autosize.utils.AutoSizeLog 中的日志級別強制提到 error 級。

首先需要引入三方庫:

然后在 aspectx 中記得加入對應的包名,不然織入不會生效。

編寫替換代碼,這里將全局的 Log.d 都替換為 Log.e 了。這個代碼上面有示例

運行后即可看到日志都被提升到 error 

反編譯apk查看 ,可以看到 debug 級別輸出的代碼已經被替換。 warn 和 err 不受影響


關于高 gradle 版本?

根據github 官方文檔,gradle 支持只到 4.4 。那么高 gradle 需要額外處理:

強制使用 gradle 7.1.3

最小的改動就是將 gradl 版本指定為 7.1.3 .之前我用的 7.2.2 不行會報

Failed to apply plugin ‘android-aspectjx’. No such property: FD_INTERMEDIATES for class: com.android.builder.model.AndroidProject

gradle7.2及以上可使用wurensen重構過的版本來實現AOP操作。

參考:GitHub - LZ9/AspectjxDemo: 簡單的Aspectjx接入demo,兼容gradle7以上版本?

classpath 'io.github.wurensen:gradle-android-plugin-aspectjx:3.3.2'?

?

apply plugin: 'io.github.wurensen.android-aspectjx'?

aspectjx {enabled trueinclude 'com.aaaa.testplayer'exclude 'androidx', 'com.google', 'com.squareup', 'com.alipay', 'com.taobao', 'org.apache','kotlinx', 'org.jetbrains', "module-info", 'versions.9'
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/67176.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/67176.shtml
英文地址,請注明出處:http://en.pswp.cn/web/67176.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第84期 | GPTSecurity周報

GPTSecurity是一個涵蓋了前沿學術研究和實踐經驗分享的社區&#xff0c;集成了生成預訓練Transformer&#xff08;GPT&#xff09;、人工智能生成內容&#xff08;AIGC&#xff09;以及大語言模型&#xff08;LLM&#xff09;等安全領域應用的知識。在這里&#xff0c;您可以找…

TCP/IP 協議:互聯網通信的基石

TCP/IP 協議:互聯網通信的基石 引言 TCP/IP協議,全稱為傳輸控制協議/互聯網協議,是互聯網上應用最為廣泛的通信協議。它定義了數據如何在網絡上傳輸,是構建現代互聯網的基礎。本文將深入探討TCP/IP協議的原理、結構、應用以及其在互聯網通信中的重要性。 TCP/IP 協議概述…

蛇年特別版貪吃蛇H5小游戲

該作者的原創文章目錄: 生產制造執行MES系統的需求設計和實現 企業后勤管理系統的需求設計和實現 行政辦公管理系統的需求設計和實現 人力資源管理HR系統的需求設計和實現 企業財務管理系統的需求設計和實現 董事會辦公管理系統的需求設計和實現 公司組織架構圖設計工具 庫存管…

MapReduce,Yarn,Spark理解與執行流程

MapReduce的API理解 Mapper 如果是單詞計數&#xff1a;hello&#xff1a;1&#xff0c; hello&#xff1a;1&#xff0c; world&#xff1a;1 public void map(Object key, // 首字符偏移量Text value, // 文件的一行內容Context context) // Mapper端的上下文&#xff0c;…

如何將xps文件轉換為txt文件?xps轉為pdf,pdf轉為txt,提取pdf表格并轉為txt

文章目錄 xps轉txt方法一方法二 pdf轉txt整頁轉txt提取pdf表格&#xff0c;并轉為txt 總結另外參考XPS文件轉換為TXT文件XPS文件轉換為PDF文件PDF文件轉換為TXT文件提取PDF表格并轉為TXT示例代碼&#xff08;部分&#xff09; 本文測試代碼已上傳&#xff0c;路徑如下&#xff…

Day26-【13003】短文,什么是順序表?順序表和數組、內存地址的關系?順序表的插入、刪除操作如何實現?操作的時間復雜度是多少?

文章目錄 第二節&#xff0c;線性表的順序存儲及實現概覽什么是順序表和鏈表&#xff1f;順序存儲的叫順序表順序表和數組還有內存地址的關系&#xff1f;順序表的基本操作如何實現&#xff1f;1、插入操作如何實現&#xff1f;2、刪除操作如何實現&#xff1f;3、賦值和查找操…

【含開題報告+文檔+PPT+源碼】基于SpringBoot的校園跑腿管理系統

開題報告 本文旨在探討校園跑腿系統的設計與實現&#xff0c;通過深入研究與分析&#xff0c;實現了一套包含用戶管理、發布跑腿單、跑腿搶單、跑腿單評論、在線留言以及用戶在線充值等功能的綜合性系統。該系統以提高校園內物品跑腿與配送效率為核心目標&#xff0c;為廣大學…

zookeeper的介紹和簡單使用

1 zookerper介紹 zookeeper是一個開源的分布式協調服務&#xff0c;由Apache軟件基金會提供&#xff0c;主要用于解決分布式應用中的數據管理、狀態同步和集群協調等問題。通過提供一個高性能、高可用的協調服務&#xff0c;幫助構建可靠的分布式系統。 Zookeeper的特點和功能…

二級 二維數組3

對角線之和 題目描述 輸入一個矩陣&#xff0c;輸出右上-左下對角線上的數字和 輸入 輸入1個整數N。(N<10)表示矩陣有n行n列 輸出 對角線的和 樣例 輸入復制 4 1 2 3 4 2 3 4 5 4 5 6 7 1 2 3 4 輸出復制 14 #include<iostream> using namespace std; int main() {i…

Spring Boot MyBatis Plus 版本兼容問題(記錄)

Spring Boot & MyBatis Plus 版本兼容問題&#xff08;Invalid value type for attribute factoryBeanObjectType: java.lang.String&#xff09; 問題描述問題排查1. 檢查 MapperScan 的路徑2. 項目中沒有配置 FactoryBean3. 檢查 Spring 和 MyBatis Plus 版本兼容性 解決…

嵌入式學習筆記-雜七雜八

文章目錄 連續波光纖耦合激光器工作原理主要特點應用領域設計考慮因素 數值孔徑&#xff08;Numerical Aperture&#xff0c;簡稱NA&#xff09;數值孔徑的定義數值孔徑的意義數值孔徑的計算示例數值孔徑與光纖 四象限探測器檢測目標方法四象限劃分檢測目標的步驟1. 數據采集2.…

Java Web-Cookie與Session

會話跟蹤技術 會話跟蹤技術是一種在 Web 應用程序中跟蹤用戶會話狀態的機制&#xff0c;它允許服務器在多個請求之間識別和關聯屬于同一用戶的請求&#xff0c;以便在整個會話過程中保持用戶相關的信息。以下是幾種常見的會話跟蹤技術&#xff1a; Cookie 概念&#xff1a;Cook…

Spring Boot - 數據庫集成04 - 集成Redis

Spring boot集成Redis 文章目錄 Spring boot集成Redis一&#xff1a;redis基本集成1&#xff1a;RedisTemplate Jedis1.1&#xff1a;RedisTemplate1.2&#xff1a;實現案例1.2.1&#xff1a;依賴引入和屬性配置1.2.2&#xff1a;redisConfig配置1.2.3&#xff1a;基礎使用 2&…

STM32使用VScode開發

文章目錄 Makefile形式創建項目新建stm項目下載stm32cubemx新建項目IED makefile保存到本地arm gcc是編譯的工具鏈G++配置編譯Cmake +vscode +MSYS2方式bilibiliMSYS2 統一環境配置mingw32-make -> makewindows環境變量Cmake CmakeListnijia 編譯輸出elfCMAKE_GENERATOR查詢…

Oracle 12c 中的 CDB和PDB的啟動和關閉

一、簡介 Oracle 12c引入了多租戶架構&#xff0c;允許一個容器數據庫&#xff08;Container Database, CDB&#xff09;托管多個獨立的可插拔數據庫&#xff08;Pluggable Database, PDB&#xff09;。本文檔旨在詳細描述如何啟動和關閉CDB及PDB。 二、容器數據庫 (CDB) 2.1…

網絡仿真工具Core環境搭建

目錄 安裝依賴包 源碼下載 Core安裝 FAQ 下載源碼TLS出錯誤 問題 解決方案 找不到dbus-launch 問題 解決方案 安裝依賴包 調用以下命令安裝依賴包 apt-get install -y ca-certificates git sudo wget tzdata libpcap-dev libpcre3-dev \ libprotobuf-dev libxml2-de…

FPGA實現任意角度視頻旋轉(二)視頻90度/270度無裁剪旋轉

本文主要介紹如何基于FPGA實現視頻的90度/270度無裁剪旋轉&#xff0c;旋轉效果示意圖如下&#xff1a; 為了實時對比旋轉效果&#xff0c;采用分屏顯示進行處理&#xff0c;左邊代表旋轉前的視頻在屏幕中的位置&#xff0c;右邊代表旋轉后的視頻在屏幕中的位置。 分屏顯示的…

JavaEE:多線程進階

JavaEE&#xff1a;多線程進階 一、對比不同鎖策略之間的應用場景及其區別1. 悲觀鎖 和 樂觀鎖1.1 定義和原理1.2 應用場景1.3 示例代碼 2. 重量級鎖 和 輕量級鎖2.1 定義和原理2.2 應用場景2.3 示例代碼 3. 掛起等待鎖 和 自旋鎖3.1 定義和原理3.2 應用場景3.3 示例代碼 4. 幾…

董事會辦公管理系統的需求設計和實現

該作者的原創文章目錄&#xff1a; 生產制造執行MES系統的需求設計和實現 企業后勤管理系統的需求設計和實現 行政辦公管理系統的需求設計和實現 人力資源管理HR系統的需求設計和實現 企業財務管理系統的需求設計和實現 董事會辦公管理系統的需求設計和實現 公司組織架構…

pytest自動化測試 - pytest夾具的基本概念

<< 返回目錄 1 pytest自動化測試 - pytest夾具的基本概念 夾具可以為測試用例提供資源(測試數據)、執行預置條件、執行后置條件&#xff0c;夾具可以是函數、類或模塊&#xff0c;使用pytest.fixture裝飾器進行標記。 1.1 夾具的作用范圍 夾具的作用范圍&#xff1a; …