大概是去年收到相关部门的报告,说app频繁弹出权限申请弹框。刚开始不太理解频繁弹出,然后看了一些大厂app,他们对申请设备权限这个增加处理。大概是这样子的,比如设备读存储权限,
第一种未作处理前是这样,当你用到存储权限会打开权限弹框,当你点击拒绝使用时,再次用到存储权限还会弹出打开权限的弹框。第一种做法会已经不符合安全合规了。
第二种做法,当你使用用到存储权限是,会弹出打开权限的弹框,当你点击拒绝,再次用到存储权限是会弹出另一个提示弹框,让你去系统打开app权限,另外当申请权限是还会增加一个权限说明弹框。第二种做法符合安全合规。如下图所示(图片来源网络,侵删)
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.Settings;
import androidx.core.content.ContextCompat;
import com.tbruyelle.rxpermissions2.RxPermissions;
import com.wangyi.Utils.ChatConstant;
import com.widget.ConfimDialog;
import com.widget.PermissionsDescriptionDialog;
import java.util.ArrayList;
import java.util.List;
public class PermissionsUtlis {
/**
* 判断是否缺少权限,利用SP存储记录
*/
public static boolean lacksPermissionNew(Context mContext, String permission) {
if (SpUtil.getBoolean(mContext, ChatConstant.APP_WRITE_EXTERNAL, true)) {
PermissionsDescriptionDialog.getInstance()
.setTitle("存储权限使用说明")
.setContent("XX正在申请读取您的存储权限,缓存并同步本地内容,同时保障服务流畅。拒绝或取消不影响您使用XX其他服务。")
.show(mContext);
SpUtil.putBoolean(mContext, ChatConstant.APP_WRITE_EXTERNAL, false);
return true;
}
return !(ContextCompat.checkSelfPermission(mContext, permission) ==
PackageManager.PERMISSION_DENIED);
}
public static void cunChuPermissisons(Context context, String permission, PermissisonsListener permissisonsListener) {
if (lacksPermissionNew(context, permission)) {
RxPermissions rxPermissions = new RxPermissions((Activity) context);
rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(aBoolean -> {
PermissionsDescriptionDialog.getInstance().hide();
if (aBoolean) {
permissisonsListener.Permissisons(true);
} else {
PermissionsUtlis.cunChuPermissisons(context);
}
});
} else {
PermissionsUtlis.cunChuPermissisons(context);
}
}
public static void cunChuPermissisons(Context context) {
ConfimDialog.getInstance().setTitle("XX需要申请存储权限")
.setContent("XX正在申请读取您的存储权限同时保障服务流畅。拒绝或取消不影响您使用XX其他服务。")
.setTvCancel("取消")
.setTvConfim("去设置")
.setOperateListener(new ConfimDialog.OnOperateListener() {
@Override
public void onSure() {
//引导用户至设置页手动授权
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", "com.包名", null);
intent.setData(uri);
context.startActivity(intent);
}
@Override
public void onCancel() {
}
}).show(context);
}
}
public interface PermissisonsListener {
void Permissisons(boolean flag);
}
package com.widget;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
public class PermissionsDescriptionDialog {
private static PermissionsDescriptionDialog minstance;
public static Dialog dialog;
private static Handler handler = new Handler(Looper.getMainLooper());
private String content = "";
private boolean flag;
private String title;
private OnOperateListener operateListener;
public static PermissionsDescriptionDialog getInstance() {
minstance = new PermissionsDescriptionDialog();
return minstance;
}
public void show(final Context context) {
handler.post(new Runnable() {
@Override
public void run() {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
dialog = new Dialog(context, R.style.MyDialogStyle);
View view = View.inflate(context, R.layout.alert_permissons_dialog, null);
dialog.setContentView(view);
initView(view);
dialog.setCancelable(false);
Window window = dialog.getWindow(); //得到对话框
// window.setWindowAnimations(R.style.dialogWindowAnim); //设置窗口弹出动画
WindowManager.LayoutParams lp = window.getAttributes();
if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {//横屏
lp.width = getScreenHeight(context) / 20 * 17;
} else {
lp.width = getScreenWidth(context) / 20 * 17;
}
window.setGravity(Gravity.TOP);
window.setAttributes(lp);
dialog.show();
}
});
}
private void initView(View view) {
TextView tvTvitle = view.findViewById(R.id.tv_tvitle);
if (!TextUtils.isEmpty(title)) {
tvTvitle.setText(title);
tvTvitle.setVisibility(View.VISIBLE);
} else {
tvTvitle.setVisibility(View.GONE);
}
TextView tvContent = (TextView) view.findViewById(R.id.tv_content);
tvContent.setText(content);
}
public void hide() {
handler.post(new Runnable() {
@Override
public void run() {
if (null == dialog)
return;
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
}
});
}
public void hideDelayed(long delayMillis) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (null == dialog)
return;
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
}
}, delayMillis);
}
public PermissionsDescriptionDialog setContent(String content) {
this.content = content;
return minstance;
}
public PermissionsDescriptionDialog setTitle(String title) {
this.title = title;
return minstance;
}
public PermissionsDescriptionDialog setContent(String content, boolean flag) {
this.content = content;
this.flag = flag;
return minstance;
}
public PermissionsDescriptionDialog setOperateListener(OnOperateListener operateListener) {
this.operateListener = operateListener;
return minstance;
}
public interface OnOperateListener {
void onSure();
void onCancel();
}
/**
* 获取屏幕分辨率宽
*/
public static int getScreenWidth(Context context) {
DisplayMetrics dm = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(dm);
Log.i("widthPixels", dm.widthPixels + "");
return dm.widthPixels;
}
/**
* 获取屏幕分辨率高
*/
public static int getScreenHeight(Context context) {
DisplayMetrics dm = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(dm);
return dm.heightPixels;
}
}
@android:color/transparent
@null
true
true
true
@null
- @android:style/Animation.Dialog
true
public static void cunChuPermissisons(Context context) {
ConfimDialog.getInstance().setTitle("XX需要申请存储权限")
.setContent("XX正在申请读取您的存储权限,,缓存并同步本地内容,同时保障服务流畅。拒绝或取消不影响您使用XX其他服务。")
.setTvCancel("取消")
.setTvConfim("去设置")
.setOperateListener(new ConfimDialog.OnOperateListener() {
@Override
public void onSure() {
//引导用户至设置页手动授权,写好对应的包名
Intent intent = new
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", "com.包名", null);
intent.setData(uri);
context.startActivity(intent);
}
@Override
public void onCancel() {
}
}).show(context);
}
PermissionsUtlis.cunChuPermissisons(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, new PermissisonsListener() {
@Override
public void Permissisons(boolean flag) {
if (flag) {
//TODO 实现你需要的逻辑
}
}
});
RxPermissions的库github地址:https://github.com/tbruyelle/RxPermissions
每个做开发都希望可以给用户带来更好的体验,前提是保证用户隐私。后续也会发表一些其他回归安全问题的方案,比如漏洞和信息安全等相关文章。部分弹框没详细写,可以结合自己代码进行完善。