在手机编写自定义内容提供商的照片(部分2) [英] Writing custom Content Provider for photos on phone (part 2)

查看:195
本文介绍了在手机编写自定义内容提供商的照片(部分2)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我工作的一个自定义的内容提供商我的应用程序。这是当然,我承担的Andr​​oid应用程序的一部分,所以请不要指望理做这一切是太大;-)这里的要点是,我了解CP。

我有一个<一个href="http://stackoverflow.com/questions/29675268/writing-custom-content-provider-for-photos-on-phone">$p$pvious帖子里面的推移和与此的,但我想我已经成功地简化了我的问题还真不少。所以,我工作的一个画廊的应用程序。因为我不知道如何以及在何处的缩略图的图像存储在手机上,我决定简单地使用MediaStore.Images.Thumbnails访问大拇指,并告诉他们在我的GridView。

不过,要fullfill说当然的要求,我会写一个PhotoProvider加载单张照片,全屏,在DetailActivity。此外,对于本作一定意义上,而不是仅仅围绕MediaStore.Media.Images的包装,我的延长的数据从JPEG文件中的一些EXIF标签。

已经发现了一个<一个href="http://stackoverflow.com/questions/15865979/how-to-implement-a-contentprovider-for-providing-image-to-gmail-facebook-evern">great张贴在这里的StackOverflow ,并继续缠斗在课堂上提供的源$ C ​​$ C,我想出了下面的类。也许你想看看,帮我出(点我在正确的方向)?

我会支持的URI,是背景:// AUTH /照片/#,以获得特定图像(给予 IMAGE_ID ,从缩略图)。此外,为了使之成为更有趣,我也希望能够到的的数据的EXIF标记 UserComment在背景:// AUTH /照片/#/评论/ * (注释字符串是最后一个参数出现)。这是否看起来合理的?

若干问题:(更新)

  1. 的getType()是混乱的。再次,这是由该应用过程放贷。当返回一个形象,我猜类型应该始终是为image / jpeg (或者PNG)?
  

编辑:学习更多的东西,我现在明白了URI从返回的插入()的方法是,我猜,可选的,但它往往是有益的返回一个链接(即URI)新(插入)的数据!对于我而言,更新EXIF标签之后,我就回来或者或URI来编辑的照片:背景:// AUTH /摄/ 7271 (其中7271 mimicks的 PHOTO_ID )。

下面是我的(未完!)code。请看看,特别是在查询()插入()功能: - )

PhotoContract.java

 包com.example.android.galleri.app.data;

进口android.content.ContentResolver;
进口android.content.ContentUris;
进口android.net.Uri;
进口android.provider.BaseColumns;
进口android.provider.MediaStore;

