6.0权限申请巧遇问题

小序

原本以为这次项目完成得不错,结果测试妹子在禅道上又提了些BUG,大多涉及到6.0权限的问题。这就比较尴尬了,毕竟是一开始并没有顾虑的。而关于权限申请,其实际上已经有很多前辈提供过相应的解决办法,在此谢过。

正文

1. 需动态申请的权限

权限的申请依据谷歌的分类来讲,一类属于普通的权限申请,与往常一样可直接在 AndroidManifest.xml 注册就可以了;另一类则需要在应用程序使用的地方执行动态申请,该类权限属危险权限(组),当然前提也得先在 AndroidManifest.xml 下注册声明。这里仅罗列需要动态申请的权限内容(摘自这里)。

权限组 权限
CALENDAR 日历 android.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR
CAMERA 相机 android.permission.CAMERA
CONTACTS 联系人 android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNTS
LOCATION 位置 android.permission.ACCESS_FINE_LOCATION
android.permission.ACCESS_COARSE_LOCATION
MICROPHONE 麦克风 android.permission.READ_PHONE_STATE
android.permission.CALL_PHONE
android.permission.READ_CALL_LOG
android.permission.WRITE_CALL_LOG
com.android.voicemail.permission.ADD_VOICEMAIL
android.permission.USE_SIP
android.permission.PROCESS_OUTGOING_CALLS
PHONE 电话 android.permission.READ_PHONE_STATE
android.permission.CALL_PHONE
android.permission.READ_CALL_LOG
android.permission.WRITE_CALL_LOG
com.android.voicemail.permission.ADD_VOICEMAIL
android.permission.USE_SIP
android.permission.PROCESS_OUTGOING_CALLS
SENSORS 传感器 android.permission.BODY_SENSORS
SMS 短信 android.permission.SEND_SMS
android.permission.RECEIVE_SMS
android.permission.READ_SMS
android.permission.RECEIVE_WAP_PUSH
android.permission.RECEIVE_MMS
android.permission.READ_CELL_BROADCASTS
STORAGE 存储 android.permission.READ_EXTERNAL_STORAGE
android.permission.WRITE_EXTERNAL_STORAGE

2. 具体实现

了解完需要申请的权限之后,需要根据实际项目当中需要用到的相关权限,在相应的位置开始我们的权限申请。申请过程主要包括以下两个操作,另外也有人封装了些简单易用的依赖库,有兴趣的亲也可了解下。

/**
 * 以定动态申请位权限为例
 */
