我只用了裁切图片那一点,其他的没试过。
最近把APP的TargetSdk从21提高至25,梳理了一下,APP中有多个地方用到了动态权限。
所以,需要把动态权限的申请与处理统一设计。
动态权限的基础知识,不再累述,请自行查询资料。
安卓端动态权限请求与处理的过程,通常如下:
总结安卓动态权限时,在网上也看了不少资料,反应最多的问题就是:
无法绝对检测权限的状态(授予、拒绝、禁止(不再提醒))!!!
原因如:各大手机厂商的定制或自身的权限体系与谷歌不一致、checkSelfPermission无效、shouldShowRequestPermissionRationale无效等等,对应的解释也是千奇百怪。
与其那么纠结,不如自己动手实验一下,看一看动态权限的判断是否如传说中的那么艰难。
用户权限状态检测的 主要方法有3种,谷歌官方大概释义如下:
//方法1:检测是否授予某权限,如果是返回0,反之,返回-1
ContextCompat.checkSelfPermission(activity, permission);
//方法2:是否应该向用户显示请求权限的原因说明(true/false)
ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
//方法3:判断程序的某操作是被允许的还是拒绝的还是忽略的
AppOpsManagerCompat.noteProxyOp(activity, op, activity.getPackageName());
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
我们先来看看将normal 和 dangerous的权限 同时在6.0+手机上的效果吧:
我们准备测试的权限如下,共6个:
前面的4个为危险权限,分为3类(SD卡读写、相机、短信);
后面的2个为标准权限,分为2类(音频调节、桌面快捷方式);
三星6.0.1.jpg
小米7.1.2.jpg
通过上面两图可以看到,申请同样的权限,在不同手机上显示的效果是不同的:
三星只显示了危险的权限,但是位置权限我们没有申请,它也给默认显示了出来;
小米将危险权限和标准权限都显示了出来,不过标准权限默认都显示着禁用的图标,清单中倒是没有位置权限,却多了后台弹出等权限项。
推测:不同厂商对于授权页面都有自己的定制规则。
//动态请求的权限数组
String[] permissions =new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.READ_SMS,
Manifest.permission.INSTALL_SHORTCUT,
Manifest.permission.MODIFY_AUDIO_SETTINGS};
//测试
Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
for (String permission : permissions) {
int isGranted = ContextCompat.checkSelfPermission(activity, permission);
if (isGranted == PackageManager.PERMISSION_GRANTED) {
//已授权
Log.i("checkSelfPermission", PermissionUtils.getInstance().getPermissionName(permission)+ ": 已授权");
}else if(isGranted == PackageManager.PERMISSION_DENIED){
//未授权的
Log.i("checkSelfPermission", PermissionUtils.getInstance().getPermissionName(permission)+ ": 未授权");
}
}
Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
运行两个手机,输出的结果如下:
三星:
02-26 15:52:07.376 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --文件存储: 未授权
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --相机/拍照: 未授权
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --短信: 未授权
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: com.android.launcher.permission.INSTALL_SHORTCUT: 已授权
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: android.permission.MODIFY_AUDIO_SETTINGS: 已授权
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
小米:
02-26 15:58:09.128 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
02-26 15:58:09.131 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --文件存储: 未授权
02-26 15:58:09.131 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --相机/拍照: 未授权
02-26 15:58:09.132 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --短信: 未授权
02-26 15:58:09.132 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: com.android.launcher.permission.INSTALL_SHORTCUT: 已授权
02-26 15:58:09.132 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: android.permission.MODIFY_AUDIO_SETTINGS: 已授权
02-26 15:58:09.132 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
通过上面两图可以看出,两种手机输出的结果都是一样的,也都是正确的。
只是小米的手机显示的状态与输出的结果有些歧义,对于标准权限(音频调节、快捷方式),明明标识着拒绝的图标(红×),输出的结果却是已授权(已授权是正确的结果)。这可能是小米手机本身的小问题吧,这也可能也是迷惑很多开发者的主要原因。
String[] permissions =new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.READ_SMS,
Manifest.permission.INSTALL_SHORTCUT,
Manifest.permission.MODIFY_AUDIO_SETTINGS};
//测试
Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
for (String permission : permissions) {
//拒绝且不再提醒、系统默认禁用的权限、用户未点过拒绝的权限均返回false, 只有用户点击拒绝后,且没有勾选不再提醒返回true
if(!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)){
Log.i("checkSelfPermission @@", PermissionUtils.getInstance().getPermissionName(permission)+ ": false");
}else{
Log.i("checkSelfPermission @@", PermissionUtils.getInstance().getPermissionName(permission)+ ": true");
}
}
Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
运行两个手机,直接运行,默认输出的结果均一致
小米、三星:
02-26 16:02:59.382 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
02-26 16:02:59.383 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --文件存储: false
02-26 16:02:59.384 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --相机/拍照: false
02-26 16:02:59.384 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --短信: false
02-26 16:02:59.384 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: com.android.launcher.permission.INSTALL_SHORTCUT: false
02-26 16:02:59.385 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: android.permission.MODIFY_AUDIO_SETTINGS: false
02-26 16:02:59.385 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
允许SD卡存储、拒绝拍照(未勾选不再提醒)后的输出结果,如下:
小米、三星:
02-26 16:07:09.383 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
02-26 16:07:09.385 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --文件存储: false
02-26 16:07:09.386 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --相机/拍照: true
02-26 16:07:09.387 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --短信: false
02-26 16:07:09.388 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: com.android.launcher.permission.INSTALL_SHORTCUT: false
02-26 16:07:09.390 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: android.permission.MODIFY_AUDIO_SETTINGS: false
02-26 16:07:09.390 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
小米手机测试.png
经过反复测试:
当用户禁用(拒绝且不再提醒)、系统默认禁用的权限、用户未点过拒绝的权限、用户授权的权限均返回false;
只有用户点击拒绝后,且没有勾选不再提醒的权限返回true;
7.测试方法3:判断程序的某操作是被允许的还是拒绝的还是忽略的
AppOpsManagerCompat.noteProxyOp(activity, op, activity.getPackageName());
String[] permissions =new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.READ_SMS,
Manifest.permission.INSTALL_SHORTCUT,
Manifest.permission.MODIFY_AUDIO_SETTINGS};
//测试
Log.i("checkSelfPermission", "***********************"+permissions.length);
for (String permission : permissions) {
String op = AppOpsManagerCompat.permissionToOp(permission);
//判断非空
if (TextUtils.isEmpty(op)) continue;
int result = AppOpsManagerCompat.noteProxyOp(activity, op, activity.getPackageName());
if (result == AppOpsManagerCompat.MODE_IGNORED) {
Log.i("checkSelfPermission", "**" + permission + " false");
} else if(result == AppOpsManagerCompat.MODE_ALLOWED) {
Log.i("checkSelfPermission", "**" + permission + " true");
}else if(result == AppOpsManagerCompat.MODE_DEFAULT) {
Log.i("checkSelfPermission", "**" + permission + " default");
}
}
Log.i("checkSelfPermission", "***********************"+permissions.length);
02-26 16:18:03.016 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: ***********************5
02-26 16:18:03.017 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: **android.permission.READ_EXTERNAL_STORAGE true
02-26 16:18:05.546 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: **android.permission.CAMERA false
02-26 16:18:05.546 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: **android.permission.READ_SMS false
02-26 16:18:05.547 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: ***********************5
4.png
由上面两图看到,我们请求了5个权限,但是只输出的3项权限,均为危险权限,而标准权限没有任何输出,因为转op时为null了,可能是标准权限不在转换清单中,所以....。
经过测试,获得的危险权限授权状态的结果与checkSelfPermission方法返回的结果一致。
权限的检测和请求可统一封装为 “权限请求”,权限结果回调可统一封装为“权限结果处理”
权限请求.png
通过上图,可以看到,权限判断被封装进了权限请求中,所以我们在请求/判断权限时,统一调用接口IRequestPermissions即可;
权限结果处理.png
通过上图,可以看到,权限结果处理方案有多种,我们此处采取的是策略模式,以扩展更多的方案;
RequestPermissionsResult、RequestPermissionsResultSetApp分别表示不同的处理方案。
那么,我们再处理权限结果回调时,仅需调用接口IRequestPermissionsResult即可。
先来看下代码中是如何调用的:
第一步:实例化 “权限请求”,“权限结果处理”
IRequestPermissions requestPermissions = RequestPermissions.getInstance();//动态权限请求
IRequestPermissionsResult requestPermissionsResult = RequestPermissionsResultSetApp.getInstance();//动态权限请求结果处理
第二步:在需要的地方,请求权限
//请求权限
private boolean requestPermissions(){
//需要请求的权限
String[] permissions = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.CAMERA};
//开始请求权限
return requestPermissions.requestPermissions(
this,
permissions,
PermissionUtils.ResultCode1);
}
第三步:onRequestPermissionsResult回调中,统一处理结果
//用户授权操作结果(可能授权了,也可能未授权)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//用户给APP授权的结果
//判断grantResults是否已全部授权,如果是,执行相应操作,如果否,提醒开启权限
if(requestPermissionsResult.doRequestPermissionsResult(this, permissions, grantResults)){
//请求的权限全部授权成功,此处可以做自己想做的事了
//输出授权结果
Toast.makeText(MainActivity.this,"授权成功,请重新点击刚才的操作!",Toast.LENGTH_LONG).show();
}else{
//输出授权结果
Toast.makeText(MainActivity.this,"请给APP授权,否则功能无法正常使用!",Toast.LENGTH_LONG).show();
}
}
通过这3步,即可快速实现动态权限的所有操作。
而如何检测、判断、请求的权限,如何引导用户设置权限等相关事项,开发人员不需要再关注。
public interface IRequestPermissions {
/**
* 请求权限
* @param activity 上下文
* @param permissions 权限集合
* @param resultCode 请求码
* @return 如果权限已全部允许,返回true; 反之,请求权限,在
*/
boolean requestPermissions(Activity activity, String[] permissions, int resultCode);
}
权限请求实现类
public class RequestPermissions implements IRequestPermissions {
private static RequestPermissions requestPermissions;
public static RequestPermissions getInstance(){
if(requestPermissions == null){
requestPermissions = new RequestPermissions();
}
return requestPermissions;
}
@Override
public boolean requestPermissions(Activity activity, String[] permissions, int resultCode) {
//判断手机版本是否23以下,如果是,不需要使用动态权限
if(Build.VERSION.SDK_INT < 23){
return true;
}
//判断并请求权限
return requestNeedPermission(activity,permissions,resultCode);
}
private boolean requestAllPermission(Activity activity, String[] permissions, int resultCode){
//判断是否已赋予了全部权限
boolean isAllGranted = CheckPermission.checkPermissionAllGranted(activity, permissions);
if(isAllGranted){
return true;
}
ActivityCompat.requestPermissions(activity, permissions, resultCode);
return false;
}
private boolean requestNeedPermission(Activity activity, String[] permissions, int resultCode){
List list = CheckPermission.checkPermissionDenied(activity, permissions);
if(list.size() == 0){
return true;
}
//请求权限
String[] deniedPermissions = list.toArray(new String[list.size()]);
ActivityCompat.requestPermissions(activity, deniedPermissions, resultCode);
return false;
}
}
权限检测类
public class CheckPermission {
/**
* 检查是否拥有指定的所有权限
* @param context 上下文
* @param permissions 权限数组
* @return 只要有一个权限没有被授予, 则直接返回 false,否则,返回true!
*/
public static boolean checkPermissionAllGranted(Context context, String[] permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 检查未允许的权限集合
* @param context 上下文
* @param permissions 权限集合
* @return 未允许的权限集合
*/
public static List checkPermissionDenied(Context context, String[] permissions){
List lstPermissions = new ArrayList<>();
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
lstPermissions.add(permission);
}
}
return lstPermissions;
}
}
接口
public interface IRequestPermissionsResult {
/**
* 处理权限请求结果
* @param activity
* @param permissions 请求的权限数组
* @param grantResults 权限请求结果数组
* @return 处理权限结果如果全部通过,返回true;否则,引导用户去授权页面
*/
boolean doRequestPermissionsResult(Activity activity, @NonNull String[] permissions, @NonNull int[] grantResults);
}
权限请求结果实现类(方案一:如果授权失败,不做任何处理)
public class RequestPermissionsResult implements IRequestPermissionsResult {
private static RequestPermissionsResult requestPermissionsResult;
public static RequestPermissionsResult getInstance(){
if(requestPermissionsResult == null){
requestPermissionsResult = new RequestPermissionsResult();
}
return requestPermissionsResult;
}
@Override
public boolean doRequestPermissionsResult(Activity activity, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean isAllGranted = true;
// 判断是否所有的权限都已经授予了
for (int grant : grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
isAllGranted = false;
break;
}
}
//已全部授权
if (isAllGranted) {
return true;
}
else {
//什么也不做
}
return false;
}
}
权限请求结果实现类(方案二:如果授权失败,引导用户进行应用授权)
public class RequestPermissionsResultSetApp implements IRequestPermissionsResult{
private static RequestPermissionsResultSetApp requestPermissionsResult;
public static RequestPermissionsResultSetApp getInstance(){
if(requestPermissionsResult == null){
requestPermissionsResult = new RequestPermissionsResultSetApp();
}
return requestPermissionsResult;
}
@Override
public boolean doRequestPermissionsResult(Activity activity, @NonNull String[] permissions, @NonNull int[] grantResults) {
List deniedPermission = new ArrayList<>();
for (int i=0; i
引导用户去授权
public class SetPermissions {
/**
* 打开APP详情页面,引导用户去设置权限
* @param activity 页面对象
* @param permissionNames 权限名称(如是多个,使用\n分割)
*/
public static void openAppDetails(final Activity activity, String permissionNames) {
StringBuilder sb = new StringBuilder();
sb.append(PermissionUtils.PermissionTip1);
sb.append(permissionNames);
sb.append(PermissionUtils.PermissionTip2);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(sb.toString());
builder.setPositiveButton(PermissionUtils.PermissionDialogPositiveButton, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + activity.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
activity.startActivity(intent);
}
});
builder.setNegativeButton(PermissionUtils.PermissionDialogNegativeButton, null);
builder.show();
}
}
public class PermissionUtils {
public static int ResultCode1 = 100;//权限请求码
public static int ResultCode2 = 200;//权限请求码
public static int ResultCode3 = 300;//权限请求码
public static String PermissionTip1 = "亲爱的用户 \n\n软件部分功能需要请求您的手机权限,请允许以下权限:\n\n";//权限提醒
public static String PermissionTip2 = "\n请到 “应用信息 -> 权限” 中授予!";//权限提醒
public static String PermissionDialogPositiveButton = "去手动授权";
public static String PermissionDialogNegativeButton = "取消";
private static PermissionUtils permissionUtils;
public static PermissionUtils getInstance(){
if(permissionUtils == null){
permissionUtils = new PermissionUtils();
}
return permissionUtils;
}
private HashMap permissions;
public HashMap getPermissions(){
if(permissions == null){
permissions = new HashMap<>();
initPermissions();
}
return permissions;
}
private void initPermissions(){
//联系人/通讯录权限
permissions.put("android.permission.WRITE_CONTACTS","--通讯录/联系人");
permissions.put("android.permission.GET_ACCOUNTS","--通讯录/联系人");
permissions.put("android.permission.READ_CONTACTS","--通讯录/联系人");
//电话权限
permissions.put("android.permission.READ_CALL_LOG","--电话");
permissions.put("android.permission.READ_PHONE_STATE","--电话");
permissions.put("android.permission.CALL_PHONE","--电话");
permissions.put("android.permission.WRITE_CALL_LOG","--电话");
permissions.put("android.permission.USE_SIP","--电话");
permissions.put("android.permission.PROCESS_OUTGOING_CALLS","--电话");
permissions.put("com.android.voicemail.permission.ADD_VOICEMAIL","--电话");
//日历权限
permissions.put("android.permission.READ_CALENDAR","--日历");
permissions.put("android.permission.WRITE_CALENDAR","--日历");
//相机拍照权限
permissions.put("android.permission.CAMERA","--相机/拍照");
//传感器权限
permissions.put("android.permission.BODY_SENSORS","--传感器");
//定位权限
permissions.put("android.permission.ACCESS_FINE_LOCATION","--定位");
permissions.put("android.permission.ACCESS_COARSE_LOCATION","--定位");
//文件存取
permissions.put("android.permission.READ_EXTERNAL_STORAGE","--文件存储");
permissions.put("android.permission.WRITE_EXTERNAL_STORAGE","--文件存储");
//音视频、录音权限
permissions.put("android.permission.RECORD_AUDIO","--音视频/录音");
//短信权限
permissions.put("android.permission.READ_SMS","--短信");
permissions.put("android.permission.RECEIVE_WAP_PUSH","--短信");
permissions.put("android.permission.RECEIVE_MMS","--短信");
permissions.put("android.permission.RECEIVE_SMS","--短信");
permissions.put("android.permission.SEND_SMS","--短信");
permissions.put("android.permission.READ_CELL_BROADCASTS","--短信");
}
/**
* 获得权限名称集合(去重)
* @param permission 权限数组
* @return 权限名称
*/
public String getPermissionNames(List permission){
if(permission==null || permission.size()==0){
return "\n";
}
StringBuilder sb = new StringBuilder();
List list = new ArrayList<>();
HashMap permissions = getPermissions();
for(int i=0; i
请求权限.jpg
请求权限--带不再提醒6.jpg
引导用户去授权--包含需要的权限名称.jpg
Demo下载地址:https://pan.baidu.com/s/1dnaugm
注意:
以上内容只是为了大家能清晰的理解动态权限的使用,Demo可以作为代码参考,但是不应拿到项目中直接使用,因为不同的项目中有不同的要求和限制。使用该Demo中的代码时,请根据自己项目的要求,进一步优化、调整Demo的代码(如什么场合使用checkSelfPermission、shouldShowRequestPermissionRationale还是noteProxyOp等)
本案例如有问题,请及时反馈给我们,感谢!
作者:Android草根王
链接:https://www.jianshu.com/p/8e37e9cf20a5
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。