公共类PhotoContract {

    公共静态最后弦乐CONTENT_AUTHORITY =no.myapp.android.galleri.app;

    公共静态最终乌里BASE_CONTENT_URI = Uri.parse(内容://+ CONTENT_AUTHORITY);

    公共静态最后弦乐PATH_PHOTO =照片;
    公共静态最后弦乐PATH_COMMENT =意见;

    公共静态final类的PhotoEntry {

        公共静态最终乌里CONTENT_URI =
                。BASE_CONTENT_URI.buildUpon()appendPath(PATH_PHOTO).build();

        公共静态最后弦乐COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
        公共静态最后弦乐COLUMN_DATA = MediaStore.Images.Media.DATA;
        公共静态最后弦乐COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
        公共静态最后弦乐COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
        公共静态最后弦乐COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
        公共静态最后弦乐COLUMN_TITLE = MediaStore.Images.Media.TITLE;
        公共静态最后弦乐COLUMN_SIZE = MediaStore.Images.Media.SIZE;
        公共静态最后弦乐COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
        公共静态最后弦乐COLUMN_EXIF_COMMENT =UserComment在;
        公共静态最后弦乐COLUMN_EXIF_AUTHOR =作者;

        //如果这些仅仅是图像/ PNG?
        公共静态最后弦乐CONTENT_TYPE =
                ContentResolver.CURSOR_DIR_BASE_TYPE +/+ CONTENT_AUTHORITY +/+ PATH_PHOTO;
        公共静态最后弦乐CONTENT_ITEM_TYPE =
                ContentResolver.CURSOR_ITEM_BASE_TYPE +/+ CONTENT_AUTHORITY +/+ PATH_PHOTO;

        //使一个URI来特定image_id
        公共静态最终乌里buildPhotoWithId(长photo_id){
            返回CONTENT_URI.buildUpon()appendPath(Long.toString(photo_id))建立()。;
        }

        //因为我们将重定向对MediaStore的URI,我们需要能够提取IMAGE_ID
        公共静态长getImageIdFromUri(URI URI){
            返回的Long.parseLong(uri.getPathSegments()得到(1)。); // TODO:这是1位?
        }

        //获取注释中的EXIF标签设置
        公共静态字符串getCommentFromUri(URI URI){
            返回uri.getPathSegments()获取(2)。 // TODO:这是位置2?
        }
    }
}
 

PhotoProvider.java:

 包com.example.android.galleri.app.data;

进口android.content.ContentProvider;
进口android.content.ContentValues​​;
进口android.content.UriMatcher;
进口android.database.Cursor;
进口android.database.MatrixCursor;
进口android.graphics.Bitmap;
进口android.graphics.BitmapFactory;
进口android.media.ExifInterface;
进口android.net.Uri;
进口android.provider.MediaStore;
进口android.support.v4.content.CursorLoader;
进口android.util.Log;

进口java.io.IOException异常;


公共类PhotoProvider扩展ContentProvider的{

    //使用这个内容提供商的URI匹配器。
    私有静态最后UriMatcher sUriMatcher = buildUriMatcher();

    静态最终诠释照片= 100;
    静态最终诠释PHOTO_SET_COMMENT = 200;

    静态UriMatcher buildUriMatcher(){
        // 1)code传递到构造重新presents的code返回根
        // URI。这是常见的使用NO_MATCH为code这种情况。添加以下构造。
        最后UriMatcher匹配=新UriMatcher(UriMatcher.NO_MATCH);
        最后弦乐权威= PhotoContract.CONTENT_AUTHORITY;

        // 2)使用addURI功能来满足每一种类型的。使用从常量
        // WeatherContract来帮助定义类型的UriMatcher。

        //匹配照片/&LT;任何数字&GT;这意味着任何有照片的证件
        matcher.addURI(权威,PhotoContract.PATH_PHOTO +/#,照片);

        //匹配照片/&LT;照片的身份证件和GT; /评论/&LT;任何意见&GT;
        matcher.addURI(权威,PhotoContract.PATH_PHOTO +/#/+ PhotoContract.PATH_COMMENT +/ *,PHOTO_SET_COMMENT);

        // 3)返回新的匹配!
        返回匹配;
    }


    @覆盖
    公共字符串的getType(URI URI){

        //使用URI匹配器,以确定什么样的URI的这是。
        最终诠释比赛= sUriMatcher.match(URI);

        开关(匹配){
            案例PHOTO_SET_COMMENT:
                返回PhotoContract.PhotoEntry.CONTENT_TYPE;
            案例图片:
                返回PhotoContract.PhotoEntry.CONTENT_TYPE;
            默认:
                抛出新UnsupportedOperationException异常(未知URI:+ URI);
        }
    }


    @覆盖
    公共布尔的onCreate(){
        返回true; // 够了?
    }


    @覆盖
    公共光标查询(URI URI,字符串[]投影,选择字符串,字符串[] selectionArgs两个,串排序顺序){
        MatrixCursor retCursor =新MatrixCursor(投影);

        //通过MediaStore打开指定的图像来获得基本列
        //通过ExifInterface然后打开图像文件以获取详细列
        长IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(URI);

        //开放的基本URI = Uri.parse(内容://媒体/外部/图片/媒体);
        开放的基本URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        基本URI = Uri.withAppendedPath(基本URI,+ IMAGE_ID);

        的String [] MS_projection = {
                MediaStore.Images.Media.DISPLAY_NAME,
                MediaStore.Images.Media.DATA,
                MediaStore.Images.Media.DESCRIPTION,
                MediaStore.Images.Media.DATE_TAKEN,
                MediaStore.Images.Media.DATE_ADDED,
                MediaStore.Images.Media.TITLE,
                MediaStore.Images.Media.SIZE,
                MediaStore.Images.Media.ORIENTATION};

        // http://androidsnippets.com/get-file-path-of-gallery-image
        光标C =的getContext()getContentResolver()查询(基本URI,MS_projection,NULL,NULL,NULL);

        //倾倒场(我们想要的 - 假设现在我们希望所有领域,在相同的顺序)到MatrixCursor
        Object []对象行=新的对象[projection.length]
        行[0] = c.getString(0); // 显示名称
        行[1] = c.getBlob(1); // 等等
        行[2] = c.getString(2);
        行[3] = c.getLong(3);
        行[4] = c.getLong(4);
        行[5] = c.getString(5);
        行[6] = c.getInt(6);
        行[7] = c.getInt(7);

        //注意!额外+2领域,为EXIF数据。
        尝试 {
            ExifInterface EXIF​​ =新ExifInterface((字符串)行[1]);
            行[8] = exif.getAttribute(UserComment在);
            行[9] = exif.getAttribute(作者);

        }赶上(IOException异常E){
            e.printStackTrace();
        }

        retCursor.addRow(行);

        返回retCursor;
    }

    @覆盖
    公众开放的插入(URI URI,ContentValues​​值){
        串comment_to_set = PhotoContract.PhotoEntry.getCommentFromUri(URI);
        长IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(URI);

        开放的基本URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        基本URI = Uri.withAppendedPath(基本URI,+ IMAGE_ID);

        //获取数据(路径/文件名)从MediaStore  - 只需要一个特定的信息
        的String [] MS_projection = {MediaStore.Images.Media.DATA};

        // http://androidsnippets.com/get-file-path-of-gallery-image
        光标C =的getContext()getContentResolver()查询(基本URI,MS_projection,NULL,NULL,NULL);
        串thumbData = c.getString(0);

        尝试 {
            ExifInterface EXIF​​ =新ExifInterface(thumbData);

            exif.setAttribute(UserComment在comment_to_set);
            exif.saveAttributes();

        }赶上(IOException异常E){
            e.printStackTrace();
        }
        返回PhotoContract.PhotoEntry.buildPhotoWithId(IMAGE_ID); //返回URI这个特定的图像
    }

    @覆盖
    公众诠释删除(URI URI,弦乐选择,字符串[] selectionArgs两个){
        返回0;
    }

    @覆盖
    公众诠释更新(开放的我们的URI,ContentValues​​价值观,选择字符串,字符串[] selectionArgs两个){
        返回0;
    }
}
 

解决方案

我相信我设法弄清楚了这一点,它是没有那么神秘毕竟。编写自定义内容,在这种情况下,提供真正归结为实现自己的查询更新的方法。我并不需要任何插入删除,因为我是唯一的阅读的的MediaStore。

要开始,我设计了两个URI;一个用于查询,一个用于更新。查询URI遵循的模式,内容:// AUTH /照片/#,其中#是从MediaStore.Images.Thumbnails的IMAGE_ID。这种方法实质上违背MediaStore.Images.Media另一个查询,以获得特定的图像上的标准的数据,但通过 ExifInterface 。所有这一切都是在返回 MatrixCursor

更新URI匹配的模式,内容:// AUTH /照片/#/注释,然后设置的(写)的 UserComment在 EXIF​​标签与标识文件中 - 再次通过 ExifInterface

要回答我的问题,我是从我的PhotoContract和PhotoProvider类发布更新code。从本质上讲,如果你正在写它做了内容提供商的没有的接口SQLite数据库 - 但别的设备上 - 所有你需要做的是在你提供适当的方法来实现这些操作类。

PhotoContract

 进口android.content.ContentResolver;
进口android.content.ContentUris;
进口android.net.Uri;
进口android.provider.BaseColumns;
进口android.provider.MediaStore;

公共类PhotoContract {

    公共静态最后弦乐CONTENT_AUTHORITY =com.example.android.myFunkyApp.app;

    公共静态最终乌里BASE_CONTENT_URI = Uri.parse(内容://+ CONTENT_AUTHORITY);

    公共静态最后弦乐PATH_PHOTO =照片;
    公共静态最后弦乐PATH_COMMENT =意见;
    //公共静态最后弦乐PATH_AUTHOR =作者;

    公共静态final类ThumbEntry {
        公共静态最后弦乐COLUMN_THUMB_ID = MediaStore.Images.Thumbnails._ID;
        公共静态最后弦乐COLUMN_DATA = MediaStore.Images.Thumbnails.DATA;
        公共静态最后弦乐COLUMN_IMAGE_ID = MediaStore.Images.Thumbnails.IMAGE_ID;
    }

    公共静态final类的PhotoEntry {

        公共静态最终乌里CONTENT_URI =
                。BASE_CONTENT_URI.buildUpon()appendPath(PATH_PHOTO).build();

        公共静态最后弦乐COLUMN_IMAGE_ID = MediaStore.Images.Media._ID;
        公共静态最后弦乐COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
        公共静态最后弦乐COLUMN_DATA = MediaStore.Images.Media.DATA;
        公共静态最后弦乐COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
        公共静态最后弦乐COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
        公共静态最后弦乐COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
        公共静态最后弦乐COLUMN_TITLE = MediaStore.Images.Media.TITLE;
        公共静态最后弦乐COLUMN_SIZE = MediaStore.Images.Media.SIZE;
        公共静态最后弦乐COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
        公共静态最后弦乐COLUMN_EXIF_COMMENT =UserComment在;
        //公共静态最后弦乐COLUMN_EXIF_AUTHOR =作者;

        公共静态最后弦乐CONTENT_TYPE =
                ContentResolver.CURSOR_DIR_BASE_TYPE +/+ CONTENT_AUTHORITY +/+ PATH_PHOTO;
        公共静态最后弦乐CONTENT_ITEM_TYPE =
                ContentResolver.CURSOR_ITEM_BASE_TYPE +/+ CONTENT_AUTHORITY +/+ PATH_PHOTO;

        //使一个URI来特定image_id
        公共静态最终乌里buildPhotoUriWithId(长photo_id){
            返回CONTENT_URI.buildUpon()appendPath(Long.toString(photo_id))建立()。;
        }

        //因为我们将重定向对MediaStore的URI,我们需要能够提取IMAGE_ID
        公共静态长getImageIdFromUri(URI URI){
            返回的Long.parseLong(uri.getPathSegments()得到(1)。); //总是在位置1
        }

    }
}
 

PhotoProvider

 进口android.content.ContentProvider;
进口android.content.ContentValues​​;
进口android.content.UriMatcher;
进口android.database.AbstractWindowedCursor;
进口android.database.Cursor;
进口android.database.CursorWindow;
进口android.database.CursorWrapper;
进口android.database.MatrixCursor;
进口android.graphics.Bitmap;
进口android.graphics.BitmapFactory;
进口android.media.ExifInterface;
进口android.net.Uri;
进口android.provider.MediaStore;
进口android.support.v4.content.CursorLoader;
进口android.util.Log;

进口java.io.IOException异常;
进口java.lang.reflect.Field中;
进口java.util.Arrays中;
进口的java.util.List;

公共类PhotoProvider扩展ContentProvider的{

    //使用这个内容提供商的URI匹配器。
    私有静态最后UriMatcher sUriMatcher = buildUriMatcher();

    静态最终诠释照片= 100;
    静态最终诠释PHOTO_COMMENT = 101;
    静态最终诠释PHOTO_AUTHOR = 102;

    静态UriMatcher buildUriMatcher(){
        最后UriMatcher匹配=新UriMatcher(UriMatcher.NO_MATCH);
        最后弦乐权威= PhotoContract.CONTENT_AUTHORITY;

        //匹配照片/&LT;任何数字&GT;这意味着任何有照片的证件
        matcher.addURI(权威,PhotoContract.PATH_PHOTO +/#,照片);

        //匹配照片/&LT;照片的身份证件和GT; /评论/(在ContentValues​​注释文本)
        matcher.addURI(权威,PhotoContract.PATH_PHOTO +/#/+ PhotoContract.PATH_COMMENT,PHOTO_COMMENT);
        //匹配照片/&LT;照片的身份证件和GT; /作家/(在ContentValues​​作者姓名)
        //matcher.addURI(authority,PhotoContract.PATH_PHOTO +/#/+ PhotoContract.PATH_AUTHOR,PHOTO_AUTHOR);

        返回匹配;
    }



    @覆盖
    公共字符串的getType(URI URI){

        //使用URI匹配器,以确定什么样的URI的这是。
        最终诠释比赛= sUriMatcher.match(URI);

        //注意:我们总是返回数据单列,所以内容类型始终是一个目录
        开关(匹配){
            案例PHOTO_COMMENT:
                返回PhotoContract.PhotoEntry.CONTENT_TYPE;
            案例PHOTO_AUTHOR:
                返回PhotoContract.PhotoEntry.CONTENT_TYPE;
            案例图片:
                返回PhotoContract.PhotoEntry.CONTENT_TYPE;
            默认:
                抛出新UnsupportedOperationException异常(未知URI:+ URI);
        }
    }


    @覆盖
    公共布尔的onCreate(){
        返回true;
    }


    @覆盖
    公共光标查询(URI URI,字符串[]投影,选择字符串,字符串[] selectionArgs两个,串排序顺序){
        MatrixCursor retCursor =新MatrixCursor(投影);

        //通过MediaStore打开指定的图像来获得基本列
        //通过ExifInterface然后打开图像文件以获取详细列
        长IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(URI);
        开放的基本URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        基本URI = Uri.withAppendedPath(基本URI,+ IMAGE_ID);

        // http://androidsnippets.com/get-file-path-of-gallery-image
        //运行对MediaStore查询,投影= null表示让所有的字段
        光标C =的getContext()getContentResolver()查询(基本URI,NULL,NULL,NULL,NULL);
        如果(!c.moveToFirst()){
            返回null;
        }
        //返回比赛对投影领域,并复制到行[]
        Object []对象行=新的对象[projection.length]

        INT I = 0;

        / * // Cursor.getType()需要API级&GT; 10 ...
        对于(字符串COLNAME:投影){
            INT IDX = c.getColumnIndex(COLNAME);
            如果(IDX&LT; = 0)返回NULL; // 错误
            INT colType = c.getType(IDX);
            开关(colType){
                案例Cursor.FIELD_TYPE_INTEGER:{
                    行[我+ +] = c.getLong(IDX);
                    打破;
                }
                案例Cursor.FIELD_TYPE_FLOAT:{
                    行[我+ +] = c.getFloat(IDX);
                    打破;
                }
                案例Cursor.FIELD_TYPE_STRING:{
                    行[我+ +] = c.getString(IDX);
                    打破;
                }
                案例Cursor.FIELD_TYPE_BLOB:{
                    行[我+ +] = c.getBlob(IDX);
                    打破;
                }
            }
        }
        * /

        //http://stackoverflow.com/questions/11658239/cursor-gettype-for-api-level-11
        CursorWrapper CW =(CursorWrapper)C;
        类&LT;&GT; cursorWrapper = CursorWrapper.class;
        现场mCursor = NULL;
        尝试 {
            mCursor = cursorWrapper.getDeclaredField(mCursor);
            mCursor.setAccessible(真正的);
            AbstractWindowedCursor abstractWindowedCursor =(AbstractWindowedCursor)mCursor.get(CW);
            CursorWindow cursorWindow = abstractWindowedCursor.getWindow();
            INT POS = abstractWindowedCursor.getPosition();
            //注意!预计导致光标移动到包含相同的顺序数据投影!
            对于(字符串COLNAME:投影){
                INT IDX = c.getColumnIndex(COLNAME);

                //简单的解决办法:如果列名在MediaStore没找到,以为这是一个EXIF标签
                //并跳过
                如果(IDX&GT; = 0){
                    如果(cursorWindow.isNull(POS,IDX)){
                        //Cursor.FIELD_TYPE_NULL
                        行[我+ +] = NULL;
                    }否则,如果(cursorWindow.isLong(POS,IDX)){
                        //Cursor.FIELD_TYPE_INTEGER
                        行[我+ +] = c.getLong(IDX);
                    }否则,如果(cursorWindow.isFloat(POS,IDX)){
                        //Cursor.FIELD_TYPE_FLOAT
                        行[我+ +] = c.getFloat(IDX);
                    }否则,如果(cursorWindow.isString(POS,IDX)){
                        //Cursor.FIELD_TYPE_STRING
                        行[我+ +] = c.getString(IDX);
                    }否则,如果(cursorWindow.isBlob(POS,IDX)){
                        //Cursor.FIELD_TYPE_BLOB
                        行[我+ +] = c.getBlob(IDX);
                    }
                }
            }
        }赶上(例外五){
            e.printStackTrace();
        }

        //现在已经处理了第i个字段的投影。如果有更多的,我们期望
        //这些是有效的EXIF标签。显然应该使这个更强大的...
        尝试 {
            ExifInterface EXIF​​ =新ExifInterface((字符串)行[2]);
            而(I&LT; projection.length){
                行[I] = exif.getAttribute(UserComment在); //投影[I]); //字符串(或空)
                我++;
            }
        }赶上(IOException异常E){
            e.printStackTrace();
        }

        retCursor.addRow(行);
        返回retCursor;
    }


    @覆盖
    公众开放的插入(URI URI,ContentValues​​值){
        返回null;
    }

    @覆盖
    公众诠释删除(URI URI,弦乐选择,字符串[] selectionArgs两个){
        返回0;
    }

    @覆盖
    公众诠释更新(开放的我们的URI,ContentValues​​价值观,选择字符串,字符串[] selectionArgs两个){
        // URI标识IMAGE_ID并EXIF标签设置;内容:// AUTH /照片/&LT; image_id&GT; /注释或/作者

        //首先,获得IMAGE_ID和prepare URI(获得从MediaStore文件路径)
        长IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(URI);
        开放的基本URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        基本URI = Uri.withAppendedPath(基本URI,+ IMAGE_ID);

        //获取数据(路径/文件名)从MediaStore  - 只需要一个特定的信息
        的String [] MS_projection = {MediaStore.Images.Media.DATA};

        // http://androidsnippets.com/get-file-path-of-gallery-image
        光标C =的getContext()getContentResolver()查询(基本URI,MS_projection,NULL,NULL,NULL);
        (!c.moveToFirst()),如果返回-1; //不能得到图像路径/文件名...
        串thumbData = c.getString(0);

        //然后,用URIMatcher标识操作(即,要设置其标签)
        最终诠释比赛= sUriMatcher.match(URI);

        字符串EXIF_tag;
        开关(匹配){
            案例PHOTO_COMMENT:{
                EXIF_tag =UserComment在;
                打破;
            }
            案例PHOTO_AUTHOR:{
                EXIF_tag =作者;
                打破;
            }
            默认:
                抛出新UnsupportedOperationException异常(未知URI:+ URI);
        }
        如果(EXIF_tag == NULL)返回-1;

        //最后,在图像设置标签
        尝试 {
            ExifInterface EXIF​​ =新ExifInterface(thumbData);
            串textToSet = values​​.get(EXIF_tag)的ToString();

            如果(textToSet == NULL)
                抛出新的IOException异常(无法检索的文本从ContentValues​​设置,关键=+ EXIF​​_tag);
            如果(textToSet.length()&GT; 48)
                抛出新的IOException异常(数据太长(+ textToSet.length()+),关键=+ EXIF​​_tag);

            exif.setAttribute(EXIF_tag,textToSet);
            exif.saveAttributes();

        }赶上(IOException异常E){
            e.printStackTrace();
        }
        返回1; // 1图像更新
    }
}
 

I'm working on a custom Content Provider for my app. This is part of a course I'm taking on Android apps, so please don't expect the rationale for doing all this to be too great ;-) The whole point here is for me to learn about CP.

I've got a previous post which goes on and on with this, but I think I've managed to simplify my problem quite a bit. So, I'm working on a "gallery app". Since I don't know how and where thumbnail images are stored on the phone, I've decided to simply use MediaStore.Images.Thumbnails to access the thumbs, and show them in my GridView.

However, to fullfill the requirements of said course, I'll write a "PhotoProvider" to load single photos, full screen, in the DetailActivity. Also, for this to make some sense, and not just be a wrapper around MediaStore.Media.Images, I'm extending that data with some EXIF tags from the JPEG file.

Having found a great post here on StackOverflow, and continuing to wrangle the source code provided in class, I've come up with the following classes. Maybe you want to take a look, and help me out (point me in the right direction)?

The URIs I'll support, are context://AUTH/photo/#, to get a specific image (given an IMAGE_ID, from the thumbnail). Furthermore, to make it a bit more interesting, I also want to be able to write data to the EXIF tag UserComment: context://AUTH/photo/#/comment/* (the comment String is the last parameter there). Does that look sensible?

Some questions: (updated)

  1. getType() is confusing. Again, this is lending from the app course. When returning an image, I guess the type should always be image/jpeg (or maybe PNG)?

Edit: Learning more stuff, I now understand that the URI returned from the insert() method is, I guess, optional, but it often useful to return a "link" (i.e. URI) to the new (inserted) data! For my case, after updating the EXIF tags, I could return either null, or an URI to the edited photo: context://AUTH/photo/7271 (where 7271 mimicks the PHOTO_ID).

Below is my (unfinished!) code. Please have a look, especially at the query() and insert() functions :-)

PhotoContract.java

package com.example.android.galleri.app.data;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore;

public class PhotoContract {

    public static final String CONTENT_AUTHORITY = "no.myapp.android.galleri.app";

    public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

    public static final String PATH_PHOTO = "photo";
    public static final String PATH_COMMENT = "comment";

    public static final class PhotoEntry {

        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_PHOTO).build();

        public static final String COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
        public static final String COLUMN_DATA = MediaStore.Images.Media.DATA;
        public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
        public static final String COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
        public static final String COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
        public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
        public static final String COLUMN_SIZE = MediaStore.Images.Media.SIZE;
        public static final String COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
        public static final String COLUMN_EXIF_COMMENT = "UserComment";
        public static final String COLUMN_EXIF_AUTHOR = "Author";

        // should these simply be image/png??
        public static final String CONTENT_TYPE =
                ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
        public static final String CONTENT_ITEM_TYPE =
                ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;

        // makes an URI to a specific image_id
        public static final Uri buildPhotoWithId(Long photo_id) {
            return CONTENT_URI.buildUpon().appendPath(Long.toString(photo_id)).build();
        }

        // since we will "redirect" the URI towards MediaStore, we need to be able to extract IMAGE_ID
        public static Long getImageIdFromUri(Uri uri) {
            return Long.parseLong(uri.getPathSegments().get(1));  // TODO: is it position 1??
        }

        // get comment to set in EXIF tag
        public static String getCommentFromUri(Uri uri) {
            return uri.getPathSegments().get(2);  // TODO: is it position 2??
        }
    }
}

PhotoProvider.java:

package com.example.android.galleri.app.data;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.content.CursorLoader;
import android.util.Log;

import java.io.IOException;


public class PhotoProvider extends ContentProvider {

    // The URI Matcher used by this content provider.
    private static final UriMatcher sUriMatcher = buildUriMatcher();

    static final int PHOTO = 100;
    static final int PHOTO_SET_COMMENT = 200;

    static UriMatcher buildUriMatcher() {
        // 1) The code passed into the constructor represents the code to return for the root
        // URI.  It's common to use NO_MATCH as the code for this case. Add the constructor below.
        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
        final String authority = PhotoContract.CONTENT_AUTHORITY;

        // 2) Use the addURI function to match each of the types.  Use the constants from
        // WeatherContract to help define the types to the UriMatcher.

        // matches photo/<any number> meaning any photo ID
        matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#", PHOTO);

        // matches photo/<photo id>/comment/<any comment>
        matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_COMMENT + "/*", PHOTO_SET_COMMENT);

        // 3) Return the new matcher!
        return matcher;
    }


    @Override
    public String getType(Uri uri) {

        // Use the Uri Matcher to determine what kind of URI this is.
        final int match = sUriMatcher.match(uri);

        switch (match) {
            case PHOTO_SET_COMMENT:
                return PhotoContract.PhotoEntry.CONTENT_TYPE;
            case PHOTO:
                return PhotoContract.PhotoEntry.CONTENT_TYPE;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
    }


    @Override
    public boolean onCreate() {
        return true;  // enough?
    }


    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        MatrixCursor retCursor = new MatrixCursor(projection);

        // open the specified image through the MediaStore to get base columns
        // then open image file through ExifInterface to get detail columns
        Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);

        //Uri baseUri = Uri.parse("content://media/external/images/media");
        Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);

        String[] MS_projection = {
                MediaStore.Images.Media.DISPLAY_NAME,
                MediaStore.Images.Media.DATA,
                MediaStore.Images.Media.DESCRIPTION,
                MediaStore.Images.Media.DATE_TAKEN,
                MediaStore.Images.Media.DATE_ADDED,
                MediaStore.Images.Media.TITLE,
                MediaStore.Images.Media.SIZE,
                MediaStore.Images.Media.ORIENTATION};

        // http://androidsnippets.com/get-file-path-of-gallery-image
        Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);

        // dump fields (the ones we want -- assuming for now we want ALL fields, in SAME ORDER) into MatrixCursor
        Object[] row = new Object[projection.length];
        row[0] = c.getString(0);  // DISPLAY_NAME
        row[1] = c.getBlob(1);  // etc
        row[2] = c.getString(2);
        row[3] = c.getLong(3);
        row[4] = c.getLong(4);
        row[5] = c.getString(5);
        row[6] = c.getInt(6);
        row[7] = c.getInt(7);

        // NB! Extra +2 fields, for EXIF data.
        try {
            ExifInterface exif = new ExifInterface((String)row[1]);
            row[8] = exif.getAttribute("UserComment");
            row[9] = exif.getAttribute("Author");

        } catch (IOException e) {
            e.printStackTrace();
        }

        retCursor.addRow(row);

        return retCursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        String comment_to_set = PhotoContract.PhotoEntry.getCommentFromUri(uri);
        Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);

        Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);

        // get DATA (path/filename) from MediaStore -- only need that specific piece of information
        String[] MS_projection = {MediaStore.Images.Media.DATA};

        // http://androidsnippets.com/get-file-path-of-gallery-image
        Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
        String thumbData = c.getString(0);

        try {
            ExifInterface exif = new ExifInterface(thumbData);

            exif.setAttribute("UserComment", comment_to_set);
            exif.saveAttributes();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return PhotoContract.PhotoEntry.buildPhotoWithId(IMAGE_ID);  // return URI to this specific image
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
}

