Android 权限(二): 动态权限讲解

1. 前言

        继上一篇文章说到Android权限汇总, 请移步笔者的文章Android 权限(一):权限大全_broadview_java的博客-CSDN博客_android 仅使用中允许权限

先要理清楚权限分类和定义,本篇文章继续说一下动态权限的申请和框架层的实现流程, 以及如何实现赋予系统应用默认的权限的需求.

2. 动态权限申请

        在Android6.0之后,  在首次打开app时的时候,比如微信,钉钉等软件时,会有提示

1)有读取设备上的照片及文字

2)获取手机号,IMEI,IMSI 

3)获取设备的定位信息 

等权限申请的对话框, 需要我们通过手动点击确认.

我们来一步一步分析对话框显示的原理和流程

2.1 检查权限

我们来通过一个Demo演示一下, Activity中的代码如下

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_TEST = 1024;

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

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if(this.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "请申请权限", Toast.LENGTH_LONG).show();
                //申请权限的代码
               // ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_TEST);
                requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_TEST);
            }
        }

    }
    
    //回调方法
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        android.util.Log.e("test", "====int requestCode==== :" + requestCode
        + " 具体的 permission :" + permissions[0] + "\n"
                + "== grantResults数组长度 : " + grantResults.length + "====grantResults对应的int 值:" + grantResults[0]);
    }
}

代码讲解:

checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) 这个方法是检测是否有具有READ_EXTERNAL_STORAGE 此权限, 返回值 :

    /**
     * Permission check result: this is returned by {@link #checkPermission}
     * if the permission has been granted to the given package.
     */
    public static final int PERMISSION_GRANTED = 0;

    /**
     * Permission check result: this is returned by {@link #checkPermission}
     * if the permission has not been granted to the given package.
     */
    public static final int PERMISSION_DENIED = -1;

0:表示已获取该权限,  -1:表示未赋予该权限

我是用Android 10源码追溯的流程,我们来看看此代码的调用流程:

1. frameworks/base/core/java/android/content/ContextWrapper.java

    @Override
    public int checkSelfPermission(String permission) {
       return mBase.checkSelfPermission(permission);
    }

2.frameworks/base/core/java/android/content/Context.java

    @PackageManager.PermissionResult
    public abstract int checkSelfPermission(@NonNull String permission);

3. 我们继续看看Context 的实现类 ContextImpl.java

frameworks/base/core/java/android/app/ContextImpl.java

    @Override
    public int checkSelfPermission(String permission) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }

        return checkPermission(permission, Process.myPid(), Process.myUid());
    }

