* 本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈java
ActivityCompat.shouldShowRequestPermissionRationale(Activity, String)
沒法彈出權限申請對話框
Boss: "mmp,爲何展現聯繫人這塊在小米手機顯示不出來?"
Programmer: "boss,其餘手機都沒問題,我這塊作了權限申請的,可是小米就是不彈出權限申請對話框,與此同時小米默認受權失敗,因此不能讀取通信錄。"
Boss: "mmp,那這塊呢,明明說了讀取日曆權限成功了,爲何仍是沒讀取到?"
Programmer: "boss,其餘手機都沒問題,我這塊作了權限申請的,可是小米就是不彈出權限申請對話框,與此同時小米默認受權成功,可是其實是受權失敗的。"
Boss: "mmp,那這塊呢,明明我拒絕授予權限,爲何你提示我受權成功?"
Programmer: "boss,其餘手機都沒問題,我這塊作了權限申請的,小米彈出權限申請對話框,與此同時你點了拒絕,可是小米作了手腳,實際上調用了受權成功的方法。"
Boss: "你有個毛用?測試機我都給你買好了,還這麼菜,收拾收拾滾蛋吧。"
Programmer: "f**k 小米!"android
原生 Android 請求方式在小米等國內機型上適配的情形,相信有部分讀者已經有過經歷,這裏就不作原生測試了,拿出國內一個比較有名的權限申請框架咱們來看看:git
小米申請地理位置:
github
小米申請聯繫人:
數組
小米申請手機狀態:
bash
其實不只僅是小米,國內其它手機也會有同樣的問題,筆者再作了一份 oppo a57 的截圖:微信
能夠看到,在申請過程當中並無任何彈窗彈出,而且提示受權成功,而實際上咱們到權限管理界面能夠看到並未獲得權限。app
下圖是使用 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 權限申請了。
簡介中只是節選了部份內容,更詳細完整的請移至項目:github.com/jokermonn/p…。
注:截止筆者發佈博客爲止,permissions4m 最新版本爲 2.0.0
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(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(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(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(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();複製代碼
當用戶點擊拒絕權限且再也不提示(國產畸形權限適配擴展)狀況下調用,此時意味着不管是 @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)
設置方法。
例:
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
傳入兩組參數
使用 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複製代碼
jokerzoc.cn@gmail.com
,謝謝。