PKMS

PKMS源码解析

PKMS全称PackageManagerService,是Android核心服务之一,主要负责功能如下:

  1. 解析AndroidManifest.xml清单文件,解析清单文件中的所有节点信息

  2. 扫描本地文件,主要针对apk,主要是系统应用、本地安装应用等。

  3. 管理本地apk,主要包括安装、删除等等

  4. 管理设备上安装的所有应用程序,并在系统启动时加载应用程序

  5. 根据请求的Intent匹配到对应的Activity、Provider、Service,提供包含包名和Component的信息对象

  6. 调用需要权限的系统函数时,检查程序是否具备相应权限从而保证系统安全

  7. 提供应用程序的安装、卸载的接口

动态权限申请概念

动态申请权限的权限组:

Google在 Android 6.0 开始引入了权限申请机制,将所有权限分成了正常权限和危险权限。
注意:App每次在使用危险权限时需要动态的申请并得到用户的授权才能使用。
权限的分类:
系统权限分为两类:正常权限 和 危险权限。
正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
危险权限会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。
那么,那些是危险权限呢,为什么是危险权限呢? 要和 说清楚

    <!-- 权限组:CALENDAR == 日历读取的权限申请 -->
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    <uses-permission android:name="android.permission.WRITE_CALENDAR" />

    <!-- 权限组:CAMERA == 相机打开的权限申请 -->
    <uses-permission android:name="android.permission.CAMERA" />

    <!-- 权限组:CONTACTS == 联系人通讯录信息获取/写入的权限申请 -->
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />

    <!-- 权限组:LOCATION == 位置相关的权限申请 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <!-- 权限组:PHONE == 拨号相关的权限申请 -->
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    <!-- 权限组:SMS == 短信相关的权限申请 -->
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />

    <!-- 权限组:STORAGE == 读取存储相关的权限申请 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
核心函数介绍
ContextCompat.checkSelfPermission
检查应用是否具有某个危险权限。如果应用具有此权限,方法将返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。
如果应用不具有此权限,方法将返回 PackageManager.PERMISSION_DENIED,且应用必须明确向用户要求权限。
ActivityCompat.requestPermissions
应用可以通过这个方法动态申请权限,调用后会弹出一个对话框提示用户授权所申请的权限。
ActivityCompat.shouldShowRequestPermissionRationale
如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。如果用户在过去拒绝了权限请求,
并在权限请求系统对话框中选择了 Don't ask again 选项,此方法将返回 false。如果设备规范禁止应用具有该权限,此方法也会返回 false。
onRequestPermissionsResult
当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,向其传递用户响应,处理对应的场景。
动态权限实例演示

现在演示,上面 “核心函数”实例:

<!-- 第一步:在AndroidManifest.xml中添加所需权限。 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        requestPermission();
    }

    // 第二步:封装了一个requestPermission方法来动态检查和申请权限
    private void requestPermission() {

        Log.i(TAG,"requestPermission");

        // Here, thisActivity is the current activity
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            Log.i(TAG,"checkSelfPermission");

            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {
                Log.i(TAG,"shouldShowRequestPermissionRationale");

                // Show an expanation to the user *asynchronously* -- don't block
                // this thread waiting for the user's response! After the user
                // sees the explanation, try again to request the permission.

                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_CONTACTS},
                        MY_PERMISSIONS_REQUEST_READ_CONTACTS);

            } else {
                Log.i(TAG,"requestPermissions");
                // No explanation needed, we can request the permission.
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_CONTACTS},
                        MY_PERMISSIONS_REQUEST_READ_CONTACTS);
                // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
                // app-defined int constant. The callback method gets the
                // result of the request.
            }
        }
    }
    // 第三步:重写onRequestPermissionsResult方法根据用户的不同选择做出响应。
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.i(TAG,"onRequestPermissionsResult granted");
                    // permission was granted, yay! Do the
                    // contacts-related task you need to do.

                } else {
                    Log.i(TAG,"onRequestPermissionsResult denied");
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                    showWaringDialog();
                }
                return;
            }

            // other 'case' lines to check for other
            // permissions this app might request
        }
    }

    // 如果点击 拒绝,就会弹出这个
    private void showWaringDialog() {
        new AlertDialog.Builder(this)
                .setTitle("警告!")
                .setMessage("请前往设置->应用->PermissionDemo->权限中打开相关权限,否则功能无法正常运行!")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 一般情况下如果用户不授权的话,功能是无法运行的,做退出处理
                        finish();
                    }
                }).show();
    }

