國產 Android 權限申請最佳適配方案 —— permissions4m

* 本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈java

前言


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

原生 Android 請求方式在小米等國內機型上適配的情形,相信有部分讀者已經有過經歷,這裏就不作原生測試了,拿出國內一個比較有名的權限申請框架咱們來看看:git

小米申請地理位置:
github

小米申請地理位置
小米申請地理位置

小米申請聯繫人:
數組

小米申請聯繫人
小米申請聯繫人

小米申請手機狀態:
bash

小米申請手機狀態
小米申請手機狀態

其實不只僅是小米,國內其它手機也會有同樣的問題,筆者再作了一份 oppo a57 的截圖:微信

這裏寫圖片描述
這裏寫圖片描述

能夠看到,在申請過程當中並無任何彈窗彈出,而且提示受權成功,而實際上咱們到權限管理界面能夠看到並未獲得權限。app

下圖是使用 permissions4m 的效果:框架

permissions4m 申請地理位置(小米):
異步

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,謝謝。
相關文章
相關標籤/搜索