Android学习之搞搞7.0和6.0uri的不同

来源:互联网 发布:下颌第一磨牙雕刻数据 编辑:程序博客网 时间:2024/06/05 01:07

最近做项目的时候,遇到一个问题,调用系统相册,进行裁剪,在系统7.0的手机上会得不到这个图片,因为说文件不可以获取,在7.0以下就可以
当时负责这个模块的同学是按照一行代码敲的,但是有问题,所以我想研究研究,这是为什么

我先写了一段代码:
是这样的:

 private void openFile(){        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);        intent.setType("image/*");        startActivityForResult(intent,1,null);    }  @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        if(requestCode == 1){            if(resultCode == RESULT_OK){                Uri uri = data.getData();                Log.d("pathPart",uri.toString());                Log.d("pathPart",uri.getPath());                Log.d("pathPart",uri.getAuthority());                Log.d("pathPart",uri.getHost());                Log.d("pathPart",uri.getScheme());            }        }        super.onActivityResult(requestCode, resultCode, data);    }

首先,我想看看系统6.0得到的uri是什么样子的:

D/pathPart: content://com.android.providers.media.documents/document/image%3A712757
D/pathPart: /document/image:712757
D/pathPart: com.android.providers.media.documents
D/pathPart: com.android.providers.media.documents
D/pathPart: content

这里的getPath就是对应的图片在数据库中的存在。
所以接下来我们要去得到这张图片,就需要通过ContentProvider进行数据库的搜索
但是手机上关于图片的类型有很多,我刚刚测试一张我从qq接收的图片,它的uri是关于这样的

D/pathPart: content://media/external/images/media/712719
D/pathPart: /external/images/media/712719
D/pathPart: media
D/pathPart: media
D/pathPart: content

所以在第一行代码中,有一段关于在api19上的代码:
这里写图片描述

首先,我们判断是不是document,因为document的类型的uri是不一样的

content://com.android.providers.media.documents/document/image%3A712757

然后我们再判断是不是媒体文件,还是下载的文件,因为所对应的在系统中的uri还一样

如果是媒体文件,我们先得到id,这个是图片在数据库中的_ID的值
我在思考为什么要将图片的authority进行改变
我打印一下这个:

Log.d(“mediaPath”, MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString());
结果是:
content://media/external/images/media

发现它和图片的uri的authority一样,这是为什么呢?
然后查看源码解释,有一个这样的:

Allow the user to select a particular kind of data and
return it. This is different than {@link #ACTION_PICK} in that here we
just say what kind of data is desired, not a URI of existing data from
which the user can pick.

也就是说通过ACTION_CONTENT返回的uri只是用来说明是那一种类型的数据,而不是现有数据真正的uri。它只有图片的编号
因此我们需要通过系统真正的uri来得到真正的uri,所以有了上面一段代码

再查看mediaStore的源码,关于它的介绍是这样的:

/** * The Media provider contains meta data for all available media on both internal * and external storage devices. */public final class MediaStore {    private final static String TAG = "MediaStore";    public static final String AUTHORITY = "media";    private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";

我们可以看到,它才是安卓给我们封装的真正的媒体提供器,它包含了所有可用媒体在内存储存设备上的数据
因此我们要进行解析!
再通过ContentResolve进行查询,得到真正的文件地址
这里写图片描述

然后我在思考这个DocumentsContract是啥,依旧打开源码

/** * Defines the contract between a documents provider and the platform. * <p> * To create a document provider, extend {@link DocumentsProvider}, which * provides a foundational implementation of this contract. * <p> * All client apps must hold a valid URI permission grant to access documents, * typically issued when a user makes a selection through * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT}, * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}. * * @see DocumentsProvider */public final class DocumentsContract {    private static final String TAG = "DocumentsContract";

它是用来定义文档提供器和平台之间的联系的

然后我们看这个方法的实现:

 /**     * Test if the given URI represents a {@link Document} backed by a     * {@link DocumentsProvider}.     *     * @see #buildDocumentUri(String, String)     * @see #buildDocumentUriUsingTree(Uri, String)     */    public static boolean isDocumentUri(Context context, @Nullable Uri uri) {        if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {            final List<String> paths = uri.getPathSegments();            if (paths.size() == 2) {                return PATH_DOCUMENT.equals(paths.get(0));            } else if (paths.size() == 4) {                return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));            }        }        return false;    }

继续深入,我找到documentProvider的源码

Base class for a document provider. A document provider offers read and write access to durable files, such as files stored on a local disk, or files in a cloud storage service. 

这是对它的介绍
大概说文档提供器访问和写入持久文件,像那些存储在本地磁盘的文件,或者在云服务器上的文件

然后我们再看看真实地址:

  private String getPath(Uri uri,String selection){        String path = "";        ContentResolver resolver = getContentResolver();        Cursor cursor = resolver.query(uri,null,selection,null,null);        if(cursor!=null){            if(cursor.moveToFirst()){                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));            }        }        cursor.close();        Log.d("filePath",path);        return path;    }

通过这段代码:
我得到一张图片的真实地址:

D/filePath: /storage/emulated/0/DCIM/Camera/IMG_20171113_143115.jpg

然后用imageview展示出来

这是我用的我的手机,6.0系统测的
现在我们换成7.0的

如果只是展示图片,没有什么问题:

D/pathPart:content://com.android.providers.media.documents/document/image%3A65
D/pathPart: /document/image:65
D/pathPart: com.android.providers.media.documents
D/pathPart: com.android.providers.media.documents
D/pathPart: content
D/mediaPath: content://media/external/images/medie
D/filePath:/storage/emulated/0/DCIM/Camera/IMG_20171113_112835.jpg

