Android 6.0實作在Acitivty及Fragment取得權限的方法

最近開始收到一些使用者反映,原本可以使用的功能,手機升級為Android 6.0之後卻不能用了,原因在於Android 6.0的權限管理,將一些列為危險等級的功能權限都預設關閉起來,如此便造成了某些功能因為權限沒有允許而沒有辦法使用。

關於解決的方法在Android官方網站很早就有列出如何透過程式的方法重新取得使用者的授權,只是因為一直沒有遇到需求,所以才會到現在才處理Android 6.0的權限部份。
整個開發的設計流程為:
a.透過程式的方法要求所須的權限
b.取得權限成功與失敗的結果
c.如果使用者提供授權就執行該功能(本例為playMusic())
d.如果使用者取消授權就跳出對話框再次詢問(告知為何需要使用者的權限)
e.如果使用者依然拒絕提供授權就不執行該功能
以下是開發實作的過程:

1.製作取得權限的公用函式

/**
* 取得權限(for Android 6.0)
*
* @param activity
* @param permission
* @param permissionCode
*/
public static void getPermission(Activity activity, String permission, int permissionCode) {

   if (ContextCompat.checkSelfPermission(activity.getApplicationContext(), permission)
           != PackageManager.PERMISSION_GRANTED) {

       ActivityCompat.requestPermissions(activity, new String[]{permission}, permissionCode);
   }
}
直接將取得授權寫成一個公用函式的好處,在於可以在不同的頁面隨時都能夠傳入不同的權限參數,就可以觸發取得所屬權限的功能,而且也可以在呼叫程式碼的時候變得簡潔。而其中第一個參數: Activtiy,如果是在Fragment的時候也可以傳入getActivity(),一樣可以使用這個函式。

2.設定權限的requestCode:
自訂的requestCode來做為判斷要求的各個不同的權限,在下一步驟將會使用到。

假設將取得寫入磁碟空間的requestCode設定為0
private final int PERMISSION_WRITE_STORAGE = 0;
而設定的requestCode必須在0~255之間,不然就會得到下列的錯誤:
java.lang.IllegalArgumentException: Can only use lower 8 bits for requestCode

3.取得要求權限所回傳結果:
要求權限之後會有一個用來處理回傳結果的的callback: onRequestPermissionsResult,所以要把取得權限成功與失敗的結果寫下後續要處理的事情。而在這邊透過取得的grantResults array來做為判斷是否有取得權限的方法。
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

   switch (requestCode) {

       case PERMISSION_WRITE_STORAGE:

           // If request is cancelled, the result arrays are empty.
           if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
               /*get permission success*/

               playMusic(); //執行播放音樂的方法

           } else {
               /*get permission fail*/

               showDialog("請提供授權", "聽音樂需要存放檔案的權限才能順利播放",
                       "重新提供", getPermissionListener, "不要提供", closeActivityListener);
           }
           break;

       default:
           break;
   }

}

4.其他搭配使用的公用函式

/**
* 產生對話框(具有兩個選項按鈕)
*
* @param title     標題
* @param message   文字
* @param confirm1  確定文字1
* @param listener1 按鈕監聽1
* @param confirm2  確定文字2
* @param listener2 按鈕監聽2
*/
public static void showDialog(String title, String message,
                       String confirm1, DialogInterface.OnClickListener listener1,
                       String confirm2, DialogInterface.OnClickListener listener2) {

   AlertDialog.Builder dialog = new AlertDialog.Builder(this);
   dialog.setTitle(title).setMessage(message)
           .setPositiveButton(confirm1, listener1)
           .setNegativeButton(confirm2, listener2)
           .show();
}

/**
* 對話框按鈕監聽(重開權限)
*/
private DialogInterface.OnClickListener getPermissionListener =
       new DialogInterface.OnClickListener() {
           @Override
           public void onClick(DialogInterface dialog, int which) {

               //取得檔案寫入的權限
               ValueUtility.getPermission(MyActivity.this,
                       Manifest.permission.WRITE_EXTERNAL_STORAGE,
                       PERMISSION_WRITE_STORAGE);
           }
       };

/**
* 對話框按鈕監聽(離開)
*/
private DialogInterface.OnClickListener closeActivityListener =
       new DialogInterface.OnClickListener() {
           @Override
           public void onClick(DialogInterface dialog, int which) {
               Toast.makeText((MyActivity.this, "無法播放音樂", Toast.LENGTH_SHORT).show();
           }
       };

基本上在Activity就如同上述的方式就能夠執行成功了,但如果是在Fragment呼叫要求權限(requestPermissions)之後,或許會發現在Fragment裡的onRequestPermissionsResult沒有反應,於是就造成沒辦法實作取得授權之後的功能(雖然有看到網路上有人提到這是bug,似乎會在Android Support Library 23.3.0修復),而目前就先使用撰寫Interface在Activity實作的方式來解決。

1.新增一個用來接收取得權限之後的interface
public interface PermissionCallback {

    void getPermission(); //通知取得權限後要執行的方法
}

2.在Fragment產生Interface的reference
public class MyFragment extends Fragment{
   public PermissionCallback permissionCallback;

   @Override
       public void onAttach(Activity activity) {
           super.onAttach(activity);
           try {
               permissionCallback = (PermissionCallback) activity; //產生Callback的reference
           } catch (ClassCastException e) {
               throw new ClassCastException(activity.toString() + " must implement onViewSelected");
           }
       }
}

3.在Fragment使用Interface裡的函式觸發
permissionCallback.getPermission();

4.在Activity實作功能
public class MyActivity extends FragmentActivity implements PermissionCallback{
   private MyFragment myFragment; //想要取得權限的fragment

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      myFragment = new MyFragment(); //產生myFragment實體

      //將Fragment加入此Activity
      getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, myFragment).commit();
   }

   @Override
   public void getPermission() {
      //在Callback function裡執行要求權限
      ValueUtility.getPermission(MyActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    PERMISSION_WRITE_STORAGE);
   }

}

5.取得要求權限所回傳結果
在MyActivity裡執行Override Method: onRequestPermissionsResult()
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

   switch (requestCode) {

       case PERMISSION_WRITE_STORAGE:

           // If request is cancelled, the result arrays are empty.
           if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
               /*get permission success*/

               myFragment.playMusic(); //執行在myFragment裡的playMusic方法

           } else {
               /*get permission fail*/

               showDialog("請提供授權", "聽音樂需要存放檔案的權限才能順利播放",
                       "重新提供", getPermissionListener, "不要提供", closeActivityListener);
           }
           break;

       default:
           break;
   }

}


Reference:
http://developer.android.com/intl/zh-tw/training/permissions/requesting.html
http://stackoverflow.com/questions/33331073/android-what-to-choose-for-requestcode-values
http://stackoverflow.com/questions/33169455/onrequestpermissionsresult-not-being-called-in-dialog-fragment

留言

這個網誌中的熱門文章

ISO 27001 LA 主導稽核員 考照心得

Android如何實作強制App版本更新

如何實作從API抓取資料顯示在列表頁(ListView)上