Android 6.0 运行时权限封装之路

前言

时间如梭,总在不经意间流逝。经常会想,毕业到现在我都干了些什么,该有什么样的追求?

运行时权限

运行时权限是 Android 6.0 (SDK 23)新特性,更好的保护了用户的隐私。

如果你build.gradle文件中声明targetSdkVersion23及以上

    defaultConfig {
        applicationId "xxxx"//you applicationId
        minSdkVersion 14
        targetSdkVersion 23//23及以上
        versionCode 1
        versionName "1.0"

        multiDexEnabled true
    }

用到的隐私权限就要进行申请;反之小于23,默认授予AndroidManifest.xml中的所有申请权限。

注意: 需要用到的权限必须在 AndroidManifest.xml 文件中申明。

权限的分类

Google将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、获取wifi连接状态、访问网络等;另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如获取位置信息、读取sdcard、访问通讯录等。最后会附上权限 Android 权限一栏。

危险权限Dangerous Permissions 如下图:

per

你也可以通过adb命令进行权限查看:

adb shell pm list permissions

危险权限查看:

adb shell pm list permissions -d -g

dangerous permissions是分组的,那么这么分组又会有什么影响呢?

如果你申请某个危险的权限,假设你的app已经被用户授权了同一组的某个危险权限比如READ_CONTACTS,那么WRITE_CONTACTSGET_ACCOUNTS危险权限也同样被授权了,而不需要用户再次去申请。同样弹出的申请权限dialog文本说明也是对整个权限组的说明,而不是单个权限。

权限处理流程

AndroidManifest.xml申明权限

AndroidManifest.xml文件下申明读的联系人权限:

 <uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
检查SDK版本
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true;
        }

如果targetSdkVersion版本小于23,不需要进行权限的处理。

检查申明的权限是否已经被授权
        if (checkSelfPermission(requestPermission) != PackageManager.PERMISSION_GRANTED) {
           //未被授权
        } else {
           //已授权
        }

如果未被授权,则需要申明授权处理。

申请授权
requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final int requestCode)

第一个参数是 activity

第二个参数是申请授权的权限组

第三个参数是请求码(如果不理解可以参考startActivityForResult()方法)

权限回调处理
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == PERMISSON_REQUESTCODE) {
           if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 申请权限已经授权
            } else {
                // 申请权限未授权
            }
        }

    }

参数grantResults申请权限返回的状态信息。

这里还有一个方法需要说明一下:

public boolean shouldShowRequestPermissionRationale(String permission)

当用户第一次勾选了【禁止后不再询问】勾选框,并点击【禁止】按钮,第二次申请该权限返回true的方法,一般给于用户提示缺少改权限。\n\n请点击\”设置\”-\”权限\”-打开所需权限进行处理。

打开设置代码:

    private void startAppSettings() {
        Intent intent = new Intent(
                Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.parse("package:" + getPackageName()));
        startActivity(intent);
    }

在实际开发中可能会几个权限同时需要授权处理,如定位需要位置权限,访问 sdcard 。若单个申请则会显得很麻烦。所以我这里单独的提取了一个抽象类用于权限的处理,并把返回结果延迟到它的子类进行处理。对的,这里用了模板模式。具体看以下代码:

public abstract class CheckPermissionsActivity extends AppCompatActivity {

首先申明CheckPermissionsActivity为抽象类。

public abstract void requestPermissionResult(boolean allowPermission);

申明抽象方法,并把allowPermission是否授予权限设置为参数。

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == PERMISSON_REQUESTCODE) {
            if (verifyPermissions(grantResults)) {
                requestPermissionResult(true);
            } else {
                requestPermissionResult(false);
                showMissingPermissionDialog();
            }
        }

    }

最后我们在回调函数中传递参数给requestPermissionResult抽象方法。下面我们来看看一个具体的例子。文章的最后已经附上了源码。

获取通讯录案例
package com.github.permissondemo;

import android.Manifest;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.util.Log;

public class MainActivity extends CheckPermissionsActivity implements LoaderManager.LoaderCallbacks<Cursor> {

    static final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
            ContactsContract.Data.DISPLAY_NAME};

    String mCurFilter;

    static String TAG;

    protected String[] needContactsPermissions = {
            Manifest.permission.READ_CONTACTS
    };

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

        TAG = getClass().getName();

        requestPermission();
    }

    private void requestPermission() {
        if (!mayRequestPermission(needContactsPermissions)) {
            return;
        }
        initLoader();
    }

    @Override
    public void requestPermissionResult(boolean allowPermission) {
        if (allowPermission) {
            initLoader();
        }
    }


    private void initLoader() {
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        Uri baseUri;
        if (mCurFilter != null) {

            baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI
                    , Uri.encode(mCurFilter));

        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
        }

        String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";

        CursorLoader loader = new CursorLoader(MainActivity.this, baseUri,
                PROJECTION, select, null, ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");

        return loader;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if (cursor == null) {
            return;
        }
        while (cursor.moveToNext()) {
            String[] names = cursor.getColumnNames();
            for (String str : names) {
                String contacts = cursor.getString(cursor.getColumnIndex(str));
                Log.e(TAG, "contacts=" + contacts);
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {

    }
}

如上案例,运行时权限的处理非常方便,只需要两步:

  1. 第一步请求权限处理:
    private void requestPermission() {
        if (!mayRequestPermission(needContactsPermissions)) {
            return;
        }
        initLoader();//读取联系人
    }    
  • needContactsPermissions 参数表示:申请的权限字符数组。

如果你需要申请定位,调摄像头或其它的危险权限,请 initLoader(); 替换成定位,开启摄像头的方法。

  1. 第二步请求权限的回调处理:
    @Override
    public void requestPermissionResult(boolean allowPermission) {
        if (allowPermission) {
            initLoader();//读取联系人
        }
    }
  • allowPermission 参数表示:用户是否允许申请的权限,true 允许;false 拒绝。

如果你需要申请定位,调摄像头或其它的危险权限,请 initLoader(); 替换成定位,开启摄像头的方法。

看下运行效果:

per

点击【始终允许】:

per

快速,简单,便捷,只需要你复制 CheckPermissionsActivity 类到你的项目中,需要申请权限的 Activity 继承于它。

可能你会说为啥不使用:

per

插件生成权限处理呢?

真心觉得不好用。

用哪种方式,取决于自己。

最后祝愿大家,每天适当休息下,不要一直敲代码,起来动一动。感觉自己就是每天坐太久,身体不如以前好了。

若有什么疑问请留言。源码地址

Android 权限一栏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值