解决方案

I believe I managed to figure this out, and it wasn't so mysterious after all. Writing a custom content, in this case, provider really boiled down to implementing my own query and update methods. I didn't need any insert or delete, since I'm only reading the MediaStore.

To get started, I designed two URIs; one for query and one for update. The query URI followed the pattern, content://AUTH/photo/#, where # is the IMAGE_ID from MediaStore.Images.Thumbnails. This method essentially runs another query against MediaStore.Images.Media, to get the "standard" data on the specific image, but then gets some additional meta data on the image via the ExifInterface. All this is returned in a MatrixCursor.

The update URI matches the pattern, content://AUTH/photo/#/comment, which then sets (writes) the UserComment EXIF tag in the file with that ID -- again via the ExifInterface.

To answer my question, I'm posting updated code from my PhotoContract and PhotoProvider classes. Essentially, if you're writing a content provider which does not interface the SQLite database -- but something else on the device -- all you need to do is implement those operations in the appropriate methods in your provider class.

PhotoContract

import android.content.ContentResolver;
import android.content.ContentUris;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore;

public class PhotoContract {

    public static final String CONTENT_AUTHORITY = "com.example.android.myFunkyApp.app";

    public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

    public static final String PATH_PHOTO = "photo";
    public static final String PATH_COMMENT = "comment";
    //public static final String PATH_AUTHOR = "author";

