Android4.4(KITKAT API19)之后文件URI解析

前言

这个月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文件共享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值