在 Android 开发里,权限是保障用户隐私和系统安全的关键机制。应用若要访问敏感资源或者执行特定操作,就必须先获得相应权限。
权限分类
1. 普通权限
这类权限不会对用户隐私和系统安全造成太大风险,应用在安装时系统会自动授予。
- 示例:网络访问权限(INTERNET)、设置时区权限(SET_TIME_ZONE)。
- 特点:应用安装完成后就拥有这些权限,无需用户手动授权。
2. 危险权限
涉及用户敏感数据或者会影响其他应用运行的权限属于危险权限。应用需要在运行时动态向用户申请,且必须得到用户明确许可。
-
示例:相机权限(CAMERA)、读写存储权限(READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE)、位置权限(ACCESS_FINE_LOCATION)。
-
特点:
- 危险权限按权限组划分,同一组内的权限,用户授予一个后,应用就可使用该组的其他权限。
- 必须在运行时进行权限检查和请求。
3. 特殊权限
像 SYSTEM_ALERT_WINDOW(悬浮窗)和 WRITE_SETTINGS(修改系统设置)这类权限属于特殊权限,它们有专门的授权流程。
- 特点:需要通过特定的 Intent 启动系统设置界面让用户授权。
4. 签名权限
只有当应用与声明该权限的应用使用相同签名时,才能获得的权限。一般用于系统组件或者同一家公司的应用之间交互。
运行时权限处理流程
1. 检查权限
// 检查是否拥有读取联系人的权限
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
// 未获得权限,需要申请
} else {
// 已获得权限,可以执行相关操作
}
2. 请求权限
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_CODE_PERMISSION_READ_CONTACTS);
3. 处理权限返回结果
通过重写 onRequestPermissionsResult() 方法来处理用户的授权结果。
@Override
public void onRequestPermissionsResult(
int requestCode,
String[] permissions,
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_PERMISSION_READ_CONTACTS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户授予了权限,执行相关操作
readContacts();
} else {
// 用户拒绝了权限
Toast.makeText(
this,
"权限被拒绝,无法读取联系人",
Toast.LENGTH_SHORT).show();
}
}
}
4. 解释请求权限的原因(可选)
// 如果应用之前请求过此权限但用户拒绝了,向用户显示解释
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.READ_CONTACTS)) {
Toast.makeText(
this,
"读取联系人权限有助于获取联系人信息",
Toast.LENGTH_SHORT).show();
// 再次请求权限
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_CODE_PERMISSION_READ_CONTACTS);
} else {
// 请求权限
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_CODE_PERMISSION_READ_CONTACTS);
}
5. 完整代码
public class CheckPermissionActivity extends AppCompatActivity {
private static final int REQUEST_CODE_PERMISSION_READ_CONTACTS = 1001;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_check_permission);
Button btnCheckPermission = findViewById(R.id.btnCheckPermission);
btnCheckPermission.setOnClickListener(v -> checkPermission());
}
private void checkPermission() {
// 检查是否拥有读取联系人的权限
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
// 如果应用之前请求过此权限但用户拒绝了,向用户显示解释
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.READ_CONTACTS)) {
Toast.makeText(
this,
"读取联系人权限有助于获取联系人信息",
Toast.LENGTH_SHORT).show();
// 再次请求权限
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_CODE_PERMISSION_READ_CONTACTS);
} else {
// 请求权限
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_CODE_PERMISSION_READ_CONTACTS);
}
} else {
// 已经拥有权限,执行相关操作
readContacts();
}
}
@Override
public void onRequestPermissionsResult(
int requestCode,
String[] permissions,
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_PERMISSION_READ_CONTACTS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户授予了权限,执行相关操作
readContacts();
} else {
// 用户拒绝了权限
Toast.makeText(
this,
"权限被拒绝,无法读取联系人",
Toast.LENGTH_SHORT).show();
}
}
}
private void readContacts() {
// 这里添加读取联系人的逻辑
Toast.makeText(this, "正在读取联系人...", Toast.LENGTH_SHORT).show();
}
}
- 权限检查:使用
ContextCompat.checkSelfPermission()检查应用是否已经拥有指定的权限。 - 请求权限:如果应用没有权限,使用
ActivityCompat.requestPermissions()请求权限。 - 处理用户响应:在
onRequestPermissionsResult()方法中处理用户对权限请求的响应。 - 解释请求原因:如果用户之前拒绝过权限,可以通过
ActivityCompat.shouldShowRequestPermissionRationale()来决定是否向用户显示解释。
危险权限组
Android 将危险权限划分为多个权限组,同一组内的权限,用户授予一个后,应用就可使用该组的其他权限。以下是一些常见的危险权限组:
| 权限组 | 包含的权限 |
|---|---|
| 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 等 |
| SENSORS | BODY_SENSORS |
| SMS | SEND_SMS、RECEIVE_SMS、READ_SMS 等 |
| STORAGE | READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE |
特殊权限处理
悬浮窗权限(SYSTEM_ALERT_WINDOW)
1. AndroidManifest.xml
在应用的 AndroidManifest.xml 文件中添加悬浮窗权限声明:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
2. MainActivity.java
public class floatingViewPermissionActivity extends AppCompatActivity {
private static final int REQUEST_CODE_OVERLAY_PERMISSION = 1;
private View floatingView;
private WindowManager windowManager;
private WindowManager.LayoutParams params;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_floating_view_permission);
Button btnShowFloatingWindow = findViewById(R.id.btnShowFloatingWindow);
btnShowFloatingWindow.setOnClickListener(v -> checkOverlayPermission());
}
private void checkOverlayPermission() {
// Check if the permission is already granted
if (Settings.canDrawOverlays(this)) {
showFloatingWindow();
} else {
// Request the permission if not granted
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMISSION);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_OVERLAY_PERMISSION) {
if (Settings.canDrawOverlays(this)) {
showFloatingWindow();
} else {
Toast.makeText(this, "悬浮窗权限未开启", Toast.LENGTH_SHORT).show();
}
}
}
private void showFloatingWindow() {
// Initialize window manager
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// Inflate the floating view layout
floatingView = LayoutInflater.from(this).inflate(R.layout.floating_view, null);
// Set up the parameters for the floating window
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
);
params.gravity = Gravity.TOP | Gravity.START;
params.x = 0;
params.y = 100;
// Add the view to the window
windowManager.addView(floatingView, params);
// Set up touch listener for dragging the floating view
floatingView.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
params.x = (int) event.getRawX();
params.y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
params.x = (int) event.getRawX() - (int) event.getX();
params.y = (int) event.getRawY() - (int) event.getY();
windowManager.updateViewLayout(floatingView, params);
break;
}
return true;
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// Remove the floating view when the activity is destroyed
if (floatingView != null && windowManager != null) {
windowManager.removeView(floatingView);
}
}
}
3. floating_view.xml
创建一个简单的悬浮窗布局文件 res/layout/floating_view.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="悬浮窗示例"
android:textSize="16sp"
android:padding="10dp"
android:background="#FF00FF" />
4. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/btnShowFloatingWindow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示悬浮窗" />
</LinearLayout>