    public static final class ThumbEntry {
        public static final String COLUMN_THUMB_ID = MediaStore.Images.Thumbnails._ID;
        public static final String COLUMN_DATA = MediaStore.Images.Thumbnails.DATA;
        public static final String COLUMN_IMAGE_ID = MediaStore.Images.Thumbnails.IMAGE_ID;
    }

    public static final class PhotoEntry {

        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_PHOTO).build();

        public static final String COLUMN_IMAGE_ID = MediaStore.Images.Media._ID;
        public static final String COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
        public static final String COLUMN_DATA = MediaStore.Images.Media.DATA;
        public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
        public static final String COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
        public static final String COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
        public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
        public static final String COLUMN_SIZE = MediaStore.Images.Media.SIZE;
        public static final String COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
        public static final String COLUMN_EXIF_COMMENT = "UserComment";
        //public static final String COLUMN_EXIF_AUTHOR = "Author";

        public static final String CONTENT_TYPE =
                ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
        public static final String CONTENT_ITEM_TYPE =
                ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;

        // makes an URI to a specific image_id
        public static final Uri buildPhotoUriWithId(Long photo_id) {
            return CONTENT_URI.buildUpon().appendPath(Long.toString(photo_id)).build();
        }

        // since we will "redirect" the URI towards MediaStore, we need to be able to extract IMAGE_ID
        public static Long getImageIdFromUri(Uri uri) {
            return Long.parseLong(uri.getPathSegments().get(1));  // always in position 1
        }

    }
}

