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

来源:互联网 发布:linux开源代码 编辑:程序博客网 时间:2024/05/21 01:56

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的用户~