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