PhotoProvider

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.AbstractWindowedCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.content.CursorLoader;
import android.util.Log;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

public class PhotoProvider extends ContentProvider {

    // The URI Matcher used by this content provider.
    private static final UriMatcher sUriMatcher = buildUriMatcher();

    static final int PHOTO = 100;
    static final int PHOTO_COMMENT = 101;
    static final int PHOTO_AUTHOR = 102;

    static UriMatcher buildUriMatcher() {
        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
        final String authority = PhotoContract.CONTENT_AUTHORITY;

        // matches photo/<any number> meaning any photo ID
        matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#", PHOTO);

        // matches photo/<photo id>/comment/ (comment text in ContentValues)
        matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_COMMENT, PHOTO_COMMENT);
        // matches photo/<photo id>/author/ (author name in ContentValues)
        //matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_AUTHOR, PHOTO_AUTHOR);

        return matcher;
    }



    @Override
    public String getType(Uri uri) {

        // Use the Uri Matcher to determine what kind of URI this is.
        final int match = sUriMatcher.match(uri);

        // Note: We always return single row of data, so content-type is always "a dir"
        switch (match) {
            case PHOTO_COMMENT:
                return PhotoContract.PhotoEntry.CONTENT_TYPE;
            case PHOTO_AUTHOR:
                return PhotoContract.PhotoEntry.CONTENT_TYPE;
            case PHOTO:
                return PhotoContract.PhotoEntry.CONTENT_TYPE;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
    }


    @Override
    public boolean onCreate() {
        return true;
    }


    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        MatrixCursor retCursor = new MatrixCursor(projection);

        // open the specified image through the MediaStore to get base columns
        // then open image file through ExifInterface to get detail columns
        Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
        Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);

        // http://androidsnippets.com/get-file-path-of-gallery-image
        // run query against MediaStore, projection = null means "get all fields"
        Cursor c = getContext().getContentResolver().query(baseUri, null, null, null, null);
        if (!c.moveToFirst()) {
            return null;
        }
        // match returned fields against projection, and copy into row[]
        Object[] row = new Object[projection.length];

        int i = 0;

        /* // Cursor.getType() Requires API level > 10...
        for (String colName : projection) {
            int idx = c.getColumnIndex(colName);
            if (idx <= 0) return null; // ERROR
            int colType = c.getType(idx);
            switch (colType) {
                case Cursor.FIELD_TYPE_INTEGER: {
                    row[i++] = c.getLong(idx);
                    break;
                }
                case Cursor.FIELD_TYPE_FLOAT: {
                    row[i++] = c.getFloat(idx);
                    break;
                }
                case Cursor.FIELD_TYPE_STRING: {
                    row[i++] = c.getString(idx);
                    break;
                }
                case Cursor.FIELD_TYPE_BLOB: {
                    row[i++] = c.getBlob(idx);
                    break;
                }
            }
        }
        */

        //http://stackoverflow.com/questions/11658239/cursor-gettype-for-api-level-11
        CursorWrapper cw = (CursorWrapper)c;
        Class<?> cursorWrapper = CursorWrapper.class;
        Field mCursor = null;
        try {
            mCursor = cursorWrapper.getDeclaredField("mCursor");
            mCursor.setAccessible(true);
            AbstractWindowedCursor abstractWindowedCursor = (AbstractWindowedCursor)mCursor.get(cw);
            CursorWindow cursorWindow = abstractWindowedCursor.getWindow();
            int pos = abstractWindowedCursor.getPosition();
            // NB! Expect resulting cursor to contain data in same order as projection!
            for (String colName : projection) {
                int idx = c.getColumnIndex(colName);

                // simple solution: If column name NOT FOUND in MediaStore, assume it's an EXIF tag
                // and skip
                if (idx >= 0) {
                    if (cursorWindow.isNull(pos, idx)) {
                        //Cursor.FIELD_TYPE_NULL
                        row[i++] = null;
                    } else if (cursorWindow.isLong(pos, idx)) {
                        //Cursor.FIELD_TYPE_INTEGER
                        row[i++] = c.getLong(idx);
                    } else if (cursorWindow.isFloat(pos, idx)) {
                        //Cursor.FIELD_TYPE_FLOAT
                        row[i++] = c.getFloat(idx);
                    } else if (cursorWindow.isString(pos, idx)) {
                        //Cursor.FIELD_TYPE_STRING
                        row[i++] = c.getString(idx);
                    } else if (cursorWindow.isBlob(pos, idx)) {
                        //Cursor.FIELD_TYPE_BLOB
                        row[i++] = c.getBlob(idx);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // have now handled the first i fields in projection. If there are any more, we expect
        // these to be valid EXIF tags. Should obviously make this more robust...
        try {
            ExifInterface exif = new ExifInterface((String) row[2]);
            while (i < projection.length) {
                row[i] = exif.getAttribute("UserComment"); //projection[i]);  // String (or null)
                i++;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        retCursor.addRow(row);
        return retCursor;
    }


    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        // URI identifies IMAGE_ID and which EXIF tag to set; content://AUTH/photo/<image_id>/comment or /author

        // first, get IMAGE_ID and prepare URI (to get file path from MediaStore)
        Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
        Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);

        // get DATA (path/filename) from MediaStore -- only need that specific piece of information
        String[] MS_projection = {MediaStore.Images.Media.DATA};

        // http://androidsnippets.com/get-file-path-of-gallery-image
        Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
        if (!c.moveToFirst()) return -1;  // can't get image path/filename...
        String thumbData = c.getString(0);

        // then, use URIMatcher to identify the "operation" (i.e., which tag to set)
        final int match = sUriMatcher.match(uri);

        String EXIF_tag;
        switch (match) {
            case PHOTO_COMMENT: {
                EXIF_tag = "UserComment";
                break;
            }
            case PHOTO_AUTHOR: {
                EXIF_tag = "Author";
                break;
            }
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        if (EXIF_tag == null) return -1;

        // finally, set tag in image
        try {
            ExifInterface exif = new ExifInterface(thumbData);
            String textToSet = values.get(EXIF_tag).toString();

            if (textToSet == null)
                throw new IOException("Can't retrieve text to set from ContentValues, on key = " + EXIF_tag);
            if (textToSet.length() > 48)
                throw new IOException("Data too long (" + textToSet.length() + "), on key = " + EXIF_tag);

            exif.setAttribute(EXIF_tag, textToSet);
            exif.saveAttributes();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return 1; // 1 image updated
    }
}

这篇关于在手机编写自定义内容提供商的照片(部分2)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