4.紧接着跳转到 ActivityManagerService.java中的方法

    @Override
    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            return PackageManager.PERMISSION_DENIED;
        }
        return checkComponentPermission(permission, pid, uid, -1, true);
    }


    public static int checkComponentPermission(String permission, int pid, int uid,
            int owningUid, boolean exported) {
        if (pid == MY_PID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return ActivityManager.checkComponentPermission(permission, uid,
                owningUid, exported);
    }

5.接着继续调用ActivityManager.java文件中的checkComponentPermission方法

public static int checkComponentPermission(String permission, int uid,
            int owningUid, boolean exported) {
        //省略
        ...........
        ...........
        try {
            return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

6.继续调用到 PackageManagerService.java中的checkUidPermission方法

    public int checkUidPermission(String permName, int uid) {
        final CheckPermissionDelegate checkPermissionDelegate;
        synchronized (mPackages) {
            if (mCheckPermissionDelegate == null)  {
                return checkUidPermissionImpl(permName, uid);
            }
            checkPermissionDelegate = mCheckPermissionDelegate;
        }
        return checkPermissionDelegate.checkUidPermission(permName, uid,
                PackageManagerService.this::checkUidPermissionImpl);
    }

    private int checkUidPermissionImpl(String permName, int uid) {
        synchronized (mPackages) {
            final String[] packageNames = getPackagesForUid(uid);
            PackageParser.Package pkg = null;
            final int N = packageNames == null ? 0 : packageNames.length;
            for (int i = 0; pkg == null && i < N; i++) {
                pkg = mPackages.get(packageNames[i]);
            }
            return mPermissionManager.checkUidPermission(permName, pkg, uid, getCallingUid());
        }
    }

7. 接下来继续调用 PermissionManagerService.java去

        public int checkUidPermission(String permName, PackageParser.Package pkg, int uid,
                int callingUid) {
            return PermissionManagerService.this.checkUidPermission(permName, pkg, uid, callingUid);
        }


//这里是最终返回判断值的方法
private int checkUidPermission(String permName, PackageParser.Package pkg, int uid,
            int callingUid) {

        final int callingUserId = UserHandle.getUserId(callingUid);
        final boolean isCallerInstantApp =
                mPackageManagerInt.getInstantAppPackageName(callingUid) != null;
        final boolean isUidInstantApp =
                mPackageManagerInt.getInstantAppPackageName(uid) != null;
        final int userId = UserHandle.getUserId(uid);
        if (!mUserManagerInt.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }

        if (pkg != null) {
            if (pkg.mSharedUserId != null) {
                if (isCallerInstantApp) {
                    return PackageManager.PERMISSION_DENIED;
                }
            } else if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) {
                return PackageManager.PERMISSION_DENIED;
            }
            final PermissionsState permissionsState =
                    ((PackageSetting) pkg.mExtras).getPermissionsState();
            if (permissionsState.hasPermission(permName, userId)) {
                if (isUidInstantApp) {
                    if (mSettings.isPermissionInstant(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                } else {
                    return PackageManager.PERMISSION_GRANTED;
                }
            }
            if (isImpliedPermissionGranted(permissionsState, permName, userId)) {
                return PackageManager.PERMISSION_GRANTED;
            }
        } else {
            ArraySet<String> perms = mSystemPermissions.get(uid);
            if (perms != null) {
                if (perms.contains(permName)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
                if (FULLER_PERMISSION_MAP.containsKey(permName)
                        && perms.contains(FULLER_PERMISSION_MAP.get(permName))) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            }
        }
        return PackageManager.PERMISSION_DENIED;
    }

这里是最终判断返回值的方法,  好了,到这里阶段一就完成了. 如果返回PERMISSION_GRANTED, 那没啥事情了, 如果返回PERMISSION_DENIED, 那你就得去动态申请了, 动态申请权限不多,就是固定的几组(在上一篇文章中以介绍说明).   android系统也为我们设计好了动态申请权限的对话框. 我们来说一下它的显示流程.

2.2 申请权限

申请权限是requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_TEST)方法实现的

1. 跳转到Activity.java中的requestPermissions方法

frameworks/base/core/java/android/app/Activity.java

    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 request 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;
        }
        //创建跳转的目标activity
        Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
        startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
        mHasCurrentPermissionsRequest = true;
    }

2. 注意一下此段代码:buildRequestPermissionsIntent(permissions)

frameworks/base/core/java/android/content/pm/PackageManager.java

    @NonNull
    @UnsupportedAppUsage
    public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
        if (ArrayUtils.isEmpty(permissions)) {
           throw new IllegalArgumentException("permission cannot be null or empty");
        }
        Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
        intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
        intent.setPackage(getPermissionControllerPackageName());
        return intent;
    }
@SystemApi
public static final String ACTION_REQUEST_PERMISSIONS =
        "android.content.pm.action.REQUEST_PERMISSIONS";

通过这个ACTION跳转到对应的activity中,  通过在源码工程下全局搜索 然后找到对应的Activity, 我们发现是GrantPermissionsActivity.java

        <activity android:name="com.android.packageinstaller.permission.ui.GrantPermissionsActivity"
                android:configChanges="keyboardHidden|screenSize"
                android:excludeFromRecents="true"
                android:theme="@style/GrantPermissions"
                android:visibleToInstantApps="true"
                android:inheritShowWhenLocked="true">
            <intent-filter android:priority="1">
                <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

确定是唯一的,出现这个界面对话框时,我们也可以通过adb shell dumpsys window | grep mFocus

命令确定一下Activity找的是不是对的.

知识储备: 在Android(<=9)版本上, 动态申请权限的对话框是集成在 packages/apps/PackageInstaller 模块中, 在Android(>=10)版本上,是在packages/apps/PermissionController中,单独把这块拿出来了

3. 对话框事件处理是在GrantPermissionsViewHandlerImpl.java中,   允许,拒绝, 仅在使用该应用时允许  对应的代码如下:

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.permission_allow_button:
                if (mResultListener != null) {
                    view.performAccessibilityAction(
                            AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
                    mResultListener.onPermissionGrantResult(mGroupName, GRANTED_ALWAYS);
                }
                break;
            case R.id.permission_allow_always_button:
                if (mResultListener != null) {
                    view.performAccessibilityAction(
                            AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
                    mResultListener.onPermissionGrantResult(mGroupName, GRANTED_ALWAYS);
                }
                break;
            case R.id.permission_allow_foreground_only_button:
                if (mResultListener != null) {
                    view.performAccessibilityAction(
                            AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
                    mResultListener.onPermissionGrantResult(mGroupName,
                            GRANTED_FOREGROUND_ONLY);
                }
                break;
            case R.id.permission_deny_button:
                if (mResultListener != null) {
                    view.performAccessibilityAction(
                            AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
                    mResultListener.onPermissionGrantResult(mGroupName, DENIED);
                }
                break;
            case R.id.permission_deny_and_dont_ask_again_button:
                if (mResultListener != null) {
                    view.performAccessibilityAction(
                            AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
                    mResultListener.onPermissionGrantResult(mGroupName,
                            DENIED_DO_NOT_ASK_AGAIN);
                }
                break;
            case R.id.permission_more_info_button:
                Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
                intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppPackageName);
                intent.putExtra(Intent.EXTRA_USER, mUserHandle);
                intent.putExtra(ManagePermissionsActivity.EXTRA_ALL_PERMISSIONS, true);
                mActivity.startActivity(intent);
                break;
        }

    }

3. 赋予应用默认运行时权限

        有些用户觉得app要申请权限比较麻烦,就有定制软件默认就赋予某个应用运行时权限,不弹出对话框,我们看看怎么修改呢?

   先来看frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java中的systemReady()方法

        // If we upgraded grant all default permissions before kicking off.
        for (int userId : grantPermissionsUserIds) {
            mDefaultPermissionPolicy.grantDefaultPermissions(userId);
        }

跳转到DefaultPermissionGrantPolicy.java中

    public void grantDefaultPermissions(int userId) {
        grantPermissionsToSysComponentsAndPrivApps(userId);
        //默认赋予应用的运行时权限
        grantDefaultSystemHandlerPermissions(userId);
        grantDefaultPermissionExceptions(userId);
        synchronized (mLock) {
            mDefaultPermissionsGrantedUsers.put(userId, userId);
        }
    }

 如下这些代码,就是默认赋予运行时权限的地方

 private void grantDefaultSystemHandlerPermissions(int userId) {
    
    .........
    .........

  // Downloads provider
        grantSystemFixedPermissionsToSystemPackage(
                getDefaultProviderAuthorityPackage("downloads", userId), userId,
                STORAGE_PERMISSIONS);

        // Downloads UI
        grantSystemFixedPermissionsToSystemPackage(
                getDefaultSystemHandlerActivityPackage(
                        DownloadManager.ACTION_VIEW_DOWNLOADS, userId),
                userId, STORAGE_PERMISSIONS);

        // Storage provider
        grantSystemFixedPermissionsToSystemPackage(
                getDefaultProviderAuthorityPackage("com.android.externalstorage.documents", userId),
                userId, STORAGE_PERMISSIONS);

        // CertInstaller
        grantSystemFixedPermissionsToSystemPackage(
                getDefaultSystemHandlerActivityPackage(Credentials.INSTALL_ACTION, userId), userId,
                STORAGE_PERMISSIONS);

        // Dialer
        if (dialerAppPackageNames == null) {
            String dialerPackage =
                    getDefaultSystemHandlerActivityPackage(Intent.ACTION_DIAL, userId);
            grantDefaultPermissionsToDefaultSystemDialerApp(dialerPackage, userId);
        } else {
            for (String dialerAppPackageName : dialerAppPackageNames) {
                grantDefaultPermissionsToDefaultSystemDialerApp(dialerAppPackageName, userId);
            }
        }

        // Sim call manager
        if (simCallManagerPackageNames != null) {
            for (String simCallManagerPackageName : simCallManagerPackageNames) {
                grantDefaultPermissionsToDefaultSystemSimCallManager(
                        simCallManagerPackageName, userId);
            }
        }

        // Use Open Wifi
        if (useOpenWifiAppPackageNames != null) {
            for (String useOpenWifiPackageName : useOpenWifiAppPackageNames) {
                grantDefaultPermissionsToDefaultSystemUseOpenWifiApp(
                        useOpenWifiPackageName, userId);
            }
        }
    

        // 添加代码,赋予应用默认的运行时权限,不弹出申请对话框.
    grantPermissionsToSystemPackage("com.android.mytestapp", userId, STORAGE_PERMISSIONS);

}

如果你想添加自己的应用默认赋予运行时权限的话,可以这样子修改,如下:

添加包名 和 运行时权限数组

grantPermissionsToSystemPackage("com.android.mytestapp", userId, STORAGE_PERMISSIONS);

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值