Android申请权限

Android申请权限

Android 6.0系统加入了危险权限管理,在使用一些涉及到用户隐私方面的操作时,需要获取用户的授权才能使用,如通讯录、打电话、短信、相机、定位、录音、存储等隐私权限。获取用户授权权限,我们提倡动态申请权限,用到的时候才去向用户申请,例如点击扫一扫,这时我们申请相机权限,用户也明白,自然就授权了。即使用户没有授权,也仅仅影响此模块不能使用正常功能,并不影响其他模块。千万不能,在应用打开时的启屏页,一股脑把应用所有地方用到的权限都申请,不给还不能用,简直是流氓行为!

本文大纲:

  • Android权限
  • 申请权限流程
    • 检查是否已被授权危险权限
    • 提示或解释接下来的操作需要危险权限
    • 申请权限
  • 申请权限引起的生命周期变化
  • 申请权限封装
  • 总结

一、Android权限

Android中的权限分为系统权限和应用自定义权限,系统权限又分为正常权限和危险权限。使用系统权限需要在manifest文件中注册权限,若是危险权限,还需要在使用时动态申请。

terminal中使用这个命令可以列出调试手机的所有权限,包含应用自定义的权限:

adb shell pm list permissions

正常权限(Normal Permissions):

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_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS

危险权限(Dangerous Permissions):

group:android.permission-group.CONTACTS
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.GET_ACCOUNTS
  permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE
  permission:android.permission.READ_CALL_LOG
  permission:android.permission.READ_PHONE_STATE
  permission:android.permission.CALL_PHONE
  permission:android.permission.WRITE_CALL_LOG
  permission:android.permission.USE_SIP
  permission:android.permission.PROCESS_OUTGOING_CALLS
  permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.CALENDAR
  permission:android.permission.READ_CALENDAR
  permission:android.permission.WRITE_CALENDAR

group:android.permission-group.CAMERA
  permission:android.permission.CAMERA

group:android.permission-group.SENSORS
  permission:android.permission.BODY_SENSORS

group:android.permission-group.LOCATION
  permission:android.permission.ACCESS_FINE_LOCATION
  permission:android.permission.ACCESS_COARSE_LOCATION

group:android.permission-group.STORAGE
  permission:android.permission.READ_EXTERNAL_STORAGE
  permission:android.permission.WRITE_EXTERNAL_STORAGE

group:android.permission-group.MICROPHONE
  permission:android.permission.RECORD_AUDIO

group:android.permission-group.SMS
  permission:android.permission.READ_SMS
  permission:android.permission.RECEIVE_WAP_PUSH
  permission:android.permission.RECEIVE_MMS
  permission:android.permission.RECEIVE_SMS
  permission:android.permission.SEND_SMS
  permission:android.permission.READ_CELL_BROADCASTS

二、申请权限流程

动态申请权限的流程:

《Android申请权限》

1、检查是否已被授权危险权限

/**
 * 检查是否已被授权危险权限
 * @param permissions
 * @return
 */
public boolean checkDangerousPermissions(Activity ac, String[] permissions) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        return true;
    }
    for (String permission : permissions) {
        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED ||
                ActivityCompat.shouldShowRequestPermissionRationale(ac, permission)) {
            return false;
        }
    }
    return true;
}

Android 6.0以下,在AndroidManifest.xml文件中注册权限即可。
Android 6.0及其以后,既要在AndroidManifest.xml文件中注册,又要动态申请危险权限。

2、提示或解释接下来的操作需要危险权限

在申请危险权限时,有些用户觉得涉及到了他的隐私内容,可能不会授权。在用户拒绝后,再提示授权危险权限的必要性,就为时已晚,用户需要去设置里打开(拒绝时设置不在提醒)。如若我们在申请权限前,就有一个温馨提示,告诉用户危险权限的必要性,用户理解后更容易授权。

饿了么的权限申请过程:

