https://github.com/xiangjiana...java
build.gradle
添加 aspectJ gradle
插件} dependencise { classpath 'com.android.tools.build:gradle:3.5.0' //1_1.grade-android-plugin-aspectjx classpath 'com.hujiang.aspectjx:gradle-android-plugun-aspectjx:2.0.5' //2_1.android-maven-gradle-plugin classpath 'com.github.dcendents:android-maven-gradler-plugin:2.1'//ADD //NOTE: Do not place your application dependencise here; they belong //in the individual module build.gradle files }
build.gradle
引入 aspect類庫
build.gradle
中啓用aspectJ
插件,而且引入 permissionmodule
appmodule
是使用框架的地方上面我說到了,使用框架思想,消除了Activity,Fragment,Service,普通類 在申請權限時的差別性,能夠所有以普通類的方式來申請權限而且處理回調。因此這裏展現 Activity,Fragment,Service 的動態權限申請寫法。
public class LocationUtil { private String TAG ="LocationUtil"; @PermissionNeed( permissions ={Manifest.permission.ACCESS_FINE_LOCATION, requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION) public void getLocation() { Log.e(TAG,"申請位置權限以後,我要獲取經緯度"); } /** * 這裏寫的要特別注意,denied方法,必須是帶有一個int參數的方法,下面的也同樣 * @param requestCode */ @permissionDenied public void denied(int requestcode) { Log.e(TAG, "用戶不給阿''); } @permisssionDeniedForever public void denidFoever(int requestcode) { Log.e(TAG,''用戶永久拒絕''); } }
pubilc class MainActivity extends AppcompatActivity { private static final string TAG = ''permissionAspectTag''; @override prtected void onCreate(Bundle saveInstanceState) { super.onCreate(saveInstanceState); setcontenceView(R.layout.activity_main); findViewById(R.id.btn_location).setonclicklistener(v ->getlocationpermission() findviewById(R.id.btn_contact).setonclicklistener(v ->getcontactpermission()); } @permissionNeed( permissions = {Mainfest.permission.READ_CONTACTS,Mainfest.permission.WRITE_,Manifest.permission.GET_ACCOUNTS}, requestcode = permissionsRequestcodeconst.REQUEST_CODE_CONTACT private void getcontactpermission() { log.d(TAG,''getcontactpermission''); } @PermissionNeed( permissions ={Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION) private void getLocationPermission() { Log.d(TAG,"getLocationPermission"); } @PermissionDenied private void permissionDenied(int requestCode) { switch (requestCode) { case PermissionRequestCodeConst.REQUEST_CODE_CONTACT Log.d(TAG,"聯繫人權限被拒絕"); break; case PermissionRequestCodeConst.REQUEST_CODE_LOCATION: Log.d(TAG,"位置權限被拒絕"); break; default: break; } }
public class MyFragment extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater,@Nullable ViewGroup container,Bundle savedInstanceState) { getLocation(); return super.onCreateView(inflater,container, savedInstanceState); } private String TAG ="LocationUtil"; @PermissionNeed( permissions ={Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION) public void getLocation() { Log.e(TAG,"申請位置權限以後,我要獲取經緯度"); } /** * 這裏寫的要特別注意,denied方法,必須是帶有一個int參數的方法,下面的也同樣 * @param requestCode */ @PermissionDenied public void denied(int requestCode) { Log.e(TAG,"用戶不給啊"); } @PermissionDeniedForever public void deniedForever(int requestCode) { Log.e(TAG,"用戶永久拒絕"); } }
public class MyService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent,int flags,int startId) { getLocation(); return super.onStartCommand(intent, flags, startId); } private String TAG ="LocationUtil"; @PermissionNeed( permissions ={Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION) public void getLocation() { Log.e(TAG,"申請位置權限以後,我要獲取經緯度"); } /** *這裏寫的要特別注意,denied方法,必須是帶有一個int參數的方法,下面的也同樣 * @param requestCode */ @PermissionDenied public void denied(int requestCode) { Log.e(TAG,"用戶不給啊"); } @PermissionDeniedForever public void deniedForever(int requestCode) { Log.e(TAG,"用戶永久拒絕"); } }
通過觀察,Activity,Fragment,Service,和普通類,都是定義了一個或者多個被@PermissionNeed
註解修飾的方法, 若是是多個,還要在@PermissionDenied
和@PermissionDeniedForever
修飾的方法 中switch處理requestCode
(參考上方Activity),以應對申請屢次申請不一樣權限的結果 。
也許除了這4個地方以外,還有別的地方須要申請動態權限,可是既然咱們消除了差別性,就能夠所有以普通類的方式來申請權限以及處理回調。這才叫從根本上解決問題。
這裏有個坑: 被 @PermissionDenied
和 @PermissionDeniedForever
修飾的方法,必須有且僅有一個int類型參數, 返回值隨意.android
zpermission
module這裏包含了框架的核心代碼,如今一步一步講解c++
類結構圖
3個註解@PermissionDenied
@PermissionDeniedForever
@PermissionNeed
git
/** * 被此註解修飾的方法,會在方法執行以前去申請相應的權限,只有用戶授予權限,被修飾的方法體纔會執行 */ @Target(ElementType.METHOD)//此註解用於修飾方法 @Retention(RetentionPolicy.RUNTIME)//註解保留到運行時,由於可能須要反射執行方法(上面說了修飾的是方法!) public @interface PermissionNeed { String[] permissions();//須要申請的權限,支持多個,須要傳入String數組 int requestCode()default 0;//這次申請權限以後的返回碼 }
/** * 被此註解修飾的方法,會在權限申請失敗時被調用 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PermissionDenied { }
/** * 被此註解修飾的方法,會在用戶永久禁止權限以後被調用 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PermissionDeniedForever { }
處理權限回調結果的接口 IPermissionCallback
github
/** * 權限申請結果接口 */ public interface IPermissionCallback { /** * 授予權限 */ void granted(int requestCode); /** * 此次拒絕,可是並無勾選"之後再也不提示" */ void denied(int requestCode); /** * 勾選"之後再也不提示",而且拒絕 */ void deniedForever(int requestCode); }
以上都是事先要預備好的東西,接下來進入核心
PermissionAspect
類面試
@Aspect public class permissinbAspect { private static final String TAG = "PermissionAspectTag"; private final String pointcutExpression="execution(@com.zhou.zpermission.annotation.PermissionNeed * *(..)) && @annotation(permissionNeed)"; @Pointcut(value = pointcutExpression) public void requestPermission(PermissionNeed permissionNeed) { Log.d(TAG,"pointCut 定義切入點"); } @Around("requestPermission(permissionNeed)") Log.d(TAG,"pointCut 定義切入點"); .... }
此段代碼解讀以下:編程
@Aspect
註解來修飾類 , @Aspect
是來自 AspectJ
框架的註解,被它修飾的類,在編譯時會被認爲是一個切面類@Pointcut
註解來修飾方法 requestPermission()
,被它修飾的方法會被認爲是一個切入點.所謂切入點,就是 面向切面編程時,咱們無侵入式地插入新的邏輯,總要找到一個確切的位置,咱們要知道程序執行到哪一行的時候,輪到咱們出場了!切入點,必定是方法, 不能是隨意哪一段代碼!切入點能夠是如下類型,不一樣的類型有不一樣的語法,我目前使用的是 method execution ,也就是 函數執行時。這意味着,當切入點的方法即將開始執行的時候,咱們插入的邏輯將會被執行。與之相似的有一個 method call ,這個不同,這個是在切入點的方法 被調用時,也就是說,當偵測到該方法被外界調用的時候,而非方法本身執行。這二者有細微差異。至於其餘的類型,暫且按下不詳述。
除了類型以外,這裏還有一個重點,那就是 MethodSignature的概念,這個相似於 jni裏的方法簽名,是爲了標記一個或者一類方法, AspectJ框架經過這個方法簽名,來肯定 JVM的全部class對象中,有哪些方法須要被插入 新的邏輯。
具體的簽名的語法規則爲:
看不懂? 看不懂就對了,舉個例子:execution(@com.zhou.zpermission.annotation.PermissionNeed**(..))&&@annotation(permissionNeed)
數組
這是Demo中我這麼寫的,如今逐步解析:服務器
execution
表示方法執行時做爲切入點@com.zhou.zpermission.annotation.PermissionNeed
表示 切入點的方法必須有這個註解修飾**(..))
這個比較詭異,咱們知道,一個方法寫完整一點多是這個樣子private void test(int a)
可是若是咱們不計較 訪問權限,不計較返回值類型,也不計較 函數名,甚至不計較參數列表的話,就能夠寫成這個樣子 **(..)) . 表示任意方法app
除此以外,還有後半截 &&@annotation(permission)
,它的含義爲:
切入點方法須要接收來自 註解的參數。
即 切入點@Pointcut
規定切入點的時候,只識別被@com.zhou.zpermission.annotation.PermissionNeed
標記的方法,可是這個@com.zhou.zpermission.annotation.PermissionNeed
註解,是有本身的參數值的,因此,必須傳入這個值給到切入方法requestPermission(PermissionNeedpermissionNeed)
去使用。
有點繞!一張圖說清楚:
圖中3個字符串必須一摸同樣,否則編譯就會報錯,並且報錯緣由還不明確。
使用 @Around 註解來修飾 方法 doPermission()
,被它修飾的方法會被認爲是一個 切入策略。
Around註解的參數爲:"requestPermission(permissionNeed)"
, 也就是 pointcut
修飾的方法名(形參名)
在咱們已經定義好切入點
requestPermission(PermissionNeedpermissionNeed)
的前提下,若是程序已經執行到了切入點,那麼我是選擇怎麼樣的策略, 目前所選擇的策略是 Around ,也就是,徹底替代切入點的方法,可是依然保留了 執行原方法邏輯的可能性 joinPoint.proceed();
除了 @Around
策略以外,還有如下:PermissionAspect
類的做用是: 定義切入點和切入策略,那麼如今咱們肯定切入點是 被註解 @PermissionNeed
修飾的方法,切入策略是 @Around,那麼,切入以後咱們作了哪些事呢?
接下往下看...
PermissionAspectActivity
類
public class permissionAspectActivity extends AppcompatActivity { private final static String permissionsTag = "permissions"; private final static String requestCodeTag = "requestCode"; private static IPermissionCallback mCallback; /** * 啓動當前這個Activity */ public static void startActivity(Context context, String[] permissions,int requestCode,IPermissionCallback callback) { Log.d("PermissionAspectTag","context is : "+ context.getClass().getSimpleName()); if (context == null) return; mCallback = callback; //啓動當前這個Activiyt而且取消切換動畫 Intent intent = new Intent(context,PermissionAspectActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |Intent.FLAG_ACTIVITY_CLEAR_TOP); //開啓新的任務棧而且清除棧頂...爲什麼要清除棧頂 intent.putExtra(permissionsTag, permissions); intent.putExtra(requestCodeTag, requestCode); context.startActivity(intent); //利用context啓動activity if (context instanceof Activity) { //而且,若是是activity啓動的,那麼還要屏蔽掉activity切換動畫 ((Activity) context).overridePendingTransition(0, 0); } } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); String[] permissions = intent.getStringArrayExtra (permissionsTag); int requestCode = intent.getIntExtra(requestCodeTag,0); if (PermissionUtil.hasSelfPermissions(this, permissions)) { mCallback.granted(requestCode); finish(); overridePendingTransition(0,0); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(permissions, requestCode); } } @Override public void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions, @NonNull int[] grantResults) { //如今拿到了權限的申請結果,那麼如何處理,我這個Activity只是爲了申請,而後把結果告訴外界,因此結果的處理只能是外界傳進來 boolean granted = PermissionUtil.verifyPermissions(grantResults); if (granted) { //若是用戶給了權限 mCallback.granted(requestCode); } else { if (PermissionUtil.shouldShowRequestPermissionRationale(this ,permissions)) { mCallback.denied(requestCode); } else { mCallback.deniedForever(requestCode); } } finish(); overridePendingTransition(0,0); } }
解讀:
1.提供一個靜態方法
publicstaticvoidstartActivity(Contextcontext,String[]permissions,intrequestCode,IPermissionCallbackcallback),
用於啓動本身PermissionAspectActivity,
接收的參數分別爲:context
,須要的權限數組,權限返回碼,權限結果回調接口
onCreate
方法中,檢查是否已經有想要申請的權限,若是有,直接調用mCallback.granted(requestCode);
而且結束自身,而且要注意隱藏Activity的切換動畫。若是沒有,那麼,就去requestPermissions(permissions,requestCode);
申請權限。- 處理權限申請的回調,而且分狀況調用
mCallback
的回調方法,而後結束自身
須要注意:PermissionAspectActivity
必須在module的清單文件中註冊
而且 要定義它的 theme使得Activity徹底透明
Gif圖效果演示:
所謂AOP
(ApsectOrientedProgramming
) 面向切面編程。此概念是基於
OOP
(ObjectOrientiedProgramming
)面向對象編程。在OOP
中,咱們能夠把不一樣的業務功能都分紅一個一個的模塊,而後每個模塊有本身的專注職責,從而優化編程過程,下降編程犯錯概率。可是隨着OOP類的數量的增長,咱們會發現,在某一些業務類中,常常有一些相同的代碼在重複編寫,可是迫不得已,好比日誌打印/動態權限申請/埋點數據上報/用戶登陸狀態檢查 /服務器端口連通性檢查 等等。這些代碼,咱們雖然能夠他們抽離出來整理到一個個專注的模塊中,可是調用的時候,仍是處處分散的,而且這些調用還入侵了原本不直接相關的業務代碼,讓咱們閱讀業務代碼十分費勁。而AOP的出現,就是基於OOP的這種缺陷而出現的優化方案。利用AOP,咱們能夠對業務邏輯的各個部分進行隔離,使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,提升開發效率,減小犯錯機率。
畫圖表示:
如上圖,OOP中,一樣的一段過程,咱們把登陸檢查,權限檢查,埋點上報的調用代碼寫了3遍,然而都是雷同代碼,只是參數不一樣而已。而,換成AOP的思想來編碼。則是以下:
所採起的方案爲:
在class A , B, C中 找到切入點,而後在切入點插入共同的邏輯,而不是屢次編寫雷同的代碼。
本文的Demo中,插入相同的邏輯,使用的是 Java自定義註解+@Aspect切面類+@PointCut切入點+@Around切入策略 的方式。這只是AOP方案的一種,叫作 AspectJ
。
除此以外,Android開發中經常使用的AOP方案還有:
(Java註解存在3個階段,一個是源碼期,一個是編譯期,一個運行期)
Java的註解解析技術(AnnotationProcessingTool), Apt的做用時期,是 經過 自定義註解解析類(extends AbastractProcessor),對自定義註解進行解析,而後經過JavaPoet這種java類生成工具,來生成編譯期纔會有的.java(源碼中並無),然而咱們源碼中卻可使用這個類。
Asm是Java的字節碼操做框架,它能夠動態生成類或者加強既有類的功能。理論上,它能夠對class文件作任何他想作的事。包括,改變class文件的內容,或者生成新的class。嚴格來講AspectJ
底層就是ASM
,只不過AspectJ
幫咱們作了ASM
框架作起來很麻煩,容易出錯的事情,讓咱們能夠簡單的經過 @Aspect
@PointCut
@Around
這樣的註解,就能完成AOP
面向切面編程。可是,ASM
做爲AspectJ
的祖宗,某些時候依然能夠完成AspectJ
所沒法觸及到的功能, 就像是c/c++做爲Java的祖宗, 如今依然有本身不可替代的做用。
原本想寫成一篇,可是發現篇幅太長,留個尾巴,下一篇,解析AspectJ是如何經過@註解的方式來插入邏輯的。
文章太長了,順手留下GitHub連接,須要獲取相關內容的能夠本身去找
https://github.com/xiangjiana/Android-MS