如何使用Runtime Permission(kotlin)

如何使用Runtime Permission(kotlin)

情境

當你在Android6.0以上的版本,加強了隱私權的重視,因此在操作APP上,就必須讓使用者自行勾選是否開放這些權限。

問題

如何使用Runtime Permission讓使用者勾選隱私權選項

完整程式碼

可以直接參考本篇文章,完整範例可以透過 GitHub 下載來看。

GitHub

  • 注意 : 你必須在Android 6.0以上的裝置才能使用這個章節的權限控制
class MainActivity : AppCompatActivity() {  
 companion object {  
  private const val MY_PERMISSIONS_REQUEST_READ_CONTACTS = 100  
 }  
 override fun onCreate(savedInstanceState: Bundle?) {  
  super.onCreate(savedInstanceState)  
  setContentView(R.layout.activity_main)  
  if (ContextCompat.checkSelfPermission(this@MainActivity,  Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {  
   if (ActivityCompat.shouldShowRequestPermissionRationale(this@MainActivity,       Manifest.permission.CAMERA)) {  
    AlertDialog.Builder(this@MainActivity)  
     .setMessage("我真的沒有要做壞事, 給我權限吧?")  
     .setPositiveButton("OK") { _, _ ->  
      ActivityCompat.requestPermissions(this@MainActivity,  
       arrayOf(Manifest.permission.CAMERA),  
       MY_PERMISSIONS_REQUEST_READ_CONTACTS)  
     }  
     .setNegativeButton("No") { _, _ -> finish() }  
     .show()  
   } else {  
    ActivityCompat.requestPermissions(this@MainActivity,  
     arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE),  
     MY_PERMISSIONS_REQUEST_READ_CONTACTS)  
   }  
  }  
 }  

 override fun onRequestPermissionsResult(requestCode: Int,  permissions: Array<String>, grantResults: IntArray) {  
  when (requestCode) {  
   MY_PERMISSIONS_REQUEST_READ_CONTACTS -> {  
    if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
    } else {  
     finish()  
    }  
    return  
   }  
  }  
 }  
}

在 Android 6.0 之後,多了一個功能叫做Request Permission at Run Time。
https://developer.android.com/training/permissions/requesting.html
也就是說在執行程式的時候,會跟user請求是否開放權限給APP,
如果使用者同意,APP才可以使用這些權限。
在新版的權限管理將權限分為兩大類:

  • 一般的權限
  • 危險的權限

https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
由這個連結可以看到危險的權限有哪些。
http://www.tablesgenerator.com/markdown_tables

權限群組(Permission Group) 權限(Permissions)
行事曆(CALENDAR) READ_CALENDAR
WRITE_CALENDAR
照相機(CAMERA) CAMERA
聯絡人(CONTACTS) READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
位置(LOCATION) ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
麥克風(MICROPHONE) RECORD_AUDIO
電話(PHONE) READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
感應器(SENSORS) BODY_SENSORS
簡訊(SMS) SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
儲存(STORAGE) READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
也就是說,除了在androidMainfes上面宣告以外,還要額外在程式內進行判斷,否則APP拿不到危險權限,則會有閃退的危機。 如果你想要操作權限控管,那麼你必須在 Gradle 內設定 target version 為 23 或以上,且 minSdkVersion 也必須在 23 或以上。
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.example.givemepass.permissiondemo"
        minSdkVersion 23
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
}

一開始可以使用 checkSelfPermission 來檢查是否擁有這個權限。

// Assume thisActivity is the current activity
val permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_CALENDAR)

你會得到 PackageManager.PERMISSION_GRANTED 跟 PackageManager.PERMISSION_DENIED 兩種結果。
如果是 PackageManager.PERMISSION_GRANTED,代表你已經獲得使用者同意 App 可以使用該權限,否則就可以透過requestPermissions 來跟使用者要求權限。

 ActivityCompat.requestPermissions(thisActivity,
     new String[]{Manifest.permission.READ_CONTACTS},
     MY_PERMISSIONS_REQUEST_READ_CONTACTS)

MY_PERMISSIONS_REQUEST_READ_CONTACTS 是你自定義常數,透過 requestPermissions 方法呼叫,會出現這樣的一個畫面。

當使用者按下 DENY or ALLOW,就會回傳至 onRequestPermissionsResult 方法,第二個參數是你所要求的權限,可以同時要求多個權限。
ActivityCompat.requestPermissions(MainActivity.this,
 new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE},
 MY_PERMISSIONS_REQUEST_READ_CONTACTS)

那麼你就會看到權限 Dialog 會出現數字,並且要按下同意數次。

而當使用者按下的結果會回傳至 onRequestPermissionsResult 方法,只需透過 requestCode 你自訂的參數,以及 permissions 跟 grantResults 的順序,就可以知道使用者同意了那些權限。

override fun onRequestPermissionsResult(requestCode: Int,  permissions: Array<String>, grantResults: IntArray) {  
 when (requestCode) {  
  MY_PERMISSIONS_REQUEST_READ_CONTACTS -> {  
   if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
   } else {  
    finish()  
   }  
   return  
  }  
 }  
}  

如果你把 permissions 和 grantResults 印出來就會看到以下訊息。

permissions:["android.permission.CAMERA","android.permission.READ_EXTERNAL_STORAGE"]
grantResults:[0,0]

grantResults 回來的結果如果 0,代表使用者同意權限,如果回來的是 -1,代表被拒絕了。
如果想知道 App 是否有開啟權限,可以到設定 -> 應用程式 -> 你的 App -> PERMISSION。

就可以看到APP內權限是否被打開。

在Google官方網站的範例中,使用 shouldShowRequestPermissionRationale 方法來跟使用者解釋更多需要使用這些權限的理由,當使用者第一次看到授權畫面,這個方法會先回傳 false,當使用者按下了拒絕, 而第二次再進入 App 的時候,shouldShowRequestPermissionRationale 就會回傳 true,讓你透過客製化的畫面來跟使用者強調拿到這個權限並沒有要做甚麼壞事 :P

// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
        Manifest.permission.READ_CONTACTS)) {
} else {
    ActivityCompat.requestPermissions(thisActivity,
            new String[]{Manifest.permission.READ_CONTACTS},
            MY_PERMISSIONS_REQUEST_READ_CONTACTS)
}

可以透過這個方法進行這樣的修改

if (ContextCompat.checkSelfPermission(this@MainActivity,  Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {  
  if (ActivityCompat.shouldShowRequestPermissionRationale(this@MainActivity,       Manifest.permission.CAMERA)) {  
   AlertDialog.Builder(this@MainActivity)  
    .setMessage("我真的沒有要做壞事, 給我權限吧?")  
    .setPositiveButton("OK") { _, _ ->  
     ActivityCompat.requestPermissions(this@MainActivity,  
      arrayOf(Manifest.permission.CAMERA),  
      MY_PERMISSIONS_REQUEST_READ_CONTACTS)  
    }  
    .setNegativeButton("No") { _, _ -> finish() }  
    .show()  
  } else {  
   ActivityCompat.requestPermissions(this@MainActivity,  
    arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE),  
    MY_PERMISSIONS_REQUEST_READ_CONTACTS)  
  }  
 }  
}

如此一來, 使用者可以決定是否給權限,則無法操作某些功能或者整個 App 無法操作(當然使用者經驗會變差)。

參考
https://developer.android.com/training/permissions/requesting.html#perm-request
https://developer.android.com/guide/topics/security/permissions.html
http://android-developers.blogspot.tw/2015/08/building-better-apps-with-runtime.html