* 本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出
前言
permissions4m 最初的設計是僅僅做成一個編譯器註解框架,在1.0.0版本時,它純粹地實現了原生 Android 請求流程,關於它的設計思路可以檢視這篇如何打造一個 Android 編譯時註解框架。但是當投入筆者自己專案中使用的時候,筆者發現國產手機有許多適配缺陷,例如:
1.
ActivityCompat.shouldShowRequestPermissionRationale(Activity, String)
無法彈出許可權申請對話方塊2. 明明使用者點選拒絕授權,卻回撥的是許可權申請成功方法
3.只能有一次許可權是否授予選擇,拒絕後就無法再有提示
相信做過許可權適配的小夥伴們都知道適配國產 Android 機的許可權會有多少坑,而國內也並沒有任何許可權申請框架解決這些問題,於是筆者在 1.0.2 版本中增強了許可權申請功能的適配,現在對於這三個問題 permissions4m 都有良好的解決:
1.許可權申請必定彈出對話方塊
2.拒絕授權時回撥的就是授權失敗方法,接受授權時回撥的就是授權成功方法,讓它一定回撥正確的方法
3.當系統許可權申請對話方塊不再彈出時,函式可返回一個 Intent,跳轉到系統設定頁面或者手機管家介面
情景再現
Boss: "mmp,為什麼展示聯絡人這塊在小米手機顯示不出來?"
Programmer: "boss,其他手機都沒問題,我這塊做了許可權申請的,但是小米就是不彈出許可權申請對話方塊,與此同時小米預設授權失敗,所以不能讀取通訊錄。"
Boss: "mmp,那這塊呢,明明說了讀取日曆許可權成功了,為什麼還是沒讀取到?"
Programmer: "boss,其他手機都沒問題,我這塊做了許可權申請的,但是小米就是不彈出許可權申請對話方塊,與此同時小米預設授權成功,但是實際上是授權失敗的。"
Boss: "mmp,那這塊呢,明明我拒絕授予許可權,為什麼你提示我授權成功?"
Programmer: "boss,其他手機都沒問題,我這塊做了許可權申請的,小米彈出許可權申請對話方塊,與此同時你點了拒絕,但是小米做了手腳,實際上呼叫了授權成功的方法。"
Boss: "你有個毛用?測試機我都給你買好了,還這麼菜,收拾收拾滾蛋吧。"
Programmer: "f**k 小米!"
原生 Android 請求方式在小米等國內機型上適配的情形,相信有部分讀者已經有過經歷,這裡就不做原生測試了,拿出國內一個比較有名的許可權申請框架我們來看看:
小米申請地理位置:
小米申請聯絡人:
小米申請手機狀態:
其實不僅僅是小米,國內其它手機也會有一樣的問題,筆者再做了一份 oppo a57 的截圖:
可以看到,在申請過程中並沒有任何彈窗彈出,並且提示授權成功,而實際上我們到許可權管理介面可以看到並未得到許可權。
下圖是使用 permissions4m 的效果:
permissions4m 申請地理位置(小米):
permissions4m 申請聯絡人(小米):
permissions4m 申請手機狀態(小米):
permissions4m 申請簡訊、日曆(OPPO A57)
我們可以看到彈出了許可權申請對話方塊,而且授予許可權的情況下確實獲得了許可權。
生活中,無論是作為開發者還是普通使用者,應該都有接觸到過 5.0+ 的小米/魅族手機,使用過這些手機的讀者們應該還有些許印象——部分國產手機早在 android 6.0 之前,也就是在 google 推出動態許可權之前就有了許可權申請,而國產的 5.0 許可權申請使用 6.0 的許可權申請程式碼是行不通的,理由很簡單——在5.0的系統原始碼裡沒有6.0許可權申請的原始碼,這個問題在 permissions4m 2.0.0
版本中已經迎刃而解了,這意味著從 2.0.0
版本開始, permissions4m 開始支援國產手機 5.0 許可權申請了。
permissions4m 簡介
簡介中只是節選了部分內容,更詳細完整的請移至專案:github.com/jokermonn/p…。
注:截止筆者釋出部落格為止,permissions4m 最新版本為 2.0.0
引入依賴
Gradle 依賴
project
中的 build.gradle
:
buildscript {
// ...
}
allprojects {
repositories {
// 請新增如下一行
maven { url 'https://jitpack.io' }
}
}複製程式碼
app
中的 build.gradle
:
dependencies {
compile 'com.github.jokermonn:permissions4m:2.0.0-lib'
annotationProcessor 'com.github.jokermonn:permissions4m:2.0.0-processor'
}複製程式碼
註解回撥
在需要許可權申請的地方呼叫
Permissions4M.get(MainActivity.this)
// 是否強制彈出許可權申請對話方塊,建議為 true
.requestForce(true)
// 許可權
.requestPermission(Manifest.permission.RECORD_AUDIO)
// 許可權碼
.requestCode(AUDIO_CODE)
// 如果需要使用 @PermissionNonRationale 註解的話,建議新增如下一行
// 返回的 intent 是跳轉至**系統設定頁面**
// .requestPageType(Permissions4M.PageType.MANAGER_PAGE)
// 返回的 intent 是跳轉至**手機管家頁面**
// .requestPageType(Permissions4M.PageType.ANDROID_SETTING_PAGE)
.request();複製程式碼
如:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Permissions4M.get(MainActivity.this)
.requestForce(true)
.requestPageType(Permissions4M.PageType.MANAGER_PAGE)
.requestPermission(Manifest.permission.RECORD_AUDIO)
.requestCode(AUDIO_CODE)
.request();
}
});複製程式碼
然後將會回撥相應的 @PermissionsGranted
、@PermissionsDenied
、@PermissionsRationale
/PermissionsCustomRationale
、@PermissionsNonRationale
所修飾的方法
@PermissionsGranted
授權成功時回撥,註解中需要傳入引數,分為兩種情況:
單引數:
@PermissionsGranted(LOCATION_CODE)
,被修飾函式無需傳入引數,例:@PermissionsGranted(LOCATION_CODE) public void granted() { ToastUtil.show("地理位置授權成功"); }複製程式碼
多引數:
@PermissionsGranted({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
,被修飾函式需要傳入一個 int 引數,例:@PermissionsGranted({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE}) public void granted(int code) { switch (code) { case LOCATION_CODE: ToastUtil.show("地理位置許可權授權成功"); break; case SENSORS_CODE: ToastUtil.show("感測器許可權授權成功"); break; case CALENDAR_CODE: ToastUtil.show("讀取日曆許可權授權成功"); break; default: break; } }複製程式碼
@PermissionsDenied
授權失敗時回撥,註解中需要傳入引數,分為兩種情況:
單引數:
@PermissionsDenied(LOCATION_CODE)
,被修飾函式無需傳入引數,例:@PermissionsDenied(LOCATION_CODE)
public void denied() { }複製程式碼
多引數:
@PermissionsDenied({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
,被修飾函式需要傳入一個 int 引數,例:@PermissionsDenied({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE}) public void denied(int code) { switch (code) { case LOCATION_CODE: ToastUtil.show("地理位置許可權授權失敗"); break; case SENSORS_CODE: ToastUtil.show("感測器許可權授權失敗"); break; case CALENDAR_CODE: ToastUtil.show("讀取日曆許可權授權失敗"); break; default: break; } }複製程式碼
@PermissionsRationale
二次授權時回撥,用於解釋為何需要此許可權,註解中需要傳入引數,分為兩種情況:
單引數:
@PermissionsRationale(LOCATION_CODE)
,被修飾函式無需傳入引數,例:@PermissionsRationale(LOCATION_CODE) public void rationale() { ToastUtil.show("請開啟讀取地理位置許可權"); }複製程式碼
多引數:
@PermissionsRationale({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
,被修飾函式需要傳入一個 int 引數,例:@PermissionsRationale({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE}) public void rationale(int code) { switch (code) { case LOCATION_CODE: ToastUtil.show("請開啟地理位置許可權授權"); break; case SENSORS_CODE: ToastUtil.show("請開啟感測器許可權授權"); break; case CALENDAR_CODE: ToastUtil.show("請開啟讀取日曆許可權授權"); break; default: break; }複製程式碼
注:系統彈出許可權申請 dialog 與 toast 提示是非同步操作,所以如果存在希望自行彈出一個 dialog 後(或其他同步需求)再彈出系統對話方塊,那麼請使用 @PermissionsCustomRationale
@PermissionsCustomRationale
二次授權時回撥,用於解釋為何需要此許可權,註解中需要傳入引數,分為兩種情況:
- 單引數:
@PermissionsCustomRationale(LOCATION_CODE)
,被修飾函式無需傳入引數,例:
@PermissionsCustomRationale(LOCATION_CODE)
public void cusRationale() {
new AlertDialog.Builder(this)
.setMessage("讀取地理位置許可權申請:\n我們需要您開啟讀取地理位置許可權(in activity with annotation)")
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Permissions4M.get(MainActivity.this)
// 注意新增 .requestOnRationale()
.requestOnRationale()
.requestPermission(Manifest.permission.READ_SMS)
.requestCode(SMS_CODE)
.request();
}
})
.show();
}複製程式碼
- 多引數:
@PermissionsCustomRationale({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
,被修飾函式需要傳入一個 int 引數,例:
@PermissionsCustomRationale({SMS_CODE, AUDIO_CODE})
public void cusRationale(int code) {
switch (code) {
case SMS_CODE:
new AlertDialog.Builder(this)
.setMessage("簡訊許可權申請:\n我們需要您開啟簡訊許可權(in activity with annotation)")
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Permissions4M.get(MainActivity.this)
// 注意新增 .requestOnRationale()
.requestOnRationale()
.requestPermission(Manifest.permission.READ_SMS)
.requestCode(SMS_CODE)
.request();
}
})
.show();
break;
case AUDIO_CODE:
new AlertDialog.Builder(this)
.setMessage("錄音許可權申請:\n我們需要您開啟錄音許可權(in activity with annotation)")
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Permissions4M.get(MainActivity.this)
// 注意新增 .requestOnRationale()
.requestOnRationale()
.requestPermission(Manifest.permission.RECORD_AUDIO)
.requestCode(AUDIO_CODE)
.request();
}
})
.show();
break;
default:
break;
}複製程式碼
注:除上述以外的 dialog,開發者可以自定義其他展示效果,呼叫許可權申請時請使用,否則會陷入無限呼叫自定義 Rationale 迴圈中:
Permissions4M.get(MainActivity.this)
// 務必新增下列一行
.requestOnRationale()
.requestPermission(Manifest.permission.RECORD_AUDIO)
.requestCode(AUDIO_CODE)
.request();複製程式碼
@PermissionsNonRationale
當使用者點選拒絕許可權且不再提示(國產畸形許可權適配擴充套件)情況下呼叫,此時意味著無論是 @PermissionsCustomRationale 或者 @PermissionsRationale 都不會被呼叫,無法給予使用者提示,此時該註解修飾的函式被呼叫,註解中需要傳入引數,分為兩種情況:
單引數:
@PermissionsNonRationale(LOCATION_CODE)
,被修飾函式只需傳入 Intent 引數,例:@PermissionsNonRationale({LOCATION_CODE}) public void nonRationale(Intent intent) { startActivity(intent); }複製程式碼
多引數:
@PermissionsNonRationale(AUDIO_CODE, CALL_LOG_CODE)
,被修飾函式需傳入 int 引數和 Intent 引數,例:@PermissionsNonRationale({AUDIO_CODE, CALL_LOG_CODE}) public void nonRationale(int code, final Intent intent) { switch (code) { case AUDIO_CODE: new AlertDialog.Builder(MainActivity.this) .setMessage("讀取錄音許可權申請:\n我們需要您開啟讀取錄音許可權") .setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startActivity(intent); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }) .show(); break; case CALL_LOG_CODE: new AlertDialog.Builder(MainActivity.this) .setMessage("讀取通話記錄許可權申請:\n我們需要您開啟讀取通話記錄許可權") .setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startActivity(intent); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }) .show(); break; default: break; } }複製程式碼
Intent 型別為兩種,一種是跳轉至系統設定頁面,另一種是跳至手機管家頁面,而具體的設定方法請參考 註解回撥 中 .requestPageType(int)
設定方法。
Listener 回撥
例:
Permissions4M.get(MainActivity.this)
// 是否強制彈出許可權申請對話方塊
.requestForce(true)
// 許可權
.requestPermission(Manifest.permission.READ_CONTACTS)
// 許可權碼
.requestCode(READ_CONTACTS_CODE)
// 許可權請求結果
.requestCallback(new Wrapper.PermissionRequestListener() {
@Override
public void permissionGranted() {
ToastUtil.show("讀取通訊錄許可權成功 in activity with listener");
}
@Override
public void permissionDenied() {
ToastUtil.show("讀取通訊錄權失敗 in activity with listener");
}
@Override
public void permissionRationale() {
ToastUtil.show("請開啟讀取通訊錄許可權 in activity with listener");
}
})
// 許可權完全被禁時回撥函式中返回 intent 型別(手機管家介面)
.requestPageType(Permissions4M.PageType.MANAGER_PAGE)
// 許可權完全被禁時回撥函式中返回 intent 型別(系統設定介面)
//.requestPageType(Permissions4M.PageType.ANDROID_SETTING_PAGE)
// 許可權完全被禁時回撥,介面函式中的引數 Intent 是由上一行決定的
.requestPage(new Wrapper.PermissionPageListener() {
@Override
public void pageIntent(final Intent intent) {
new AlertDialog.Builder(MainActivity.this)
.setMessage("讀取通訊錄許可權申請:\n我們需要您開啟讀取通訊錄許可權(in activity with listener)")
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(intent);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
})
.show();
}
})
.request();複製程式碼
同步申請
使用
@PermissionsRequestSync
修飾 Activity 或 Fragment傳入兩組引數
- value 陣列:請求碼
- permission 陣列:請求許可權
使用
Permissions4M.get(MainActivity.this).requestSync();
進行同步許可權申請
例:參考 sample 中 MainActivity 上的設定 ——
@PermissionsRequestSync(
permission = {Manifest.permission.BODY_SENSORS,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.READ_CALENDAR},
value = {SENSORS_CODE,
LOCATION_CODE,
CALENDAR_CODE})
public class MainActivity extends AppCompatActivity複製程式碼
後記
permissions4m 的目標是適配儘可能多的國產機型,包括但不限於小米、魅族、OPPO、VIVO、華為等機型,不僅是6.0+版本,後期也會支援到小米、魅族等低版本也有許可權申請的手機。但是由於筆者個人能力有限,所以希望儘可能多的開發者參與到此專案的開發當中,更多詳情請移至 permissions4m。
求職
筆者目前剛剛大四,想找一份關於 android 的實習,上海/杭州或其他地區都可,如果貴司正在招實習,望告知
jokerzoc.cn@gmail.com
,謝謝。