Android是一个权限分离的操作系统,每个应用使用不同的系统身份运行(Linux用户ID和群组ID)。系统的不同部分也被分隔进不同的身份域。Linux以此来实现不同应用与其他应用和系统的独立。
更细粒度的安全特征由“权限”机制提供,该机制在一个进程能够执行的特定操作上强制施加了限制,并且每一个URI权限能够授予对特定数据的临时访问。
Android安全框架的一个中心设计点是,默认情况下,没有应用具有执行任何不利地影响其他应用,操作系统或用户的操作的权限。这些操作包括,读写用户私有数据,读写其他应用程序文件,执行网络访问,保持设备唤醒等等。
因为每一个Android程序运行在进程沙盒里,所以应用间必须严格地分享资源和数据。它们能够通过声明它们需要的权限来增加基础沙盒没有提供的能力。应用声明并请求它们需要的权限,Android系统提示用户以获得用户赞同。
应用程序沙盒不依赖用来构建应用的技术。实际上,Dalvik VM不是一个安全的边界,任何应用都能运行本地代码。所有类型的应用-Java,native,hybrid-是使用相同的方式被沙盒封装,他们有相同的安全等级。
所有的apk必须使用证书签名,该证书的私有密钥由开发者持有。该证书标识了应用的作者。该证书不需要被一个证书颁发机构签名。并且对于Android应用使用自签名证书,是非常被允许和支持的。在Android中证书的作用是用来区分应用的作者。
在安装时,Android会给每一个安装包一个不同的Linux用户ID。这个身份在该应用在设备上的期间是恒定不变的。在不同的设备上,相同的安装包可有有不同的UID。
安全限制发生在进程级别。由于不同应用需要运行作为不同的Linux用户,所以两个不同安装包的代码通常不能运行在相同的进程。但你可以在AndroidManifest.xml的manifest
标签中使用sharedUserId
属性来让不同的包分配相同的用户ID。通过这样做,两个包是被作为相同的应用,有相同的用户ID和文件权限。注意为了确保安全,仅两个使用相同签名,且请求相同sharedUserId
的应用会被分配相同的用户ID。
一个应用存储的任何数据都会被分配应用的用户ID,并且正常情况下不能被其他的包访问。当使用getSharedPreferences(String, int)
,openFileOutput(String, int)
或openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)
等方法创建新文件时,你能够使用MODE_WORLD_READABLE
或MODE_WORLD_WRITEABLE
标签来允许其他应用读写该文件。当设置这些标签时,该文件仍然被你的应用所拥有,但它的全局读写权限已经被适当地设置,所以其它应用可以看见它。
默认情况下,一个基本的Android应用没有被分配任何权限,这意味着它不能做任何不利地影响用户体验和数据的操作。为了使用设备上受保护的特征,你必须在AndroidManifest.xml中使用一个或多个
标签。
例如,一个应用需要接收SMS信息,则这样定义:
"http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
"android.permission.RECEIVE_SMS" />
...
如果你的应用声明了“正常权限”(不会对用户隐私或设备造成影响的操作权限),系统会自动授予这些权限。如果你的应用声明了“危险权限”(会对用户隐私或设备造成影响的操作权限),系统会询问用户严格地授予这些权限。请求权限的表现形式具体依赖于设备系统版本和应用的目标系统版本:
通常一个权限失败会导致抛出一个SecurityException
。然而,这不是绝对的。例如,sendBroadcast(Intent)
方法在数据被传递到每一个接收器时检查权限,当方法调用返回后,如果权限失败,不会收到异常。在几乎所有情况下,一个权限失败会打印一条log。
Android系统提供的所有权限可以在Manifest.permission
中找到。应用也可以定义自己的权限。
通常特定的权限被施加在你的应用的有数的几个位置上:
随着时间的推移,新的限制会被增加到平台,为了使用这些APIs,你的应用必须请求它以前不需要的权限。如果已发布的应用访问了这些API会怎么呢?Android会让其自由的访问,并在新平台版本上将请求的新权限授予应用,以防止已发布的应用挂掉。Android通过targetSdkVersion属性提供的值来判断是否需要请求新权限。如果这个值低于新权限被增加的版本,则Android默认授予该权限。
例如,WRITE_EXTERNAL_STORAGE权限是在API级别4增加的用来限制访问共享存储空间的权限。如果你的targetSdkVersion设置的值小于等于3,则该权限在新Android版本上默认被增加到你的应用。
注意:被自动增加到App的权限,会被列在Google Play权限列表中,尽管你的应用不需要请求他们。
为了避免这些并移除你不需要的默认权限,一直尽可能高的更新你的targetSdkVersion。你可以在Build.VERSION_CODES
文档中找到某个权限是在哪个版本中新增的。
系统权限被分为几个不同的保护级别。两个最重要的保护级别是正常”和“危险”权限:
PROTECTION_NORMAL类权限 |
---|
ACCESS_LOCATION_EXTRA_COMMANDS |
ACCESS_NETWORK_STATE |
ACCESS_NOTIFICATION_POLICY |
ACCESS_WIFI_STATE |
BLUETOOTH |
BLUETOOTH_ADMIN |
BROADCAST_STICKY |
CHANGE_NETWORK_STATE |
CHANGE_WIFI_MULTICAST_STATE |
CHANGE_WIFI_STATE |
DISABLE_KEYGUARD |
EXPAND_STATUS_BAR |
GET_PACKAGE_SIZE |
INSTALL_SHORTCUT |
INTERNET |
KILL_BACKGROUND_PROCESSES |
MODIFY_AUDIO_SETTINGS |
NFC |
READ_SYNC_SETTINGS |
READ_SYNC_STATS |
RECEIVE_BOOT_COMPLETED |
REORDER_TASKS |
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS |
REQUEST_INSTALL_PACKAGES |
SET_ALARM |
SET_TIME_ZONE |
SET_WALLPAPER |
SET_WALLPAPER_HINTS |
TRANSMIT_IR |
UNINSTALL_SHORTCUT |
USE_FINGERPRINT |
VIBRATE |
WAKE_LOCK |
WRITE_SYNC_SETTINGS |
所有“危险权限”属于权限组。如果设备正运行在Android6.0(API级别23)之上,并且该应用的targetSdkVersion是23或更高,则当应用请求一个“危险权限”时,下面的系统行为出现。
注意:任何权限都属于一个权限组,包括“正常权限”和用户自定义的权限。只是“危险权限”的权限组会影响用户体验,所以我们特殊对待。
权限组 | 权限 |
---|---|
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 |
为了施行自己的权限,你首先需要在AndroidManifest.xml中使用
标签定义权限。
例如,应用想要控制谁能启动一个Activity,则可以像下面这样定义权限:
"http://schemas.android.com/apk/res/android"
package="com.me.app.myapp" >
"com.me.app.myapp.permission.DEADLY_ACTIVITY"
android:label="@string/permlab_deadlyActivity"
android:description="@string/permdesc_deadlyActivity"
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous" />
...
"com.me.app.myapp.permission.DEADLY_ACTIVITY" />
. . .
".FreneticActivity"
android:permission="com.me.app.myapp.permission.DEADLY_ACTIVITY"
. . . >
. . .
protectionLevel
:指定了该权限的安全级别,它告诉系统当应用请求该权限时怎样通知用户,或谁被允许持有该权限。permissionGroup
:其仅帮助系统向用户显示权限。通常你设置其为一个标准系统权限组(android.Manifest.permission_group
中列举)或自定义的权限组。label
:被用来展示给用户当展示一个权限列表时,要尽量简短description
:具体描述一个权限可以在手机“设置”->“应用程序”中选择具体的应用来查看其需要的相关权限,也可以使用adb shell pm list permissions
命令查看权限:
$ adb shell pm list permissions -s
All Permissions:
Network communication: view Wi-Fi state, create Bluetooth connections, full
Internet access, view network state
Your location: access extra location provider commands, fine (GPS) location,
mock location sources for testing, coarse (network-based) location
Services that cost you money: send SMS messages, directly call phone numbers
...
从Android6.0(API级别23)开始,用户在应用运行时授予权限。这种方式简化了应用的安装过程,因为用户不再需要在安装或升级应用时授予权限。这也给了用户在使用应用过程中的更多控制。例如,一个用户能够选择给一个相机应用访问相机的权限而不给获取设备位置的权限。用户能够随时在应用设置界面取消授予的权限。
如果你的应用需要一个“危险权限”,则在每次执行请求这个权限的操作时,你必须检测你是否已经被授予该权限。因为用户可以随时取消授权。
使用ContextCompat.checkSelfPermission()
方法来检测你是否被授予一个权限。例如,下面的代码段展示了怎样检测一个Activity是否已被授予写日历的权限:
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR);
如果应用具有该权限,则该方法返回PackageManager.PERMISSION_GRANTED
,则该应用可以执行该操作。如果该应用不具有该权限,则该方法返回PERMISSION_DENIED
,则应用不得不严格地向用户请求该权限。
如果你的应用需要一个“危险权限”,它必须询问用户授予该权限。Android提供了几种用来请求权限的方法。调用这些方法调起一个标准的对话框,它不能被订制。
有些情况下,你可能想帮用户理解为什么你的应用需要一个权限。例如,如果启动一个拍照应用,则用户不会惊讶于应用请求使用相机的权限,但用户不能理解应用为什么想要访问用户的位置和联系人。在你请求一个权限之前,你应该考虑向用户做出一个解释。注意不要让解释影响了用户。如果你提供了太多的解释,用户可能会产生厌烦,卸载了你的应用。
一种你能够使用的方法是,仅当用户已经拒绝了权限请求时提供解释。如果用户持续尝试使用一个请求权限的功能,但却保持拒绝权限请求,则这可能表明用户不理解为什么应用需要该权限来提供这个功能。像这种情况,可能给出一个解释是个好的实践。
为了帮助找出用户可能需要一个解释的情况,Android提供了一个实用的方法shouldShowRequestPermissionRationale()
。如果应用之前请求过这个权限并且用户拒绝了该请求,则该方法返回true。
注意:如果用户在过去拒绝了这个权限请求并在该权限的请求对话框中选择了“不再询问”选项,则该方法返回false。如果设备策略禁止该应用有该权限则该方法也返回false。
如果你的应用还没有它需要的权限,则该应用必须调用requestPermissions()
方法来请求该权限。该方法需要传递你的应用需要的权限和标识该次权限请求的一个整形请求码。该方法的作用是异步的,当用户响应该对话框后,系统调用回调方法返回授权结果,结果中会包含请求权限时传入的相同请求码。
下面的代码检测应用是否具有读用户联系人的权限并在需要时请求该权限:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
当你的应用请求权限时,系统呈现一个对话框给用户。当用户响应时,系统触发onRequestPermissionsResult()
方法,给出响应结果。下面给出了请求READ_CONTACTS权限时的响应回调方法:
Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
注意:你的应用仍需要严格地请求每一个它需要的权限,尽管用户已经授予了相同权限组的其他权限。因为权限组的分组可能会在将来的某个Android版本更改。你的代码不应该依赖该权限是不是在相同权限组的假设。
当系统询问用户授予权限时,用户有一个操作选项用来告诉系统需要再次询问授予该权限。在这种情况下,每次使用requestPermissions()
方法请求权限,系统都会立即拒绝请求,而不再弹出授权对话框。
Android API中处理运行时权限的方法checkSelfPermission()
,shouldShowRequestPermissionRationale()
,requestPermissions()
等都是在Android6.0版本增加的方法,所以要使用这些方法必须要至少API级别为23,为了调用方法的兼容性可以通过如下检查运行版本的方式处理:
if (Build.VERSION.SDK_INT >= 23) {
// Marshmallow+
} else {
// Pre-Marshmallow
}
这种方法虽然可以解决兼容性问题,但太过复杂,幸运的是,v4兼容库已经提供了相应的兼容方法。
ContextCompat.checkSelfPermission()
PERMISSION_GRANTED
,否则返回PERMISSION_DENIED
,在所有版本都是如此。ActivityCompat.requestPermissions()
OnRequestPermissionsResult
回调方法直接被调用,带着正确的PERMISSION_GRANTED
或者PERMISSION_DENIED
。ActivityCompat.shouldShowRequestPermissionRationale()
后两种方法,在Fragment中也有相应方法,使用v13兼容包的FragmentCompat.requestPermissions()
和FragmentCompat.shouldShowRequestPermissionRationale()
方法。
以拍照功能为例,我们示例请求拍照权限。
在AndroidManifest.xml中声明权限
<uses-permission android:name="android.permission.CAMERA" />
在SingleNormalActivity.java中动态请求权限
/**
* 单个权限请求处理
* Created by sunxiaodong on 16/4/26.
*/
public class SingleNormalActivity extends AppCompatActivity implements View.OnClickListener {
private static final int CAMERA_REQUEST_CODE = 1;
private static final int PERMISSION_REQUEST_CODE = 2;
private Button mGoCamera;
private ImageView mPhotoImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_activity);
initView();
}
private void initView() {
mGoCamera = (Button) findViewById(R.id.go_camera);
mGoCamera.setOnClickListener(this);
mPhotoImageView = (ImageView) findViewById(R.id.photo_imageview);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.go_camera:
getCameraPermission();
break;
}
}
private void getCameraPermission() {
int hasCameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
if (hasCameraPermission != PackageManager.PERMISSION_GRANTED) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
showRationaleDialog(getResources().getString(R.string.permission_camera_rationale),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(SingleNormalActivity.this,
new String[]{Manifest.permission.CAMERA},
PERMISSION_REQUEST_CODE);
}
});
return;
}
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
PERMISSION_REQUEST_CODE);
return;
}
camera();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_CODE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
camera();
} else {
Toast.makeText(this, getResources().getString(R.string.permission_camera_denied), Toast.LENGTH_SHORT).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void camera() {
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCameraIntent, CAMERA_REQUEST_CODE);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE:
Bitmap bm = (Bitmap) data.getExtras().get("data");
mPhotoImageView.setImageBitmap(bm);
break;
default:
break;
}
}
}
private void showRationaleDialog(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
}
下面给出授权过程的一个执行路径
首次点击拍照按钮,弹出自定义授权解释对话框:
点击授权解释对话框的“允许”选项,弹出系统的授权请求对话框:
点击授权请求对话框的“拒绝”选项后,再次点击拍照按钮,弹出有“不再询问”选项的授权请求对话框:
选择“不再询问”选项,点击授权请求对话框的“拒绝”选项后,再次点击拍照按钮,弹出自定义授权解释对话框:
点击授权解释对话框的“允许”选项,不再有任何反应。
分析程序中可知,在运行时权限下,如果想要使用拍照功能,就必须在使用相机功能前,主动请求相机权限,程序中,我们首先调用ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
方法获取应用是否已经被授予相机权限,如果已被授予该权限,则可直接使用相机功能,否则通过调用ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
来判断用户是否拒绝过该权限的授予或是否用户已经选择了“不再询问”选项予以拒绝授予该权限,如果用户已经选择了“不再询问”选项予以拒绝授予该权限,则系统不再弹出权限请求提示框,所以我们自己弹出需要相机权限的解释,如果仅是简单地被拒绝了授予权限,则再次调用ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CODE);
方法请求权限。请求权限后,用户响应授权提示的结果,通过onRequestPermissionsResult()
回调方法返回,接下来根据用户授权情况,做出相应处理。
接下来,在请求拍照功能的同时,一起请求联系人权限。
在AndroidManifest.xml中声明权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
在MultiNormalActivity.java中动态请求权限
/**
* 常规的一次请求多个权限处理方式
* Created by sunxiaodong on 16/4/26.
*/
public class MultiNormalActivity extends AppCompatActivity implements View.OnClickListener {
private static final int CAMERA_REQUEST_CODE = 1;
private static final int PERMISSION_REQUEST_CODE = 2;
private Button mGoCamera;
private ImageView mPhotoImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_activity);
initView();
}
private void initView() {
mGoCamera = (Button) findViewById(R.id.go_camera);
mGoCamera.setOnClickListener(this);
mPhotoImageView = (ImageView) findViewById(R.id.photo_imageview);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.go_camera:
getCameraAndContactsPermission();
break;
}
}
private boolean addPermission(List permissionsList, String permission) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
permissionsList.add(permission);
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission))
return false;
}
return true;
}
private void getCameraAndContactsPermission() {
List permissionsNeeded = new ArrayList();
final List permissionsList = new ArrayList();
if (!addPermission(permissionsList, Manifest.permission.CAMERA)) {
permissionsNeeded.add("相机");
}
if (!addPermission(permissionsList, Manifest.permission.READ_CONTACTS)) {
permissionsNeeded.add("读联系人");
}
if (!addPermission(permissionsList, Manifest.permission.WRITE_CONTACTS)) {
permissionsNeeded.add("写联系人");
}
if (permissionsList.size() > 0) {
if (permissionsNeeded.size() > 0) {
String message = getResources().getString(R.string.permission_rationale, permissionsNeeded.get(0));
for (int i = 1; i < permissionsNeeded.size(); i++)
message = message + ", " + permissionsNeeded.get(i);
showRationaleDialog(message,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MultiNormalActivity.this, permissionsList.toArray(new String[permissionsList.size()]),
PERMISSION_REQUEST_CODE);
}
});
return;
}
ActivityCompat.requestPermissions(this, permissionsList.toArray(new String[permissionsList.size()]),
PERMISSION_REQUEST_CODE);
return;
}
camera();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_CODE: {
Map perms = new HashMap();
perms.put(Manifest.permission.CAMERA, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
if (perms.get(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
camera();
} else {
Toast.makeText(this, getResources().getString(R.string.permission_denied), Toast.LENGTH_SHORT).show();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void camera() {
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCameraIntent, CAMERA_REQUEST_CODE);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE:
Bitmap bm = (Bitmap) data.getExtras().get("data");
mPhotoImageView.setImageBitmap(bm);
break;
default:
break;
}
}
}
private void showRationaleDialog(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
}
从程序中可知,一次请求多个权限的过程同一次请求一个权限的过程,所以不再对其进行分析,读者可下载示例源码自行查看结果。
使用Android原生API请求权限有些复杂,所以出现了很多第三方库来简化过程,PermissionDispatcher
是其中一个比较好用的库,接下来对使用其进行权限请求进行介绍。
继续以拍照为例。
在AndroidManifest.xml中声明权限
<uses-permission android:name="android.permission.CAMERA" />
在SinglePermissionDispatcherActivity.java中动态请求权限
/**
* 使用PermissionDispatcher处理一次单个权限请求
* Created by sunxiaodong on 16/4/26.
*/
@RuntimePermissions
public class SinglePermissionDispatcherActivity extends AppCompatActivity implements View.OnClickListener {
private static final int CAMERA_REQUEST_CODE = 1;
private Button mGoCamera;
private ImageView mPhotoImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_activity);
initView();
}
private void initView() {
mGoCamera = (Button) findViewById(R.id.go_camera);
mGoCamera.setOnClickListener(this);
mPhotoImageView = (ImageView) findViewById(R.id.photo_imageview);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.go_camera:
SinglePermissionDispatcherActivityPermissionsDispatcher.cameraWithCheck(this);
break;
}
}
@NeedsPermission(Manifest.permission.CAMERA)
public void camera() {
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCameraIntent, CAMERA_REQUEST_CODE);
}
@OnShowRationale(Manifest.permission.CAMERA)
void showRationaleForCamera(PermissionRequest request) {
showRationaleDialog(R.string.permission_camera_rationale, request);
}
@OnPermissionDenied(Manifest.permission.CAMERA)
void onCameraDenied() {
Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show();
}
@OnNeverAskAgain(Manifest.permission.CAMERA)
void onCameraNeverAskAgain() {
Toast.makeText(this, R.string.permission_camera_never_askagain, Toast.LENGTH_SHORT).show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
SinglePermissionDispatcherActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE:
Bitmap bm = (Bitmap) data.getExtras().get("data");
mPhotoImageView.setImageBitmap(bm);
break;
default:
break;
}
}
}
private void showRationaleDialog(@StringRes int messageResId, final PermissionRequest request) {
new AlertDialog.Builder(this)
.setPositiveButton(R.string.button_allow, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton(R.string.button_deny, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.cancel();
}
})
.setCancelable(false)
.setMessage(messageResId)
.show();
}
}
下面给出授权过程的一个执行路径
首次点击拍照按钮,弹出系统的授权请求对话框:
点击授权请求对话框的“拒绝”选项后,再次点击拍照按钮,弹出自定义授权解释对话框:
点击自定义授权解释对话框的“允许”选项后,弹出有“不再询问”选项的授权请求对话框:
选择“不再询问”选项,点击授权请求对话框的“拒绝”选项后,再次点击拍照按钮,弹出授权不再询问的Toast提示:
在AndroidManifest.xml中声明权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
在MultiPermissionDispatcherActivity.java中动态请求权限
/**
* 使用PermissionDispatcher处理一次多个权限请求
* Created by sunxiaodong on 16/4/26.
*/
@RuntimePermissions
public class MultiPermissionDispatcherActivity extends AppCompatActivity implements View.OnClickListener {
private static final int CAMERA_REQUEST_CODE = 1;
private Button mGoCamera;
private ImageView mPhotoImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_activity);
initView();
}
private void initView() {
mGoCamera = (Button) findViewById(R.id.go_camera);
mGoCamera.setOnClickListener(this);
mPhotoImageView = (ImageView) findViewById(R.id.photo_imageview);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.go_camera:
MultiPermissionDispatcherActivityPermissionsDispatcher.cameraWithCheck(this);
break;
}
}
@NeedsPermission({Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void camera() {
Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCameraIntent, CAMERA_REQUEST_CODE);
}
@OnShowRationale({Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
void showRationaleForCamera(PermissionRequest request) {
showRationaleDialog(R.string.permission_camera_contacts_rationale, request);
}
@OnPermissionDenied({Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
void onCameraDenied() {
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_SHORT).show();
}
@OnNeverAskAgain({Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
void onCameraNeverAskAgain() {
Toast.makeText(this, R.string.permission_never_askagain, Toast.LENGTH_SHORT).show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
MultiPermissionDispatcherActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE:
Bitmap bm = (Bitmap) data.getExtras().get("data");
mPhotoImageView.setImageBitmap(bm);
break;
default:
break;
}
}
}
private void showRationaleDialog(@StringRes int messageResId, final PermissionRequest request) {
new AlertDialog.Builder(this)
.setPositiveButton(R.string.button_allow, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton(R.string.button_deny, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.cancel();
}
})
.setCancelable(false)
.setMessage(messageResId)
.show();
}
}
从程序中可知,使用PermissionDispatcher一次请求多个权限的过程同一次请求一个权限的过程,所以不再对其进行分析,读者可下载示例源码自行查看结果。
源码地址