运行结果:
在这里插入图片描述

2.requestPermissions源码流程阅读-15步
1.流程图介绍

总结上面的几个 ”核心函数“

检查权限
checkSelfPermission(@NonNull String permission) 

申请权限
requestPermissions(@NonNull String[] permissions, int requestCode)  

处理结果回调
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 

是否需要显示UI界面提示用户为什么需要这个权限
shouldShowRequestPermissionRationale(@NonNull String permission)    

权限申请源码流程总结:
第一步:MainActivity 调用 requestPermissions 进行动态权限申请;
第二步:requestPermissions函数通过隐士意图,激活PackageInstaller的GrantPermissionsActivity界面,让用户选择是否授权;
第三步:经过PKMS把相关信息传递给PermissionManagerService处理;
第四步:PermissionManagerService处理结束后回调给---->PKMS中的onPermissionGranted方法把处理结果返回;
第五步:PKMS通知过程中权限变化,并调用writeRuntimePermissionsForUserLPr函数让PackageManager的settings记录下相关授权信息;
权限申请整体流程图:
在这里插入图片描述

2.MainActiivty:
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS},
                                    MY_PERMISSIONS_REQUEST_READ_CONTACTS);
3.ActivityCompat.requestPermissions:
public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
        if (sDelegate != null
                && sDelegate.requestPermissions(activity, permissions, requestCode)) {
            // Delegate has handled the permission request.
            return;
        }

        if (Build.VERSION.SDK_INT >= 23) {
            if (activity instanceof RequestPermissionsRequestCodeValidator) {
                ((RequestPermissionsRequestCodeValidator) activity)
                        .validateRequestPermissionsRequestCode(requestCode);
            }
            // 重点是看这句代码,就是权限申请, 下面代码就会分析这句代码
            activity.requestPermissions(permissions, requestCode);
        } else if (activity instanceof OnRequestPermissionsResultCallback) {
           ....
        }
    }
4.Activity.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;
    }
    // 【注意】 关注 buildRequestPermissionsIntent,下面会分析这个函数
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
    mHasCurrentPermissionsRequest = true;
}
5.PackageManager.buildRequestPermissionsIntent:

位置:frameworks/base/core/java/android/content/pm/PackageManager.java

    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;
    }

总结:这个buildRequestPermissionsIntent函数的目的,就是去 激活某个Activity。

慢慢来分析:
注意:灵感来自与此(既然buildRequestPermissionsIntent函数是为了拼接一个Intent,那么想都不用想,一定是想搞隐士意图来激活某个Activity,这个灵感非常关键, 以后遇到Intent的封装,就应该想到是为了什么了吧,是不是为了激活某个Activity)
public static final String ACTION_REQUEST_PERMISSIONS = “android.content.pm.action.REQUEST_PERMISSIONS”;
那么我们就根据 ”android.content.pm.action.REQUEST_PERMISSIONS“ 表示动作来找到 需要激活的某个Activity不就行了, 是不是很简单
在这里插入图片描述

6.apps/PackageInstaller/AndroidManfiest.xml
          注意:下面我们就分析 GrantPermissionsActivity
        <activity android:name=".permission.ui.GrantPermissionsActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true"
                android:theme="@style/GrantPermissions"
                android:visibleToInstantApps="true">
            
                <!-- 那么我们就根据  ”android.content.pm.action.REQUEST_PERMISSIONS“ 表示动作来找到 
                     需要激活的某个Activity不就行了,  是不是很简单 -->
                <intent-filter android:priority="1">
                    <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
        </activity>