《Android申请权限》

3、申请权限

在activity中申请权限,权限授权结果会回调在acitvity中:

//acitivty中申请权限
ActivityCompat.requestPermissions(activity, permissions, requestCode);

//activity权限授权结果回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

下面我们看ActivityCompat.requestPermissions这个方法:

public static void requestPermissions(final @NonNull Activity activity,
        final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
    if (Build.VERSION.SDK_INT >= 23) {
        if (activity instanceof RequestPermissionsRequestCodeValidator) {
            ((RequestPermissionsRequestCodeValidator) activity)
                    .validateRequestPermissionsRequestCode(requestCode);
        }
        activity.requestPermissions(permissions, requestCode);
    } else if (activity instanceof OnRequestPermissionsResultCallback) {
        Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                final int[] grantResults = new int[permissions.length];

                PackageManager packageManager = activity.getPackageManager();
                String packageName = activity.getPackageName();

                final int permissionCount = permissions.length;
                for (int i = 0; i < permissionCount; i++) {
                    grantResults[i] = packageManager.checkPermission(
                            permissions[i], packageName);
                }

                ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
                        requestCode, permissions, grantResults);
            }
        });
    }
}
  • 系统版本大于等于Android 6.0,请求权限时,先校验requestCode是否有效,再去调用activity的requestPermissions申请权限。

  • 系统小于Android 6.0,请求权限时,在主线程检查AndroidManifest.xml文件中是否注册权限,并将结果回调。

我们把activity.requestPermissions()放在这,等会再看他。查源码我们知道FragmentActivity实现了这两个接口:

ActivityCompat.OnRequestPermissionsResultCallback
ActivityCompat.RequestPermissionsRequestCodeValidator

public class FragmentActivity extends BaseFragmentActivityApi16 implements
        ActivityCompat.OnRequestPermissionsResultCallback,
        ActivityCompat.RequestPermissionsRequestCodeValidator {

   .......省略


    @Override
    public final void validateRequestPermissionsRequestCode(int requestCode) {
        // We use 16 bits of the request code to encode the fragment id when
        // requesting permissions from a fragment. Hence, requestPermissions()
        // should validate the code against that but we cannot override it as
        // we can not then call super and also the ActivityCompat would call
        // back to this override. To handle this we use dependency inversion
        // where we are the validator of request codes when requesting
        // permissions in ActivityCompat.
        if (!mRequestedPermissionsFromFragment
                && requestCode != -1) {
            checkForValidRequestCode(requestCode);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        int index = (requestCode >> 16) & 0xffff;
        if (index != 0) {
            index--;

            String who = mPendingFragmentActivityResults.get(index);
            mPendingFragmentActivityResults.remove(index);
            if (who == null) {
                Log.w(TAG, "Activity result delivered for unknown Fragment.");
                return;
            }
            Fragment frag = mFragments.findFragmentByWho(who);
            if (frag == null) {
                Log.w(TAG, "Activity result no fragment exists for who: " + who);
            } else {
                frag.onRequestPermissionsResult(requestCode & 0xffff, permissions, grantResults);
            }
        }
    }  
    .......省略
}

BaseFragmentActivityApi14.class中:

/**
 * Checks whether the given request code is a valid code by masking it with 0xffff0000. Throws
 * an {@link IllegalArgumentException} if the code is not valid.
 */
static void checkForValidRequestCode(int requestCode) {
    if ((requestCode & 0xffff0000) != 0) {
        throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
    }
}

requestCode是int数据类型,4个字节,按2进制是32位数据,从上面可以知道它的取值是有范围的,只用到了低16位,0x00000000 ~ 0x0000ffff0 ~ 65535,那系统保留高16位有什么用呢?我们看到上面FragmentActivityonRequestPermissionsResult中,先查找是否是fragment请求的权限,然后直接&0xffff,也就是取requestCode得低16位,并回调权限结果在fragment中。

接下来我们看在fragment中请求权限:

//fragment中申请权限
framgent.requestPermissions(permissions, requestCode);

//fragment权限授权结果回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

framgentrequestPermissions方法:

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    if (mHost == null) {
        throw new IllegalStateException("Fragment " + this + " not attached to Activity");
    }
    mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
}

