随着 Marshmallow 的发布,安卓增加了一种新的权限管理模式,要求开发者们采用一种不同的方式来处理安卓的权限管理。在本系列文章中,我们将会从技术角度和如何提供流畅用户体验的角度来探讨权限问题的处理方法。(#Permissions – Part 1)

在深入探讨之前,必须先说明一点:一个 app 需要的权限实际分为以下两种:app 操作的核心权限——如果没有这些核心权限,应用程序就无法正确运行;以及辅助功能所需的权限。举个例子,对于一个拍照 app 来说,CAMERA 权限是核心功能的一部分——不能拍照的拍照 app 完全没用。不过,还有一些额外功能,例如为照片标记拍摄地点(需要开启ACCESSFINELOCATION),但是没有这些功能 app 也可以正常使用。

接下来的系列文章中,我们将要以某个 app 为例展开讨论,但是要想正常使用,需要开启两个权限: RECORDAUDIO 和 MODIFYAUDIO_SETTINGS。为了获得这些权限,我们必须按照常规在 Manifest 中声明:

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:Android="http://schemas.android.com/apk/res/android"  
  xmlns:tools="http://schemas.android.com/tools"
  package="com.stylingandroid.permissions">

  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

  <application
    android:allowBackup="false"
    android:fullBackupContent="false"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme.NoActionBar"
    tools:ignore="GoogleAppIndexingWarning">

    <activity android:name=".MainActivity" />

    <activity
      android:name=".PermissionsActivity"
      android:label="@string/title_activity_permissions"
      android:theme="@style/AppTheme.NoActionBar">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>

</manifest>  

这是从 API 1就开始执行的安卓权限声明的标准操作。但是,一旦我们指定 targetSdkVersion 23 或其后版本,我们还需要请求开放运行时所需的权限。这一点非常重要,因为已经有很多失败案例,开发者直接将 targetSdkVersion 更新到最新版本,却发现他们的 app 总是崩溃,这是因为他们没有实现请求运行时所需权限的代码。如果你在Google Play 应用商店发布了基于 API 23 或之后版本的 app,之后你就无法用基于更低版本开发的 APK 替换此 app,这就成了大问题。

另外在这里需要说明的是,现在已经有很多库可以简化运行权限的请求流程。它们的效果和用处各不相同,但是笔者觉得关键是在使用之前,先理解基本流程,否则就会因为不了解所选库的实际功能而出现问题。这也是促成本系列文章的主要原因。

我们请求的两个权限实际上属于不同的类别:RECORDAUDIO 被看做是一个高风险的权限,MODIFYAUDIO_SETTINGS 则被看做是低风险的权限。高风险权限指的是那些可能危害安全或隐私的权限;而低风险权限指的是需要访问 app 域以外的资源,但是对用户的隐私几乎没有危害。低风险权限将会被系统自动批准使用,而高风险权限则需要运行时用户明确授权。

在这个流程中,首先要做的是确定我们是否已经获得所需权限。在 API 23 中,Context 增加了一些新方法来检验是否已经获得某项权限。但是,使用 ContextCompat 总是比直接使用 Context、并且进行 API 层面的检查更好:

class PermissionsChecker {  
    private final Context context;

    public PermissionsChecker(Context context) {
        this.context = context;
    }

    public boolean lacksPermissions(String... permissions) {
        for (String permission : permissions) {
            if (lacksPermission(permission)) {
                return true;
            }
        }
        return false;
    }

    private boolean lacksPermission(String permission) {
        return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED;
    }

}

这点其实很简单——ContextCompat#checkSelfPermission 方法的作用不言自明,它会返回 PackageManager.PERMISSIONDENIED 或者 PackageManager.PERMISSIONGRANTED。笔者还增加了更进一步的逻辑,可用于检查一个 app 是否已经获得所需权限。

这里有必要重申 ContextCompat 的作用。在未升级到 Marshmallow系统、不支持新的运行权限管理模式的设备上运行 checkSelfPermission() 方法时,总会返回 PackageManger.PERMISSION_GRANTED——在旧的操作系统中,权限已经得到隐式授权,因此我们只需要通过 Manifest 声明,就能实现适用于所有操作系统的方法,也不需要在代码中写入任何 API 层面的特定检查。

以防你不明白为什么笔者要专门讲解这个问题,原因是之后我们要在 app 内的所有 Activity 中进行这些检查。因此,如果能够将逻辑检查与 Activity 区别开来,就能减少重复,提高代码的可维护性。

要想在 Activity 中实际使用该方法,我们只需用 Activity 所需的权限列表来调用之:

public class MainActivity extends AppCompatActivity {  
    private static final String[] PERMISSIONS = new String[] {Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        PermissionsChecker checker = new PermissionsChecker(this);

        if (checker.lacksPermissions(PERMISSIONS)) {
            Snackbar.make(toolbar, R.string.no_permissions, Snackbar.LENGTH_INDEFINITE).show();
        }
    .
    .
    .
    }
    }

这一点其实非常直白。

这在未升级到 Marshmallow 系统的设备上同样适用:

如何用不同的方式来处理安卓的权限管理? 技术分享 第1张

【图中文字】权限

但是我们还没有 Marshmallow 系统下缺省问题的处理办法,因此只会跳出一个悬浮框。

如何用不同的方式来处理安卓的权限管理? 技术分享 第2张

【图中文字】

权限

App 需要开启麦克风才能运行

请求缺失权限的流程要复杂得多。我们将在下篇文章中详细讨论。

The source code for this article is available here.

本文中的源代码在此处

OneAPM Mobile Insight ,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客

原文地址:https://blog.stylingandroid.com/permissions-part-1/