Android6.0和7.0上遇到的坑以及解决方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Moing557/article/details/78585795

android系统的版本已经更新到了8.0了。根据统计版本的分布已经从过去的2.x推进到4.x以上了。所以开发中已经几乎可以不考虑2.x等版本了。
然后像6.0以上的份额也越来越多。所以开发中是有必要考虑6.0以上版本的。
现在比较新的版本中,6.0(API23 VERSION_CODES M )和7.0(API24 VERSION_CODES N)的安全性大大提高。对权限的要求也高了。所以以前4.X、5.X的上运行没问题的程序,到了6.X,7.X上都可能会出现一些问题。
这是我前些天在我的手机上测试时候发现的。之前用的都是4.4测试,后来换7.0测试了。
第一个坑:6.0以上 特殊的权限需要动态请求获取,不再是清单文件申请就ok的了。
我的代码中有在内部储存中创建目录的代码。创建目录后接下来的一些文件操作才能成功。之前在4.4上运行没有问题。然后到7.0上总是出错。一开始是摸不着头脑~咋回事?莫非是手机管家类的禁止了?还是代码出现什么问题?后面发现是创建目录失败了。

String path=Environment.getExternalStorageDirectory() + File.separator + "dirPath" + File.separator;
File file=new File(path);
if(!file.exists()){
    //创建目录
    boolean mkdirs=file.mkdirs();
    Log.e(tag,"----"+mkdirs+"----");
}

log打印false。然后就是查找资料。发现6.0以上写文件的权限是需要动态请求的,否则默认就是没有这个权限的。所以,通过发送权限请求的代码就可以解决这个问题了。

 public static final int PERMISSIONS_REQUEST_CODE = 1;

    /**
     * 发起一个写文件的权限请求
     */
    public static boolean setApi23(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            /**
             * API23以上版本需要发起写文件权限请求
             */
            ActivityCompat.requestPermissions(activity, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_CODE);
            return true;
        } else {
            return false;
        }
    }

封装成了一个方法。调用这个方法就可以弹出一个请求权限的dialog了。因为android的dialog都不会阻塞线程,如果在用户没有同意授权之前就执行了代码,仍然会出错。所以需要判断一下,而且需要一个回调接口。
示例代码:

//activity onCreate中
if(!setApi23(this)){
    //api23以下!正常执行
}
//用户操作后的回调方法
@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
             //上面请求时候的请求码
            case FileUtils.PERMISSIONS_REQUEST_CODE:
                if (grantResults.length > 0 && grantResults[0] == PackageManager
                        .PERMISSION_GRANTED) {
                   Log.e(tag,"获取成功");
                   //正常执行代码
                } else {
                    Log.e(tag,"获取失败");
                    //干点啥好?...
                }
                break;
        }
    }

6.0上对用户的安全考虑,这会让一些比较涉及用户隐私的权限都暴露给用户知道。是一个比较赞的设计。而且代码修改也是比较简单。不像7.0上的另外一个坑。害我花了好长时间才搞定。

第二个坑:7.0以上 和外部分享文件必须要通过provider
这个是对应用的安全考虑,因为在7.0之前,比如调用系统应用打开文件都是通过 Uri.fromFile(file);获得的。而7.0后如果需要让外部应用通过Intent访问自己应用所有的文件,必须通过一个FileProvider类来获得Intent。就是通过四大组件的方式来实现。
这个android已经写好了。
第一步:
在清单文件中注册

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.xxx.xxxx.xxxxx"
    android:exported="false"
    android:grantUriPermissions="true">
<meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_paths"/>
</provider>

这个已经是固定模式了。除了android:authorities和android:resource这两个属性可以自定义外,其他都得按照这个形式。比如exported要求必须为false,为true的话会报安全异常;grantUriPermissions:true表示授予 URI 临时访问权限。这里的authorities填的是我的应用包名。
还有一点需要注意。这个provider一定要写在Application标签内。如果粗心写在了外面就会报一个空指针异常。
然后还必须创建这个xml文件。xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="xxoo"
        path=""/>
 <!--<files-path/>代表目录 Context.getFilesDir()
<external-path/>代表目录: Environment.getExternalStorageDirectory()
<cache-path/>代表的目录: getCacheDir();-->
</paths>

比较坑人的地方就是这个xml文件了。一开始不知道name和path属性代表什么意思。所以不知道这xml该怎么写。网上看文章全都是清一色的复制粘贴。连文字都一模一样。都没有说清楚这个xml文件的name和path的意义。究竟是path填什么,然后name是否代表目录的名称啊都懵逼了~最后看到这篇文章 Android7.0须知–应用间共享文件(FileProvider) 中说的一句话我才明白这个name和path属性该怎么用。

“但是,因为有很多时候,图片来源不确定,而且每款手机的相册所在的文件名称也可能不一样,如果一一添加的话,很麻烦,而且容易遗漏,这里,我用了一个简单的方法,很方便。代码如下,这样的话,我可以传递外部存储设备根目录下的任意一张图片了(包括其子目录)
<external-path path="" name="camera_photos" />

看到这我才明白。其实属性中的name是可以不用去管的。见名知意即可。然后path就是根据标签为根目录,它所在的目录路径,而且可以为空。
比如<external-path/>这个标签代表
Environment.getExternalStorageDirectory()
假如path=”/image”
那么最终就是指定了
Environment.getExternalStorageDirectory()+/image
所以当path=””
那就表示
Environment.getExternalStorageDirectory()+""
就是这么回事了。

以上准备好后就可以了。代码中修改依旧是很简单。

/**
     * 获得调用系统应用打开文件的intent
     *
     * @param context
     * @param file
     */
    public static Intent getFileIntent(Context context, File file) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Uri contentUri;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //因为注册的时候authorities填的是包名...
            String authority = context.getPackageName();
            //7.0上必须这样获得Intent
            contentUri = FileProvider.getUriForFile(context, authority, file);
        } else {
            //7.0以下使用旧版本的方式
            contentUri = Uri.fromFile(file);
        }
        String MimeType = context.getContentResolver().getType(contentUri);
        intent.setDataAndType(contentUri, MimeType);
        return intent;
    }

同样是封装了一个方法。判断版本分别应付。得到intent后直接startActivity或传递给PendingIntent就行了。

以上就是我在7.0版本遇到的坑和解决办法。当是自己记录一下。懒得截图了~幸好没有懒得发文章。(最坑的是坑人的文章。不说清楚一点~!!吐槽ing→→ [虽然可能我也没说清楚←←])
总体来讲虽然一些原来普通的操作变得需要一些手续才能完成,但这会使Android系统变得越来越好。对开发者和用户而言都是一个好消息。毕竟我也是Android的用户~

猜你喜欢

转载自blog.csdn.net/Moing557/article/details/78585795
今日推荐