打开GrantPermissionsActivity
在这里插入图片描述

7.GrantPermissionsActivity.onPermissionGrantResult函数:

注意,原来GrantPermissionsActivity也就是我们常见的权限申请界面,用户可以根据提示选择是否授权给应用相应的权限。用户操作后的结果会通过回调GrantPermissionsActivity的onPermissionGrantResult方法返回。在onPermissionGrantResult方法中会根据返回结果去决定是走授予权限还是撤销权限流程,然后会更新授权结果,最后返回结果并结束自己:
在这里插入图片描述

    @Override
    public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
        GroupState groupState = mRequestGrantPermissionGroups.get(name);
        if (groupState.mGroup != null) {
            if (granted) {
                // 【注意】重点是这个 授予权限, 下面会分析这个函数grantRuntimePermissions
                // 授予权限
                groupState.mGroup.grantRuntimePermissions(doNotAskAgain,
                        groupState.affectedPermissions);
                groupState.mState = GroupState.STATE_ALLOWED;
            } else {
                // 撤销权限
                groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
                        groupState.affectedPermissions);
                groupState.mState = GroupState.STATE_DENIED;

                int numRequestedPermissions = mRequestedPermissions.length;
                for (int i = 0; i < numRequestedPermissions; i++) {
                    String permission = mRequestedPermissions[i];

                    if (groupState.mGroup.hasPermission(permission)) {
                        EventLogger.logPermissionDenied(this, permission,
                                mAppPermissions.getPackageInfo().packageName);
                    }
                }
            }
            // 更新授权结果
            updateGrantResults(groupState.mGroup);
        }
        if (!showNextPermissionGroupGrantRequest()) {
            // 返回授权结果并结束自己
            setResultAndFinish();
        }
    }
8.AppPermissionGroup.grantRuntimePermissions

接下来继续跟踪AppPermissionGroup.grantRuntimePermissions方法分析授权流程。
AppPermissionGroup.grantRuntimePermissions方法中会判断targetSdkVersion是否大于LOLLIPOP_MR1(22),如果大于则做动态权限申请处理
位置: packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java
在这里插入图片描述

    public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
        final int uid = mPackageInfo.applicationInfo.uid;

        // We toggle permissions only to apps that support runtime
        // permissions, otherwise we toggle the app op corresponding
        // to the permission if the permission is granted to the app.
        for (Permission permission : mPermissions.values()) {
            ...
            if (mAppSupportsRuntimePermissions) {
                // 【  注意】 在Android 5.1后,就需要支持动态申请权限啦
                // LOLLIPOP_MR1之后版本,支持动态权限申请
                // Do not touch permissions fixed by the system.
                if (permission.isSystemFixed()) {
                    return false;
                }

                // Ensure the permission app op enabled before the permission grant.
                if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
                    permission.setAppOpAllowed(true);
                    mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                }

                // Grant the permission if needed.
                if (!permission.isGranted()) {
                    permission.setGranted(true);
                    
                    // 【注意】 这里很关键, 通过 mPackageManager.grantRuntimePermission 跨进程到 PKMS
                    // 下面我们就分析这个操作了哦,注意哦
                    // 熟悉Android源码的同学都知道XXXManager只是一个辅助类,其真正提供服务的都是XXXManagerService,
                    // 所以直接跳转PackageManagerService中的grantRuntimePermission方法。
                    mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                            permission.getName(), mUserHandle);
                }

                // Update the permission flags.
                if (!fixedByTheUser) {
                    // Now the apps can ask for the permission as the user
                    // no longer has it fixed in a denied state.
                    if (permission.isUserFixed() || permission.isUserSet()) {
                        permission.setUserFixed(false);
                        permission.setUserSet(false);
                        mPackageManager.updatePermissionFlags(permission.getName(),
                                mPackageInfo.packageName,
                                PackageManager.FLAG_PERMISSION_USER_FIXED
                                        | PackageManager.FLAG_PERMISSION_USER_SET,
                                0, mUserHandle);
                    }
                }
            } else {
                // LOLLIPOP_MR1之前版本,不支持动态权限申请
                // Legacy apps cannot have a not granted permission but just in case.
                ....
            }
        }

        return true;
    }