现在我对图片进行裁剪:
我先使用这段代码:

  public void startPhotoZoom(Uri inputUri) {        if (inputUri == null) {           Log.d("eee","The uri is not exist.");            return;        }        Intent intent = new Intent("com.android.camera.action.CROP");        //sdk>=24        mCropFile = new File(getFilesDir().getAbsoluteFile()+"//crop.jpg");            Uri outPutUri = Uri.fromFile(mCropFile);            intent.setDataAndType(inputUri, "image/*");            intent.putExtra(MediaStore.EXTRA_OUTPUT, outPutUri);            intent.putExtra("noFaceDetection", false);//去除默认的人脸识别,否则和剪裁匡重叠            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);        // 设置裁剪        intent.putExtra("crop", "true");        // aspectX aspectY 是宽高的比例        intent.putExtra("aspectX", 1);        intent.putExtra("aspectY", 1);        // outputX outputY 是裁剪图片宽高        intent.putExtra("outputX", 200);        intent.putExtra("outputY", 200);        intent.putExtra("return-data", false);        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());// 图片格式        startActivityForResult(intent, 3);//这里就将裁剪后的图片的Uri返回了    }

在我启动裁剪功能的时候,出现:
这里写图片描述

就是这个坑!!!!!!

那是7.0又加了一些东西

这里写图片描述

我们不能直接用

我在给裁剪代码传递uri的的时候,是这样传的:

  Uri uri1 = Uri.fromFile(new File(imagePath));  //原谅用这个,不知道为啥,看不到log       System.out.println(uri1.toString());        startPhotoZoom(uri1);

这个输出出来是:

file:///storage/emulated/0/Tencent/QQ_Images/1510631227113.jpeg

然后就报错啦,不允许这样用!!!!

怎么解决呢,要将其转成content开头的uri
这个时候就要用到FileProvider了
首先在Manifest中注册provider

  <provider            android:authorities="com.vivian.provider"            android:name="android.support.v4.content.FileProvider"            android:exported="false"            //不允许外界访问            android:grantUriPermissions="true"            >            <meta-data                android:name="android.support.FILE_PROVIDER_PATHS"//固定值                android:resource="@xml/filepath"/>        </provider>

然后在xml文件夹下定义:

<paths>    <!--        xml文件是唯一设置分享的目录 ,不能用代码设置         1.<files-path>        getFilesDir()  /data/data//files目录         2.<cache-path>        getCacheDir()  /data/data//cache目录         3.<external-path>     Environment.getExternalStorageDirectory()         SDCard/Android/data/你的应用的包名/files/ 目录         4.<external-files-path>     Context#getExternalFilesDir(String) Context.getExternalFilesDir(null).         5.<external-cache-path>      Context.getExternalCacheDir().     -->    <!--    path :代表设置的目录下一级目录 eg:<external-path path="images/"                整个目录为Environment.getExternalStorageDirectory()+"/images/"            name: 代表定义在Content中的字段 eg:name = "myimages" ,并且请求的内容的文件名为default_image.jpg                则 返回一个URI   content://com.example.myapp.fileprovider/myimages/default_image.jpg    -->    <!--当path 为空时 5个全配置就可以解决-->    <!--下载apk-->    <external-path path="" name="sdcard_files" />    <!--相机相册裁剪-->    <external-files-path   path="" name="camera_has_sdcard"/>    <files-path path=""     name="camera_no_sdcard"/></paths>

然后更改上面的代码,这里我只是测试7.0的情况

 Uri uri1 = Uri.fromFile(new File(imagePath));        Uri inputuri = FileProvider.getUriForFile(MainActivity.this,"com.vivian.provider",new File(imagePath));       System.out.println(uri1.toString());        System.out.println(inputuri.toString());        startPhotoZoom(inputuri);

然后设置图片:

  Bundle bundle = data.getExtras();               if(bundle!=null){                   Bitmap bitmap = bundle.getParcelable("data");                   mImageView.setImageBitmap(bitmap);               }

我们看看打印的东西:

D/filePath:/storage/emulated/0/DCIM/Camera/IMG_20171113_112733.jpg

System.out:file:///storage/emulated/0/DCIM/Camera/IMG_20171113_112733.jpg

System.out:content://com.vivian.provider/sdcard_files/DCIM/Camera/IMG_20171113_112733.jpg

这个是7.0得到的文件的uri和 转成content uri

  public void startPhotoZoom(Uri inputUri) {        if (inputUri == null) {           Log.d("eee","The uri is not exist.");            return;        }        Intent intent = new Intent("com.android.camera.action.CROP");        //sdk>=24        mCropFile = new File(getFilesDir().getAbsoluteFile()+"//crop.jpg");            Uri outPutUri = Uri.fromFile(mCropFile);            intent.setDataAndType(inputUri, "image/*");            intent.putExtra(MediaStore.EXTRA_OUTPUT, outPutUri);            intent.putExtra("noFaceDetection", false);//去除默认的人脸识别,否则和剪裁匡重叠            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);            // 设置裁剪            intent.putExtra("crop", "true");            // aspectX aspectY 是宽高的比例            intent.putExtra("aspectX", 1);            intent.putExtra("aspectY", 1);            // outputX outputY 是裁剪图片宽高            intent.putExtra("outputX", 200);            intent.putExtra("outputY", 200);            intent.putExtra("return-data", true);            //一定要是true才能返回数据            intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());// 图片格式            startActivityForResult(intent, 3);//这里就将裁剪后的图片的Uri返回了//        }catch (IOException e){//            e.printStackTrace();//        }    }

参考:
http://www.jianshu.com/p/ba57444a7e69

项目地址:
有点乱。。。。。
https://github.com/vivianluomin

原创粉丝点击