我们mHost即是fragment的宿主activity,在 FragmentActivityonRequestPermissionsFromFragment

    @Override
    public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
            @NonNull String[] permissions, int requestCode) {
        FragmentActivity.this.requestPermissionsFromFragment(fragment, permissions,
                requestCode);
    }


    /**
     * Called by Fragment.requestPermissions() to implement its behavior.
     */
    void requestPermissionsFromFragment(Fragment fragment, String[] permissions,
            int requestCode) {
        if (requestCode == -1) {
            ActivityCompat.requestPermissions(this, permissions, requestCode);
            return;
        }
        checkForValidRequestCode(requestCode);
        try {
            mRequestedPermissionsFromFragment = true;
            int requestIndex = allocateRequestIndex(fragment);
            ActivityCompat.requestPermissions(this, permissions,
                    ((requestIndex + 1) << 16) + (requestCode & 0xffff));
        } finally {
            mRequestedPermissionsFromFragment = false;
        }
    }
  • requestCode = -1时,直接调用ActivityCompat.requestPermissions
  • requestCode != -1时,先校验requestCode是否有效,即 0 ~ 65535。然后获取一个分配请求索引(范围0 ~ 0xffff - 10 ~ 65534),索引加1之后左移16位,在和requestCode的低16组成新的requestCode
    0xaaaabbbb:高16位aaaa为索引加1之后左移16位,低16位bbbbfragmentrequestCode, 这就和之前在fragment中回调权限结果时requestCode一致了。

最后都会调用ActivityCompat.requestPermissions,就回到了前面,既而activity.requestPermissions(),再来看AcitvityrequestPermissions

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    if (requestCode < 0) {
        throw new IllegalArgumentException("requestCode should be >= 0");
    }
    if (mHasCurrentPermissionsRequest) {
        Log.w(TAG, "Can reqeust only one set of permissions at a time");
        // Dispatch the callback with empty arrays which means a cancellation.
        onRequestPermissionsResult(requestCode, new String[0], new int[0]);
        return;
    }
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
    mHasCurrentPermissionsRequest = true;
}
  • 通过startActivityForResult打开系统授权权限界面,注意字符串常量REQUEST_PERMISSIONS_WHO_PREFIX
  • 不可短时间内重复执行同一请求权限代码,否则第一次之后的直接给定默认值结果,若结果grantResults会错误影响授权。

然而在 ActivityonRequestPermissionsFromFragment中,这与FragmentActivity中的不一样

    @Override
    public void onRequestPermissionsFromFragment(Fragment fragment, String[] permissions,
            int requestCode) {
        String who = REQUEST_PERMISSIONS_WHO_PREFIX + fragment.mWho;
        Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
        startActivityForResult(who, intent, requestCode, null);
    }

最终会通过startActivityForResult打开系统权限授权界面。注意这个who,系统权限授权界面回调结果时,就是根据这个who把结果分配给指定的fragment,即结果回调到fragment的onRequestPermissionsResult。 fragment请求权限时,requestCode取值范围没有限制.

既然是通过startActivityForResult打开系统授权权限界面,那我们能在activity的onActivityResult(int requestCode, int resultCode, Intent data)中拿到授权结果吗?答案是不能,让我们一起来看为什么。当结果回传来时,都会调用ActivitydispatchActivityResult分配结果。