public void handPermission() {
    // 定位权限组
    String[] mPermissionGroup = new String[]{
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION};

    // 过滤已持有的权限
    List<String> mRequestList = new ArrayList<>();
    for (String permission : mPermissionGroup) {
        if ((ContextCompat.checkSelfPermission(getActivity(), permission)
                != PackageManager.PERMISSION_GRANTED)) {
            mRequestList.add(permission);
        }
    }

    // 申请未持有的权限
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !mRequestList.isEmpty()) {
        ActivityCompat.requestPermissions(mActivity, mRequestList.toArray(
                new String[mRequestList.size()]), 100);
    } else {
        // 权限都有了,就可以继续后面的操作
    }
}
/**
 * 回调权限申请结果
 *
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
@Override
public void onRequestPermissionsResult(
        int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case 100: {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 同意权限申请,就可以继续后面的操作
            } else {
                // 拒绝权限申请,提示申请权限的重要性
            }
        }
        break;
    }
}

3. 巧遇问题

a. 反复申请

“重复申请”是不合理的,这样会严重影响到用户的体验。最好是在用户拒绝权限的申请用给予用户用好提示,或本次应用开启权限仅申请一次,不通过提示为授权之类的。

部分机子系统需要特别注意,因为它们整个应用的申请仅提示一次,如最为熟悉的小米/红米。无论之后再怎么requestPermissions也无济于事,到头来还得提示用户到应用设置手动启动权限。这算是比较恶心的一个事情,且其他机子上如果用户勾选了不再提示也是一样的。

建议权限申请不要再onResume()的生命周期中执行,因为权限申请操作后会再次调用onResume(),个人在小米/红米机子上测试出现过该问题。

b. 多权限申请带来的问题

String[] mPermissionGroup = new String[]{
            Manifest.permission.CAMERA
            Manifest.permission.ACCESS_COARSE_LOCATION};

// 注意:这样的多权限申请是存在问题的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        ActivityCompat.requestPermissions(mActivity, mPermissionGroup, 100);
    }

如上面所见,我定义了一个需要权限请求的数组。里面包括了两个权限组,一个是照相机的权限,另一个则是定位的权限(权限组当中的任意成员被授予权限,相当于权限组内的各个权限都将被授予权限)。那么在实际执行当中会是什么效果呢?

多权限申请效果1

经上面图示效果可以看到,多权限申请的时候每一个权限组的申请都被单独开来了。那么问题来了,因为权限的申请是我们主动发出的,并且一个应用当中往往不止一个地方需要权限申请。简单的说如果你已经执行百度定位授予了定位权限了。那么你下次就应该先判断定位权限是否已被授权再加入到多权限申请单中。

那么有人就要问了,即使一开始不判断是否授权。在申请授权的回调过程中不也能自己判断麽?嗯,确实是这样没错。但那只是针对mPermissionGroup这个整体,即如果当中有部分权限已经被授予,而剩下的未授予,将全部被重新申请

E/SQLiteDatabase: Failed to open database ‘/storage/emulated/0/emlibs/libs/monitor.db’
E/SQLiteDatabase: Failed to open database ‘/storage/emulated/0/baidu/tempdata/ls.db’

以上列举两种异常,主要在小米/红米机子上测试出现,其他机子未测试确认。出现该问题的罪魁祸首就是,在多权限申请当中将原已授权的权限拒绝了,且该权限刚好正被使用。解决以上原因只需要一步“过滤”,遍历mPermissionGroup集合当中每个成员的授权状态再执行多权限申请(如第2点当中写到的)

发布了50 篇原创文章 · 获赞 3 · 访问量 8万+
展开阅读全文

三星A7100文件写入失败(Permission denied)

05-10

如题,三星A7100文件写入失败:java.io.FileNotFoundException: /storage/emulated/0/abc.apk: open failed: EACCES (Permission denied),做自动更新从服务器下载apk存储到手机上,其他两台真机都能存储成功,唯独三星A7100这台不行,不懂为什么,求大神帮助。 权限都加了 <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 源码: public class UpdateManger { // 应用程序Context private Context mContext; // 提示消息 private String updateMsg = "有最新的软件包,请下载!"; // 下载安装包的网络路径 private String apkUrl = "http://115.28.6.127:8090/app/1462785029964BirdStore20150929V131.apk"; private Dialog noticeDialog;// 提示有软件更新的对话框 private Dialog downloadDialog;// 下载对话框 private static final String savePath = Environment.getExternalStorageDirectory().getPath()+"/updateDemo/";// 保存apk的文件夹 private static final String saveFileName = savePath + "UpdateDemoRelease.apk"; // 进度条与通知UI刷新的handler和msg常量 private ProgressBar mProgress; private static final int DOWN_UPDATE = 1; private static final int DOWN_OVER = 2; private static final int DOWN_FAIL = 3; private int progress;// 当前进度 private Thread downLoadThread; // 下载线程 private boolean interceptFlag = false;// 用户取消下载 // 通知处理刷新界面的handler private Handler mHandler = new Handler() { @SuppressLint("HandlerLeak") @Override public void handleMessage(Message msg) { switch (msg.what) { case DOWN_UPDATE: mProgress.setProgress(progress); break; case DOWN_OVER: downloadDialog.cancel(); installApk(); break; case DOWN_FAIL: Toast.makeText(mContext, "下载出错", Toast.LENGTH_SHORT).show(); break; } super.handleMessage(msg); } }; public UpdateManger(Context context) { this.mContext = context; } // 显示更新程序对话框,供主程序调用 public void checkUpdateInfo() { showNoticeDialog(); } private void showNoticeDialog() { android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder( mContext);// Builder,可以通过此builder设置改变AleartDialog的默认的主题样式及属性相关信息 builder.setTitle("软件版本更新"); builder.setMessage(updateMsg); builder.setPositiveButton("下载", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss();// 当取消对话框后进行操作一定的代码?取消对话框 showDownloadDialog(); } }); builder.setNegativeButton("以后再说", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); noticeDialog = builder.create(); noticeDialog.setCancelable(false); noticeDialog.show(); } protected void showDownloadDialog() { android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder( mContext); builder.setTitle("正在下载"); final LayoutInflater inflater = LayoutInflater.from(mContext); View v = inflater.inflate(R.layout.progress, null); mProgress = (ProgressBar) v.findViewById(R.id.progress); builder.setView(v);// 设置对话框的内容为一个View builder.setNegativeButton("取消", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); interceptFlag = true; } }); downloadDialog = builder.create(); downloadDialog.show(); downloadApk(); } private void downloadApk() { downLoadThread = new Thread(mdownApkRunnable); downLoadThread.start(); } protected void installApk() { File apkfile = new File(saveFileName); if (!apkfile.exists()) { return; } Log.e("File.toString()", ""+apkfile.toString()); Intent i = new Intent(Intent.ACTION_VIEW); i.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");// File.toString()会返回路径信息 mContext.startActivity(i); } private Runnable mdownApkRunnable = new Runnable() { @Override public void run() { URL url; try { url = new URL(apkUrl); HttpURLConnection conn = (HttpURLConnection) url .openConnection(); conn.connect(); int length = conn.getContentLength(); InputStream ins = conn.getInputStream(); File file = new File(savePath); if (!file.exists()) { boolean b = file.mkdirs(); Log.e("exists", saveFileName+","+b); } String apkFile = saveFileName; File ApkFile = new File(apkFile); FileOutputStream outStream = new FileOutputStream(ApkFile); int count = 0; byte buf[] = new byte[1024]; do { int numread = ins.read(buf); count += numread; progress = (int) (((float) count / length) * 100); // 下载进度 mHandler.sendEmptyMessage(DOWN_UPDATE); if (numread <= 0) { // 下载完成通知安装 mHandler.sendEmptyMessage(DOWN_OVER); break; } outStream.write(buf, 0, numread); } while (!interceptFlag);// 点击取消停止下载 outStream.close(); ins.close(); } catch (Exception e) { Log.e("Exception", ""+e.getMessage().toString()); mHandler.sendEmptyMessage(DOWN_FAIL); e.printStackTrace(); } } }; } 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览