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文件共享。

阅读全文
0 0