void dispatchActivityResult(String who, int requestCode,
    int resultCode, Intent data) {
    if (false) Log.v(
        TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
        + ", resCode=" + resultCode + ", data=" + data);
    mFragments.noteStateNotSaved();
    if (who == null) {
        onActivityResult(requestCode, resultCode, data);
    } else if (who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)) {
        who = who.substring(REQUEST_PERMISSIONS_WHO_PREFIX.length());
        if (TextUtils.isEmpty(who)) {
            dispatchRequestPermissionsResult(requestCode, data);
        } else {
            Fragment frag = mFragments.findFragmentByWho(who);
            if (frag != null) {
                dispatchRequestPermissionsResultToFragment(requestCode, data, frag);
            }
        }
    } else if (who.startsWith("@android:view:")) {
        ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
                getActivityToken());
        for (ViewRootImpl viewRoot : views) {
            if (viewRoot.getView() != null
                    && viewRoot.getView().dispatchActivityResult(
                            who, requestCode, resultCode, data)) {
                return;
            }
        }
    } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) {
        Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null;
        getAutofillManager().onAuthenticationResult(requestCode, resultData);
    } else {
        Fragment frag = mFragments.findFragmentByWho(who);
        if (frag != null) {
            frag.onActivityResult(requestCode, resultCode, data);
        }
    }
}

这时字符串常量REQUEST_PERMISSIONS_WHO_PREFIX就派上用场了。如果who不为空,说明activity是Activity,不是FragmentActivity,直接结果回调在fragment中。如果为空,actvity不一定为FragmentActivity,结果直接进入Activity的
dispatchRequestPermissionsResult(requestCode, data);

private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
    mHasCurrentPermissionsRequest = false;
    // If the package installer crashed we may have not data - best effort.
    String[] permissions = (data != null) ? data.getStringArrayExtra(
            PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
    final int[] grantResults = (data != null) ? data.getIntArrayExtra(
            PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
    onRequestPermissionsResult(requestCode, permissions, grantResults);
}

结果进而进入回调activity的onRequestPermissionsResult,若activity未处理,且activit是FragmentActivity,则通过requestCode右移16位,进而传递结果到fragment的onRequestPermissionsResult(),完成整个权限请求流程。

三、申请权限引起的生命周期变化

由上面知道最终通过startActivityForResult来打开系统权限授权界面,我们在BaseActivity中相关方法中打印日志:

@Override
public void onResume() {
    super.onResume();
    Log.i(TAG, "onResume: ");
}

@Override
public void onPause() {
    super.onPause();
    Log.i(TAG, "onPause: ");
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Log.i(TAG, "onSaveInstanceState: ");
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    Log.i(TAG, "onRequestPermissionsResult: ");
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

然后请求权限,我们观察到:

《Android申请权限》

权限结果回调,发生在onPause、onSaveInstanceState之后,onResume之前,即页面未处于前台显示。

注意此时fragment事务提交时会状态丢失,如果执行ft.commit(),会报错。

如此时不能DialogFragment.show(),因为DialogFragment.show()是ft.commit(),这个不允许状态丢失。

public void show(FragmentManager manager, String tag) {
    mDismissed = false;
    mShownByMe = true;
    FragmentTransaction ft = manager.beginTransaction();
    ft.add(this, tag);
    ft.commit();
}

四、申请权限封装

a、BaseActivity:

public class BaseActivity extends AppCompatActivity {
    /**
     * 请求权限
     */
    public void requestDangerousPermissions(String[] permissions, int requestCode) {
        if (checkDangerousPermissions(permissions)){
            handlePermissionResult(requestCode, true);
            return;
        }
        ActivityCompat.requestPermissions(this, permissions, requestCode);
    }

    /**
     * 检查是否已被授权危险权限
     * @param permissions
     * @return
     */
    public boolean checkDangerousPermissions(String[] permissions) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true;
        }
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED ||
                    ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
                return false;
            }
        }
        return true;
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        boolean granted = true;
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                granted = false;
            }
        }
        boolean finish = handlePermissionResult(requestCode, granted);
        if (!finish){
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    /**
     * 处理请求危险权限的结果
     * @return
     */
    public boolean handlePermissionResult(int requestCode, boolean granted) {
        return false;
    }
}