终于到了 PKMS了。

9.PKMS.grantRuntimePermission

位置:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    @Override
    public void grantRuntimePermission(String packageName, String permName, final int userId) {
        mPermissionManager.grantRuntimePermission(permName, packageName, false /*overridePolicy*/,
                getCallingUid(), userId, mPermissionCallback);
    }
10.PermissionManagerService.grantRuntimePermission

PermissionManagerInternal---- 接口到实现 —PermissionManagerService.grantRuntimePermission:
D:\ANDROID\android9\frameworks\base\services\core\java\com\android\server\pm\permission\PermissionManagerService.java

    private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
            int callingUid, final int userId, PermissionCallback callback) {
        // 检查用户是否存在
        if (!mUserManagerInt.exists(userId)) {
            Log.e(TAG, "No such user:" + userId);
            return;
        }
        // 检查PackageInstaller是否有动态权限授权权限
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
                "grantRuntimePermission");
        ...
        
        // 【  注意】 下面会分析这个回调
        // 回调PermissionCallback的onPermissionGranted方法通知授予权限
        if (callback != null) {
            callback.onPermissionGranted(uid, userId);
        }
        ...
    }
11.PKMS.onPermissionGranted(回调回PKMS)

又回到 PKMS,位置: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

        // 回调的是PackageManagerService中的PermissionCallback,在其实现的onPermissionGranted方法中会去通知观察者权            
        // 限发生变化,并调用PackageManager的Settings记录动态权限授权状态。
        @Override
        public void onPermissionGranted(int uid, int userId) {
            mOnPermissionChangeListeners.onPermissionsChanged(uid);

            // Not critical; if this is lost, the application has to request again.
            synchronized (mPackages) {
                // [  注意] 下面会分钟分析这个函数
                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
            }
        }
12.Settings.writePermissionsSync

调用流程: mSettings.writeRuntimePermissionsForUserLPr —> writePermissionsForUserSyncLPr ----> writePermissionsSync:
位置: frameworks/base/services/core/java/com/android/server/pm/Settings.java

