Android 版本、权限适配相关总结
目录
- 请求存储权限
- 版本适配
- Android 7.0 前
- Android 7.0 后
- Android 10.0
- 什么是作用域
- 举个栗子
请求存储权限
首先需要在 AndroidManifest.xml 文件中声明权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
在代码中请求用户权限:
// 权限请求码 private static final int PERMISSION_REQ_ID = 0; // 请求权限 private static final String[] REQUESTED_PERMISSIONS = { Manifest.permission.READ_EXTERNAL_STORAGE }; ... // 判断有没有存储权限 if (checkSelfPermission(REQUESTED_PERMISSIONS[0],PERMISSION_REQ_ID)){ //YSE }else { //NO } private boolean checkSelfPermission(String permissions,int requestCode){ if (ContextCompat.checkSelfPermission(this,permissions) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode); return false; } return true; } // 重写此方法,接收用户授权回调 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { Log.i(TAG, "onRequestPermissionsResult: requestCode =" + requestCode +"\n,permissions =" + Arrays.toString(permissions) +"\n,grantResults =" + Arrays.toString(grantResults)); if (requestCode == PERMISSION_REQ_ID){ if (grantResults[0] == PackageManager.PERMISSION_GRANTED){ //用户同意权限 }else { //用户拒绝权限 } }
版本适配
从 Android 6.0 到 Android 10 存储/访问文件功能,有发生了很多变化。
Android 7.0 前
在Android 7.0 以前我们访问内存中的文件可以通过 Uri.fromFile,将 File 转换成 Uri 对象,这个 Uri 对象表示这本地真实路径。 访问一个图片:
String fileName = "default_Image.jpg"; File file = new File("file_path", fileName); Uri uri = Uri.fromFile(file);
Android 7.0 后
在 7.0 后,这种通过真实路径来获取的 Uri 被认为是不安全的,所以提供了一种新的解决方案,就是通过 FileProvide 来实现文件的访问,FileProvider 是一种比较特殊的内容提供器,他使用了类似于内容提供器的机制来对数据进行保护。 访问一个图片:
File file = new File(CACHE_IMG, "file_name"); Uri imageUri = FileProvider.getUriForFile(activity,"com.sandan.fileprovider", file); //这里进行替换uri的获得方式
然而上面这种真的好吗,对用开发者而且这算是好处吧,但是对用用户而言,上述的无疑一些流氓作用,因为开发者完全可以访问的内存中的所有位置,并作出一些改变,导致 SD 卡中的空间变得非常乱,即使卸载了 app,但是一些垃圾文件却还在内存中。
Android 10.0
在 Android 10.0 ,为了解决上述问题,Google 在 Android 10.0 中加入了 作用域功能。
什么是作用域
就是 Android 系统对 SD 卡做了很大的限制,从 Android 10.0 开始,每个程序只能有权在自己的外置存储空间关联的目录下读取和创建相应的文件,也称作沙箱。获取该目录的代码是:getExternalFilesDir() ,关联的目录路径大致如下:
Html CSS JavaScript Vb vbs Asp PHP Perl Python Ruby C# C++ SQL Delphi Diff Groovy Java JavaFX ActionScript3 Bash/shell powershell Plain Text Scala XML显示语言名称 显示行号 允许折叠
将数据放在这个目录下,你可以使用之前的方法对文件进行读写,不需要作出任何变更和适配。但是这个文件夹中的文件会随着应用卸载而被随之删除。 那如果需要访问其他目录怎么办呢?比如获取相册中的图片,向相册中添加一张图片。为此,Android 系统针对系统文件类型进行了分类:图片,音频,视频 这三类文件可以通过 MediaStore API 来进行访问,这种称为共享空间,其他的系统文件需要使用 系统的文件选择器来进行访问,另外,如果程序向媒体库写入图片,视频,音频,将会自动用于读写权限,不需要额外申请权限,如果你要读取其他程序向媒体贡献的图片,视频,音频,则必须要申请 READ_EXTERNAL_STORAGE 权限,WRITE_EXTERNAL_STORAGE 权限会在未来的版本中被废弃。
举个栗子
举例说明:有一张本地图片,向这张图片添加水印,并保存到相册。
直接上代码:
/** * 保存图片到相册 * * @param context 上下文 * @param text 水印文字 */ private void savePhotoAlbum(final Context context, final String text) { //这里开启子线程,防止堵塞。 new Thread(new Runnable() { @Override public void run() { try { //从本地获取一张图片,转成Bitmap Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_info); //在沙箱中创建文件,名称:info.jpg File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), "info.jpg"); //判断文件是否存在,不存在创建文件。 if (!file.exists()) { file.createNewFile(); } // 向图片添加水印 Bitmap newBitmap = addInfoWatermark(context, bitmap, text); // 更新相册 updatePhotoAlbum(context, newBitmap, file); } catch (Exception e) { e.printStackTrace(); } } }).start(); //开始线程 } /** * 保存到相册 * * @param context 上下文 * @param src 源图片 * @param text 水印文字 */ private Bitmap addInfoWatermark(final Context context, Bitmap src, String text) { //判断图片/水印文字 是否为空 if (isEmptyBitmap(src) || text == null ) { return null; } // 从源图片复制一份 Bitmap ret = src.copy(src.getConfig(), true); // 初始化画笔 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); // 初始化画布 Canvas canvas = new Canvas(ret); // 水印文字:黑色 paint.setColor(Color.BLACK); // 文字大小:19dp paint.setTextSize(dip2px(context, 19)); // 开始绘画 canvas.drawText(text, 10, 10 , paint); // 循环利用资源 if (!src.isRecycled()) { src.recycle(); } return ret; } /** * 保存到相册 * * @param context 上下文 * @param src 源图片 * @param file 要保存到的文件 */ private void savePhotoAlbum(final Context context, Bitmap src, final File file) { //判断图片 是否为空 if (isEmptyBitmap(src)) { return; } // 保存文件 OutputStream outputStream; try { //输出这个文件 outputStream = new BufferedOutputStream(new FileOutputStream(file)); // 压缩 src.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); // 循环利用资源 if (!src.isRecycled()) { src.recycle(); } } catch (FileNotFoundException e) { e.printStackTrace(); } // 更新图库,这个在 Android 6.0 和 Android 10.0 更新图库,存在差异。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10.0 及以上 // 创建 ContentValues 对象,准备插入数据 ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DISPLAY_NAME, file.getName()); values.put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(file)); values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM); ContentResolver contentResolver = context.getContentResolver(); // 插入数据,返回所插入数据对应的Uri Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (uri == null) { return; } try { // 获取刚插入的数据的Uri对应的输出流 outputStream = contentResolver.openOutputStream(uri); FileInputStream fileInputStream = new FileInputStream(file); // 从一个流复制到另一个流上 FileUtils.copy(fileInputStream, outputStream); //关闭流 fileInputStream.close(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } else { // android 6.0 - 10.0 // 扫描文件 MediaScannerConnection.scanFile( context.getApplicationContext(), new String[]{file.getAbsolutePath()}, new String[]{"image/jpeg"}, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String path, Uri uri) { //通知相册更新 // 插入图片 MediaStore.Images.Media.insertImage( context.getContentResolver(), BitmapFactory.decodeFile(path), file.getName(), null); Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); Uri u = Uri.fromFile(file); intent.setData(u); context.sendBroadcast(intent); // 发广播通知,更新相册 } }); } } /** * Bitmap对象是否为空。 */ private static boolean isEmptyBitmap(Bitmap src) { return src == null || src.getWidth() == 0 || src.getHeight() == 0; } /** * 获取 Mime 类型 * * @param file 文件 * @return Mime 类型 */ private static String getMimeType(File file) { FileNameMap fileNameMap = URLConnection.getFileNameMap(); String type = fileNameMap.getContentTypeFor(file.getName()); return type; } /** * 根据手机的分辨率从 px(像素) 的单位 转成为 dp */ public int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); }
以上就是Android 版本、权限适配相关总结的详细内容,更多关于Android 版本、权限适配的资料请关注我们其它相关文章!