Android4.4(KITKAT API19)之后文件URI解析
来源:互联网 发布:k均值聚类算法课件 编辑:程序博客网 时间:2024/06/14 10:50
前言
这个月app改版,忙了大半个月。最近在想,之前写的博客有部分都是笔记,纯粹是为了记笔记。我想这样大家看起来有时甚至觉得云里雾里的。以后写博客,尽量就是按照为大家解决问题的方式来写,当然笔记肯定还是会有,但是会尽量解释清楚大概的用途,贴近实际的操作,能让大家从中有收获。
一、从相册中选择照片
这个需求,大家都不陌生,比如做个人中心时拍照或者从相册选择图片。代码也很简单:
package com.example.davidchen.blogdemo.ui.activity;import android.Manifest;import android.content.ContentUris;import android.content.Intent;import android.content.pm.PackageManager;import android.database.Cursor;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.net.Uri;import android.os.Build;import android.os.Bundle;import android.provider.DocumentsContract;import android.provider.MediaStore;import android.support.annotation.NonNull;import android.support.annotation.Nullable;import android.support.annotation.RequiresApi;import android.support.v4.app.ActivityCompat;import android.support.v4.content.ContextCompat;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.view.View;import android.widget.ImageView;import android.widget.Toast;import com.example.davidchen.blogdemo.R;/** * 选择照片 * Created by DavidChen on 2017/8/25. */public class ChoosePhotoActivity extends AppCompatActivity { private static final int REQUEST_WRITE_EXTERNAL_STORAGE = 1; private static final int CHOOSE_PHOTO = 2; private static final String TAG = "ChoosePhotoActivity"; ImageView result; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_choose_photo); result = (ImageView) findViewById(R.id.result); } /** * 请求读写SDK权限 */ public void choosePhoto(View view) { if (ContextCompat.checkSelfPermission(ChoosePhotoActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(ChoosePhotoActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_EXTERNAL_STORAGE); } else { openAlbum(); } } /** * 打开图片浏览器 */ private void openAlbum() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); startActivityForResult(intent, CHOOSE_PHOTO); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openAlbum(); } else { Toast.makeText(this, "拒绝读取SD卡权限将无法获取照片", Toast.LENGTH_SHORT).show(); } break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case CHOOSE_PHOTO: if (resultCode == RESULT_OK) { handleImage(data); } break; } } /** * 处理返回结果 * * @param data onActivityResult 返回结果 */ private void handleImage(Intent data) { Uri uri = data.getData(); Log.i(TAG, "handleImage: " + uri.toString() + " " + Build.VERSION.SDK_INT); String imagePath = getImagePath(uri, null); displayImage(imagePath); } /** * 根据图片路径,显示图片 */ private void displayImage(String imagePath) { if (imagePath != null) { Bitmap bitmap = BitmapFactory.decodeFile(imagePath); result.setImageBitmap(bitmap); } else { Toast.makeText(this, "照片路径为空", Toast.LENGTH_SHORT).show(); } } /** * 使用ContentProvider获取图片路径 * * @param uri 图片uri * @param selection 条件 * @return 图片路径 */ private String getImagePath(Uri uri, String selection) { String path = null; Cursor cursor = getContentResolver().query(uri, null, selection, null, null); if (cursor != null) { if (cursor.moveToFirst()) { path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); } cursor.close(); } return path; }}
布局文件:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="choosePhoto" android:text="选择照片"/> <ImageView android:id="@+id/result" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout>
好了,很简单。我们跑一下(机器:魅族)。
似乎很顺利啊。我们再来选一张看看,这次是从选择器中(有图库、文件等)的文件中选。gg,挂了。
什么鬼???打印一下uri。
content://com.android.providers.media.documents/document/image%3A63335
而之前正确显示的为:content://media/external/images/media/60338。我们正常的格式应该是下面的这样,显示在文件夹中的位置,而后面的数字是图片在数据库中的id。
还不死心。继续测试(测试了几个真机和几个不同Android版本模拟器),大概的几种uri为:
content://media/external/images/media/104
content://com.android.providers.media.documents/document/image%3A11
file:///sdcard/DCIM/Camera/IMG_20170824_235629.jpg
file:///storage/emulated/0/DCIM/Camera/IMG_20170825_105458.jpg
凡是以地址是com.android.providers.meiad.documents的都跑了异常,还有就是file开头的虽然都跑异常,但是imagePath都是空的。
二、问题分析
看了《第一行代码(第2版)》中介绍说选择图片不会返回真实的图片Uri,而是封装过的。查看Android文档,在KitKat的变更中可以看到存储访问框架的变化,更推荐是用content://的形式来指定其访问位置。
三、解决
这个解决方案也是我从《第一行代码》中copy下来的。仅供参考。
全部的代码如下:
package com.example.davidchen.blogdemo.ui.activity;import android.Manifest;import android.content.ContentUris;import android.content.Intent;import android.content.pm.PackageManager;import android.database.Cursor;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.net.Uri;import android.os.Build;import android.os.Bundle;import android.provider.DocumentsContract;import android.provider.MediaStore;import android.support.annotation.NonNull;import android.support.annotation.Nullable;import android.support.annotation.RequiresApi;import android.support.v4.app.ActivityCompat;import android.support.v4.content.ContextCompat;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.view.View;import android.widget.ImageView;import android.widget.Toast;import com.example.davidchen.blogdemo.R;/** * 选择照片 * Created by DavidChen on 2017/8/25. */public class ChoosePhotoActivity extends AppCompatActivity { private static final int REQUEST_WRITE_EXTERNAL_STORAGE = 1; private static final int CHOOSE_PHOTO = 2; private static final String TAG = "ChoosePhotoActivity"; ImageView result; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_choose_photo); result = (ImageView) findViewById(R.id.result); } /** * 请求读写SDK权限 */ public void choosePhoto(View view) { if (ContextCompat.checkSelfPermission(ChoosePhotoActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(ChoosePhotoActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_EXTERNAL_STORAGE); } else { openAlbum(); } } /** * 打开图片浏览器 */ private void openAlbum() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); startActivityForResult(intent, CHOOSE_PHOTO); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openAlbum(); } else { Toast.makeText(this, "拒绝读取SD卡权限将无法获取照片", Toast.LENGTH_SHORT).show(); } break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case CHOOSE_PHOTO: if (resultCode == RESULT_OK) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { handleImageAfterKitKat(data); } else { handleImage(data); } } break; } } /** * API19之后处理内容 * * @param data onActivityResult 返回结果 */ @RequiresApi(api = Build.VERSION_CODES.KITKAT) private void handleImageAfterKitKat(Intent data) { String imagePath = null; Uri uri = data.getData(); if (DocumentsContract.isDocumentUri(this, uri)) { // 如果是document类型的Uri,通过documentId处理 String docId = DocumentsContract.getDocumentId(uri); if ("com.android.providers.media.documents".equals(uri.getAuthority())) { String id = docId.split(":")[1]; // 解析出数字格式id String selection = MediaStore.Images.Media._ID + "=" + id; imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection); } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) { Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId)); imagePath = getImagePath(contentUri, null); } } else if ("content".equalsIgnoreCase(uri.getScheme())) { imagePath = getImagePath(uri, null); } else if ("file".equalsIgnoreCase(uri.getScheme())) { imagePath = uri.getPath(); } displayImage(imagePath); } /** * 处理返回结果 * * @param data onActivityResult 返回结果 */ private void handleImage(Intent data) { Uri uri = data.getData(); String imagePath = getImagePath(uri, null); displayImage(imagePath); } /** * 根据图片路径,显示图片 */ private void displayImage(String imagePath) { if (imagePath != null) { Bitmap bitmap = BitmapFactory.decodeFile(imagePath); result.setImageBitmap(bitmap); } else { Toast.makeText(this, "照片路径为空", Toast.LENGTH_SHORT).show(); } } /** * 使用ContentProvider获取图片路径 * * @param uri 图片uri * @param selection 条件 * @return 图片路径 */ private String getImagePath(Uri uri, String selection) { String path = null; Cursor cursor = getContentResolver().query(uri, null, selection, null, null); if (cursor != null) { if (cursor.moveToFirst()) { path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); } cursor.close(); } return path; }}
ok,所有都正确跑起来了。
如果你只是显示图片,文档中还提供了一种方式:
private void handleImageAfterKitKat(Intent data) { ParcelFileDescriptor parcelFileDescriptor; try { parcelFileDescriptor = getContentResolver().openFileDescriptor(data.getData(), "r"); FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); parcelFileDescriptor.close(); result.setImageBitmap(image); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }}
再多提一句,在7.0之后,如果你使用file://指定文件位置会报异常。为了安全,Android建议使用FileProvider来共享文件。详细使用,请参考我的博客:FileProvider文件共享。
- Android4.4(KITKAT API19)之后文件URI解析
- Android4.4-KitKat源码下载
- Android4.4r1(KitKat)源码下载地址
- google kitkat android4.4 新特性
- android4.4-kitkat的短消息sms mms
- android4.4(kitkat),如何让APP可以直接写入SMS短信
- android4.4及之后写文件到sdcard
- Android4.4:Kitkat给产品团队带来了哪些变化?
- 基于android4.4 KitKat 制作彩色Emoji的字体
- Mac系统下下载Android4.4(KITKAT)源码
- Android4.4上下如何根据uri获取文件的真是路径
- android4.4的文件管理器documentsui源码解析
- android4.4的文件管理器documentsui源码解析
- Android4.4之后的外置SD卡文件读写的解决方法
- Android4.4之后的外置SD卡文件读写的解决方法
- CodeIgniter 核心代码阅读-URI解析文件URI.php
- Android4.4之后SD卡存储方案
- android kitkat(4.4以上)各个版本的特性解析
- mysql的in和not in的用法(特别注意not in结果集中不能有null)
- 8月24日云栖精选夜读:预告|阿里云华北5地域将于十月开放服务 全系25G网络_侧重服务人工智能行业
- Xshell 内网机图形化操作设置
- 方便有效地解决MyEclipse占用电脑内存问题
- Zookeeper 日志输出到指定文件夹
- Android4.4(KITKAT API19)之后文件URI解析
- LCD1602显示屏的驱动设置及例程
- 软件调试
- Linux下常用的压缩与解压命令
- centos6.5 安装rabbitmq
- 数据传递方的法
- 支持持续性滑动动画的图片组件,用来做背景效果 应该不错
- 科技大猜想!10月杭州云栖大会有哪些黑科技会亮相?
- SDL播放视频