注意:最终会通过这个类将相应的权限写入data/system/users/0/runtime-permissions.xml这个文件中,供其他接口调用
在这里插入图片描述

        //   注意: 
        // writePermissionsSync方法来完成最后的记录工作。
        // writePermissionsSync方法的代码很长,但是逻辑很清晰,就是先查询与应用相关的所有权限状态,
        // 然后创建 runtime-permissions.xml 文件把这些信息记录进去。
        private void writePermissionsSync(int userId) {
            //  动态权限文件(runtime-permissions.xml)
            AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId),
                    "package-perms-" + userId);

            ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>();
            ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>();

            synchronized (mPersistenceLock) {
                mWriteScheduled.delete(userId);
                // 获得Package权限状态
                final int packageCount = mPackages.size();
                for (int i = 0; i < packageCount; i++) {
                    String packageName = mPackages.keyAt(i);
                    PackageSetting packageSetting = mPackages.valueAt(i);
                    if (packageSetting.sharedUser == null) {
                        PermissionsState permissionsState = packageSetting.getPermissionsState();
                        List<PermissionState> permissionsStates = permissionsState
                                .getRuntimePermissionStates(userId);
                        if (!permissionsStates.isEmpty()) {
                            permissionsForPackage.put(packageName, permissionsStates);
                        }
                    }
                }
                // 获得SharedUser权限状态
                final int sharedUserCount = mSharedUsers.size();
                for (int i = 0; i < sharedUserCount; i++) {
                    String sharedUserName = mSharedUsers.keyAt(i);
                    SharedUserSetting sharedUser = mSharedUsers.valueAt(i);
                    PermissionsState permissionsState = sharedUser.getPermissionsState();
                    List<PermissionState> permissionsStates = permissionsState
                            .getRuntimePermissionStates(userId);
                    if (!permissionsStates.isEmpty()) {
                        permissionsForSharedUser.put(sharedUserName, permissionsStates);
                    }
                }
            }

            FileOutputStream out = null;
            try {
                out = destination.startWrite();
                // 创建xml文件用于记录权限状态
                XmlSerializer serializer = Xml.newSerializer();
                serializer.setOutput(out, StandardCharsets.UTF_8.name());
                serializer.setFeature(
                        "http://xmlpull.org/v1/doc/features.html#indent-output", true);
                serializer.startDocument(null, true);

                serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);

                String fingerprint = mFingerprints.get(userId);
                if (fingerprint != null) {
                    serializer.attribute(null, ATTR_FINGERPRINT, fingerprint);
                }
                // 写入Package权限状态
                final int packageCount = permissionsForPackage.size();
                for (int i = 0; i < packageCount; i++) {
                    String packageName = permissionsForPackage.keyAt(i);
                    List<PermissionState> permissionStates = permissionsForPackage.valueAt(i);
                    serializer.startTag(null, TAG_PACKAGE);
                    serializer.attribute(null, ATTR_NAME, packageName);
                    writePermissions(serializer, permissionStates);
                    serializer.endTag(null, TAG_PACKAGE);
                }
                 // 写入SharedUser权限状态
                final int sharedUserCount = permissionsForSharedUser.size();
                for (int i = 0; i < sharedUserCount; i++) {
                    String packageName = permissionsForSharedUser.keyAt(i);
                    List<PermissionState> permissionStates = permissionsForSharedUser.valueAt(i);
                    serializer.startTag(null, TAG_SHARED_USER);
                    serializer.attribute(null, ATTR_NAME, packageName);
                    writePermissions(serializer, permissionStates);
                    serializer.endTag(null, TAG_SHARED_USER);
                }

                serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
                // 写入结束
                serializer.endDocument();
                destination.finishWrite(out);

                if (Build.FINGERPRINT.equals(fingerprint)) {
                    mDefaultPermissionsGranted.put(userId, true);
                }
            // Any error while writing is fatal.
            } catch (Throwable t) {
                Slog.wtf(PackageManagerService.TAG,
                        "Failed to write settings, restoring backup", t);
                destination.failWrite(out);
            } finally {
                IoUtils.closeQuietly(out);
            }
        }
13.安装后查阅权限文件

如果安装了应用,并且授权了,
然后同步
在这里插入图片描述

是可以在 runtime-permissions.xml 中查询到 com.xxx.permissionstudy
在这里插入图片描述

14.卸载后查阅权限文件

如果卸载了应用
然后同步
在这里插入图片描述

就查询不到 com.xxx.permissionstudy 了,因为卸载时,会删除com.xxx.permissionstudy的信息

15.权限申请源码流程总结:

第一步:MainActivity 调用 requestPermissions 进行动态权限申请;
第二步:requestPermissions函数通过隐士意图,激活PackageInstaller的GrantPermissionsActivity界面,让用户选择是否授权;
第三步:经过PKMS把相关信息传递给PermissionManagerService处理;
第四步:PermissionManagerService处理结束后回调给---->PKMS中的onPermissionGranted方法把处理结果返回;
第五步:PKMS通知过程中权限变化,并调用writeRuntimePermissionsForUserLPr函数让PackageManager的settings记录下相关授权信息;

3.无侵入式权限框架

在这里插入图片描述
代码点击这里

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值