上一個大的系列文章叫 "手把手講解", 歷時10個月,出產博文二十餘篇,講解細緻,幾乎每一篇都提供了詳實的原理講解,提供了可運行 githubDemo,並且針對Demo中的關鍵地地方進行了重點拆解。相信每一位詳細閱讀文章的同行都會有所收穫。但是,講解雖詳細,但是缺乏對於技術的深度的挖掘。
從今天開始開闢新的專題: 移動架構師專業技能深入淺出,以一步步成為架構師為目標,詳述一項架構師技能的最直接使用價值,橫向周邊知識以及縱深專業技術.
最直接使用價值: 網上最怕看到一種文章,全文開篇高大上,讓人覺得遙不可及,通篇看下來卻沒有展示技術如何落地,落地之後是何種效果。文章寫出來,就要以最容易讓人接受的方式帶讀者進入作者的世界,而不是裝作一副高高在上的樣子俯視眾生。所以,文章開篇,一定是最直接的展示技術的落地效果。提供可運行Demo可以讓讀者親自嘗試。
橫向周邊知識: 一項核心技術,必然不是獨立存在,技術是一個體系,但是一篇文章能夠詳述的技術有限,必然是以一項技術為中心,其他技術作為輔助。核心技術需要詳述,但是周邊技術,也需要交代,參天大樹拔地而起也少不得土壤作為依附。用簡明的語言交代周邊知識,並提供這些知識正確的研究方向。也是一個負責任的博文作者不可忽視的一步。
縱深專業技術: 做技術,最忌諱的就是淺嘗輒止。稍微深入一點就退出去,一來不利於理解底層實現,長此以往永遠只是一個技術小白,成不了大師;二來不利於長久記憶, 記憶力再強的人時間長了,技術細節必然會記憶模糊。但是如果深入內核,理解了原理,在技術的大方向上絕對不會偏差。作為要成為架構師的男人,即使記不了那麼多細節,但是對於大方向的把握絕對不能錯。所以,技術縱深很有必要。
正文大綱1、Demo地址
2、本文所涉技術盤點
3、關於Android權限的梗
4、初級/中級/高級android開發的權限請求寫法
5、AOP優雅權限框架詳解
gradle配置
Java代碼
6、AOP思想以及常用AOP框架
7、AspectJ AOP框架的深入原理研究
正文1.Demo地址Demo地址:https://github.com/18598925736/GracefulPermissionFramework/tree/dev_aspectJ
2. 本文所涉技術盤點以下適合有一定Android開發年限的開發閱讀。並且對以下技術點至少有個基本了解,才能理解本文demo代碼
java代碼中大量使用@符號作為註解標誌,註解用途多種多樣,但是基本都是做標記,用於源碼期,編譯期,或者運行期的特別處理。註解有自己的特定語法以及API。
Gradle是androidStudio中的項目構建框架,用於將android源碼工程整合編譯打包成apk。其中可以自定義gradle插件,也可以引用他人發布的gradle插件來給項目構建過程中加入自己想要的邏輯。
java反射,某些不方便直接使用的類或者方法,可以通過反射的方式使用。反射通常用在框架設計,hook技術中。
本文的重點是優雅地寫出權限申請的代碼,要讀懂本文自然不能對權限一無所知。
面向切面編程是代碼解耦的重要手段之一。更多信息且看下文。
3. 關於Android權限的梗權限問題,自android問世以來就是一個梗,最早做android的那一批人,當時可以隨便獲取用戶信息,包括聯繫人,包括簡訊內容,包括通話記錄,可以說Android被人詬病的安全問題,源自於此。代碼層面,開發者只需要對照android官網權限說明,在manifest文件中聲明所需的權限,即可在代碼種訪問所需的數據。各類權限十分繁多,超過上百種權限,適用於各種不同場景,記不下來,一般也不用記。
需要的時候到官網https://developer.android.com/reference/android/Manifest.permission.html查找即可.
下面總結幾點Android發展歷史種,權限體系的重大變革。
Android 1.0 - Android 5.0/5.1 App開發者只需要在清單文件中聲明權限,安裝的時候就會自動授予。
Android 6.0 起 谷歌把所有的權限分為2類,普通權限,即 依然是只需要在清單文件中聲明即可。另一類,是危險權限,涉及到用戶隱私的權限,除了在清單文件種聲明之外,還需要在 App啟動之時動態申請,並且,谷歌還提供了 權限組和權限的概念差別,把某一些功能類似的權限放在一個組別,當你去申請其中一個權限的時候,其實也是在默認申請該組的其他權限。雖然這種做法可以讓你少寫一個權限,但是谷歌依然建議把所需的權限寫完整,因為保不齊哪天谷歌就變更了權限組,到時候代碼出兼容問題,沒必要,而且把所需權限寫完整也是編程好習慣。
下圖是所有的危險權限以及權限組。
1. STORAGE 權限組的兩個權限,READEXTERNALSTORAGE / WRITEETERNALSTORAGE 無需動態申請(但是依然要在清單文件中聲明), 因為Q系統啟用了沙盒機制,app訪問自己app所屬目錄無需任何權限,而如果是要訪問app所屬目錄之外的地方,就需要申請 READEXTERNALSTORAGE / WRITEETERNALSTORAGE這兩個權限。
2. 如果設備在後臺運行時,需要使用 位置信息,需要動態申請權限,Q 引入了 ACCESSBACKGROUNDLOCATION 這個新權限,目的是限制後臺進程獲取悄悄的獲取用戶位置信息。如果此權限運行在Q以下(不含)的系統時,就會默認授予,但是Q及以上,則必須申請。
3. 其他一些改動,詳見官網,https://developer.android.google.cn/about/versions/10/privacy/changes?hl=zh-tw.
4. 初級/中級/高級android開發的權限請求寫法權限的梗其實就那麼一些,比較簡單。上面這些梗,我們需要 特別關注的只有一個,那就是6.0以後的動態權限申請。它的處理方式為:
主要流程轉化成代碼展示出來:
AndroidManifest.xml
<uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permissionandroid:name="android.permission.ACCESS_COARSE_LOCATION"/>
Java 代碼
/**
* 申請權限
*/
protectedvoid requestPermission(String[] permissions, int requestCode) {
// 檢查已經有了這些權限
if(PermissionUtil.hasSelfPermissions(this, permissions)) {
Log.e(TAG, "Activity,requestPermission: 所有權限都已經有了,無需申請");
} else{
// 開始請求權限
ActivityCompat.requestPermissions(this, permissions, requestCode);
}
}
/**
* 處理回調
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
publicvoid onRequestPermissionsResult(int requestCode, @NonNullString[] permissions, @NonNullint[] grantResults) {
if(PermissionUtil.verifyPermissions(grantResults)) {//檢查是否都賦予了權限
granted(requestCode);
} else{
// 顯示提示
if(PermissionUtil.shouldShowRequestPermissionRationale(this, permissions)) {
//shouldShowRequestPermissionRationale 這個方法就是檢查,是不是權限被永久拒絕了。。。如果用就拒絕,這裡就返回false,只是第一次拒絕,那就返回true
// 取消權限
denied(requestCode);
} else{
// 權限被拒絕
deniedForever(requestCode);
}
}
}
上面申請權限 ActivityCompat.requestPermissions 和處理回調 onRequestPermissionsResult是開發者需要手動編碼的地方。
同樣是上面的邏輯, 初級/中級/高級開發者的處理方式截然不同。
一個完整的商業項目,勢必會涉及到非常多的 Activity, Fragment,以及 普通Java類等等 ,諸多地方需要使用到特定的權限,如果我們 ctrl+H全文搜索一下 onRequestPermissionsResult,發現如下場景:
同樣一份回調方法,居然在項目中出現了25次之多. 而且是權限申請這種和業務並不直接搭邊的代碼 還嵌入到業務代碼內部。OK,這裡就不多說了。
(PS: 其實這個就是我自己公司的代碼,我不知道為什麼會這樣....也許是公司人員更替太多,後人都懶得改架構)
PS:參考 https://github.com/18598925736/GracefulPermissionFramework/tree/dev
中級開發,作為有一定工作經驗的程式設計師,知道如何優化代碼,減少維護成本,那麼他很可能會發現,需要用到權限申請的地方,基本上是以Activity和Fragment,只要解決了這裡的代碼冗餘,他會這麼寫
publicabstractclassBaseActivityextendsAppCompatActivityimplementsIPermissionCallback{
protectedstaticfinalString TAG = "BaseActivity";
/**
* 申請權限
*/
protectedvoid requestPermission(String[] permissions, int requestCode) {
// 檢查已經有了這些權限
if(PermissionUtil.hasSelfPermissions(this, permissions)) {
Log.e(TAG, "Activity,requestPermission: 所有權限都已經有了,無需申請");
} else{
// 開始請求權限
ActivityCompat.requestPermissions(this, permissions, requestCode);
}
}
/**
* 請求回饋
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
publicvoid onRequestPermissionsResult(int requestCode, @NonNullString[] permissions, @NonNullint[] grantResults) {
if(PermissionUtil.verifyPermissions(grantResults)) {//檢查是否都賦予了權限
granted(requestCode);
} else{
// 顯示提示
if(PermissionUtil.shouldShowRequestPermissionRationale(this, permissions)) {
//shouldShowRequestPermissionRationale 這個方法就是檢查,是不是權限被永久拒絕了。。。如果用就拒絕,這裡就返回false,只是第一次拒絕,那就返回true
// 取消權限
denied(requestCode);
} else{
// 權限被拒絕
deniedForever(requestCode);
}
}
}
}
publicabstractclassBaseFragmentextendsFragmentimplementsIPermissionCallback{
protectedstaticfinalString TAG = "BaseFragment";
/**
* 申請權限
*/
protectedvoid requestPermission(String[] permissions, int requestCode) {
// 是否已經有了這些權限
if(PermissionUtil.hasSelfPermissions(getActivity(), permissions)) {
Log.e(TAG, "Activity,requestPermission: 所有權限都已經有了,無需申請");
} else{
// 開始請求權限
ActivityCompat.requestPermissions(getActivity(), permissions, requestCode);
}
}
/**
* 請求回饋
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
publicvoid onRequestPermissionsResult(int requestCode, @NonNullString[] permissions, @NonNullint[] grantResults) {
if(PermissionUtil.verifyPermissions(grantResults)) {//檢查是否都賦予了權限
granted(requestCode);
} else{
// 顯示提示
if(PermissionUtil.shouldShowRequestPermissionRationale(getActivity(), permissions)) {
//shouldShowRequestPermissionRationale 這個方法就是檢查,是不是權限被永久拒絕了。。。如果用就拒絕,這裡就返回false,只是第一次拒絕,那就返回true
// 取消權限
denied(requestCode);
} else{
// 權限被拒絕
deniedForever(requestCode);
}
}
}
}
然後使用同樣一個 IPermissionCallback接口來處理申請權限的可能結果(用戶同意,用戶拒絕,用戶永久拒絕)
/**
* 權限申請結果接口
*/
publicinterfaceIPermissionCallback{
/**
* 授予權限
*/
void granted(int requestCode);
/**
* 這次拒絕,但是並沒有勾選"以後不再提示"
*/
void denied(int requestCode);
/**
* 勾選"以後不再提示",並且拒絕
*/
void deniedForever(int requestCode);
}
但是, 我們需要權限申請的地方只有Activity和Fragment麼?
不,還可能有:
Service : 比如啟動一個Service播放本地音樂,可能需要本地存儲權限,如果此時才來申請,那麼service應該如何申請權限?經過實驗,我發現Service沒有辦法去申請權限,因為 ActivityCompat.requestPermissions()方法的第一個參數是 Activity, 而在一個Service中,無法直接去獲得一個Activity對象。
普通Java類: 一個普通的Java工具類,他的作用是從手機內部存儲中讀寫文件,那麼他需要本地存儲權限, 它該如何申請?獲取你可以想出一點偏方來解決這個問題,但是如果停留在中級開發的層次,永遠無法給出一個優雅的解決方案。
詳細的解析下一章節再寫,先來看代碼效果:
Activity:
Fragment:
普通Java類:
**Service**:
觀察以上三張圖中代碼的相同點:
都利用了3個自定義註解: @PermissionNeed , @PermissionDenied, @PermissionCancel
@PermissionNeed 修飾修飾的是 用戶授予權限之後的 java方法
@PermissionDenied 註解修飾的是 用戶拒絕權限之後的 java方法
@PermissionCancel 註解修飾的是 用戶永久拒絕之後的 java方法
3個註解,在Activity,Fragment,Service 以及 普通Java類的使用方式完全相同,也可以說,高級開發/架構師的處理方式,把 Activity,Fragment,Service以及普通Java類 的差異化消除了,達成了 代碼調用的通用性, 從根本上解決了 動態權限申請在 業務代碼中的冗餘問題。
使用這種做法,再也不用擔心自己的業務代碼會和 權限相關的代碼發生交叉,讓業務代碼更加清晰。
下一篇開始講解Demo.
萬水千山總是情,點個在看行不行