b、BaseFragment:

public class BaseFragment extends Fragment {
    /**
     * 请求权限
     *
     * @param permissions
     * @param requestCode
     */
    public void requestDangerousPermissions(String[] permissions, int requestCode) {
        if (checkDangerousPermissions(permissions)){
            handlePermissionResult(requestCode, true);
            return;
        }
        requestPermissions(permissions, requestCode);
    }

    /**
     * 检查是否已被授权危险权限
     * @param permissions
     * @return
     */
    public boolean checkDangerousPermissions(String[] permissions) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true;
        }
        if (getActivity() == null){
            return false;
        }
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(getActivity(), permission) != PackageManager.PERMISSION_GRANTED ||
                    ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permission)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        boolean isGranted = true;
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                isGranted = false;
            }
        }

        if (!handlePermissionResult(requestCode, isGranted)){
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    /**
     * 处理请求危险权限的结果
     *
     * @param requestCode
     * @param isGranted 是否允许
     * @return
     */
    public boolean handlePermissionResult(int requestCode, boolean isGranted) {
        return false;
    }
}

c、使用,如打电话:

private void attemptToCall(String phone) {
    if (!isIntentExisting(getContext(), Intent.ACTION_DIAL)) {
        Toast.makeText(getContext(), "该设备不能打电话", Toast.LENGTH_SHORT).show();
        return;
    }
    mPhone = phone;
    requestDangerousPermissions(callPermission, 88);
}

@Override
public boolean handlePermissionResult(int requestCode, boolean isGranted) {
    if (requestCode == 88){
        final String phone = mPhone;
        getView().post(new Runnable() {
            @Override
            public void run() {
                call(phone);
            }
        });
        mPhone = null;
        return true;
    }

    return super.handlePermissionResult(requestCode, isGranted);
}

private void call(String phone) {
    Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phone));
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

//判断电话应用是否存在
public boolean isIntentExisting(Context context, String action) {
    final PackageManager packageManager = context.getPackageManager();
    final Intent intent = new Intent(action);
    List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
    if (resolveInfo.size() > 0) {
        return true;
    }
    return false;
}

五、总结:

a、当系统小于Android 6.0,只需要在AndroidManifest.xml注册权限即可使用(不包括某些手机厂商自定义的权限管理)。当系统大于等于Android 6.0时,既要在AndroidManifest.xml注册权限,又要去动态申请权限。

b、申请权限时,最终通过startActivityForResult来打开系统权限授权界面,权限结果回调在onRequestPermissionsResult中,不会在onActivityResult中出现。

c、requestCode取值范围:

当activity为FragmentActivity时:

  • 在activity中请求权限时,requestCode有取值范围,为0 ~ 65535,权限回调结果在activity中。
  • 在fragment中请求权限时,requestCode有取值范围,为0 ~ 65535,权限回调结果先传递到activity中,若activity未拦截,再传递到fragment中。在activity中拦截时,可requestCode & 0xffff得到正确的requestCode。

当activity为Activity时:

  • 在activity中请求权限时,requestCode取值范围无要求,权限结果回调在activity中。
  • 在fragment中请求权限时,requestCode取值范围无要求,权限结果直接回调在fragment中。在activity中不可拦截。

d、不可短时间内重复执行同一请求权限代码,否则第一次之后的直接给定默认值结果,若结果grantResults会错误影响授权

e、权限结果回调,发生在onPause、onSaveInstanceState之后,onResume之前,即页面未处于前台显示。

备注:

本文基于Android API 26

本文测试手机华为P20 Pro

Git示例代码:https://github.com/jinxiyang/RequestPermissions.git

    原文作者:挂云帆love
    原文地址: https://blog.csdn.net/guayunfanlove/article/details/80106241
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