您好,登錄后才能下訂單哦!
https://github.com/xiangjiana/Android-MS
在project的 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,''用戶永久拒絕''); } }
Activity
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(
br/>}
@permissionNeed(
CONTACTS,Mainfest.permission.WRITE,Manifest.permission.GET_ACCOUNTS},
requestcode = permissionsRequestcodeconst.REQUEST_CODE_CONTACT
private void getcontactpermission() {log.d(TAG,''getcontactpermission'');
}
@PermissionNeed(
br/>log.d(TAG,''getcontactpermission'');
}
@PermissionNeed(
requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION)
private void getLocationPermission() {
Log.d(TAG,"getLocationPermission");
}@PermissionDenied
br/>@PermissionDenied
switch (requestCode) {
case PermissionRequestCodeConst.REQUEST_CODE_CONTACT
Log.d(TAG,"聯系人權限被拒絕");
break;
case PermissionRequestCodeConst.REQUEST_CODE_LOCATION:
Log.d(TAG,"位置權限被拒絕");
break;
default:
break;
}
}
##### Fragment
public class MyFragment extends Fragment {@Nullable
br/>@Nullable
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,"申請位置權限之后,我要獲取經緯度");
}
/**
}
##### Service
public class MyService extends Service {@Nullable
br/>@Nullable
public IBinder onBind(Intent intent) {
return null;}
@Override
br/>}
@Override
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參數的方法,下面的也一樣
}
> 經過觀察,Activity,Fragment,Service,和普通類,都是定義了一個或者多個被 `@PermissionNeed`注解修飾的方法, 如果是多個,還要在 `@PermissionDenied`和 `@PermissionDeniedForever`修飾的方法 中switch處理 `requestCode`(參考上方Activity),以應對申請多次申請不同權限的結果 。
也許除了這4個地方之外,還有別的地方需要申請動態權限,但是既然我們消除了差異性,就可以全部以普通類的方式來申請權限以及處理回調。這才叫從根本上解決問題。
這里有個坑: 被 @PermissionDenied
和 @PermissionDeniedForever
修飾的方法,必須有且僅有一個int類型參數, 返回值隨意.
zpermission
module類結構圖
3個注解@PermissionDenied
@PermissionDeniedForever
@PermissionNeed
/**
* 被此注解修飾的方法,會在方法執行之前去申請相應的權限,只有用戶授予權限,被修飾的方法體才會執行
*/
@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
/**
* 權限申請結果接口
*/
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)
但是如果我們不計較 訪問權限,不計較返回值類型,也不計較 函數名,甚至不計較參數列表的話,就可以寫成這個樣子 **(..)) . 表示任意方法
除此之外,還有后半截 &&@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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。