如何在 android 内容提供程序中存储大 blob?

Posted

技术标签:

【中文标题】如何在 android 内容提供程序中存储大 blob?【英文标题】:How to store large blobs in an android content provider? 【发布时间】:2011-04-22 10:39:44 【问题描述】:

我有一些大文件(图像和视频)需要存储在内容提供商中。 android文档表明...

如果您要公开的字节数据是 太大而不能放在桌子上—— 比如一个大的位图文件—— 向客户端公开数据的字段 实际上应该包含一个内容:URI 细绳。这是给出的领域 客户端访问数据文件。这 记录还应该有另一个字段, 命名为“_data”,列出了确切的 该文件在设备上的文件路径。 此字段不打算阅读 由客户,但由 内容解析器。客户会打电话 ContentResolver.openInputStream() on 持有 URI 的面向用户的字段 为该项目。 ContentResolver 将 为此请求“_data”字段 记录,因为它有更高的 权限比客户端,它应该 能够直接访问该文件 并返回文件的读取包装 给客户。 -- http://developer.android.com/guide/topics/providers/content-providers.html#creating

我很难找到一个例子。 特别是我希望在 ImageView 上下文中使用位图。 考虑下面的代码准代码(不行)...

ImageView iv = ....
String iconUri = cursor.getString(cursor.getColumnIndex(Table.ICON));
iv.setImageURI(Uri.parse(iconUri));

观察/问题...

    如何正确重建存储/恢复的 uri? (就是表格中的文字)

    setImageURI 实现使用了内容解析 openInputStream,所以这应该可以工作。

    String scheme = mUri.getScheme();
    ...
     else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
            || ContentResolver.SCHEME_FILE.equals(scheme)) 
      try 
        d = Drawable.createFromStream(
                mContext.getContentResolver().openInputStream(mUri),
                null);
    

    --frameworks/base/core/java/android/widget/ImageView.java

我让它工作了。 我从 MediaStore 和 MediaProvider 得到了提示。 包含数据的文件根据内容提供者(目录)、列名、行 ID 和媒体类型命名。 内容解析器然后像这样获取文件描述符......

Uri iconUri = Uri.withAppendedPath(Table.getUri(cursor), Table.ICON);
ib.setImageURI(iconUri);

...内容提供者以实物回应...

@Override
public ParcelFileDescriptor openFile (Uri uri, String mode) 
int imode = 0;
if (mode.contains("w")) imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
if (mode.contains("r")) imode |= ParcelFileDescriptor.MODE_READ_ONLY;
if (mode.contains("+")) imode |= ParcelFileDescriptor.MODE_APPEND;
List<String> pseg = uri.getPathSegments();
if (pseg.size() < 3) return null;

try 
    File filePath = filePathFromRecord(pseg.get(2), pseg.get(1));
    return ParcelFileDescriptor.open(filePath, imode);
 catch (FileNotFoundException e) 
    e.printStackTrace();

return null;

【问题讨论】:

糟糕!我通过编辑原始文本而不是发布答案来回答我自己的问题,即“如何在和 android 内容提供程序中存储大型 blob”。无论如何,简短的回答是您按照描述的方式使用 openFile 和朋友。 【参考方案1】:

问题下半部分phreed给出的解决方案基本正确。我将尝试在此处添加更多详细信息。

当您执行getContentResolver().openInputStream(...) 时,内容解析器将转到您的内容提供商并调用其openFile 方法。这就是openFile 在ContentProvider.java 中的样子:

public ParcelFileDescriptor openFile(Uri uri, String mode)
     throws FileNotFoundException 
 throw new FileNotFoundException("No files supported by provider at "
         + uri);

所以这解释了“不支持文件...”错误的确切来源!您可以通过覆盖子类中的 openFile 方法并提供自己的实现来解决此问题。很简洁:当任何客户端执行openInputStreamopenOutputStream 时,您可以完美控制文件的放置位置。

phreed 问题中的代码示例提示了实现的外观。这是我稍作修改的版本,它还根据需要创建目录和文件。我是这方面的新手,所以这可能不是最佳的做事方式,但它提供了一个想法。一方面,它可能应该检查外部存储是否可用。

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException 
    File root = new File(Environment.getExternalStorageDirectory(), 
            "/Android/data/com.example.myapp/cache");
    root.mkdirs();
    File path = new File(root, uri.getEncodedPath());
    // So, if the uri was content://com.example.myapp/some/data.xml,
    // we'll end up accessing /Android/data/com.example.myapp/cache/some/data.xml

    int imode = 0;
    if (mode.contains("w")) 
            imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
            if (!path.exists()) 
                try 
                    path.createNewFile();
                 catch (IOException e) 
                    // TODO decide what to do about it, whom to notify...
                    e.printStackTrace();
                
            
    
    if (mode.contains("r")) imode |= ParcelFileDescriptor.MODE_READ_ONLY;
    if (mode.contains("+")) imode |= ParcelFileDescriptor.MODE_APPEND;        

    return ParcelFileDescriptor.open(path, imode);

【讨论】:

你好@Peteris Caune。我已经像你一样实现了 openFile 方法,但我仍然不知道如何使用 openFileMethod() 中的 ParcelFileDescriptor 对象编写我的文件(*.png),以及现在如何填充 "_data" 字段。我花了很多时间寻找一个例子或解释。谢谢 我自己解决了,看来_data字段是提供者专用的,所以我一直在尝试像游标中的常规字段一样获取数据。我已经注意到我需要改用mResolver.openInputStream(resourceUri); 方法。顺便说一句谢谢。【参考方案2】:

Android 提供了帮助方法 openFileHelper(),这使得 openFile() 方法的实现变得非常容易。要使用此方法,您所要做的就是在名为“_data”的列中提供文件的位置。

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
      throws FileNotFoundException 
   if (URI_MATCHER.match(uri) != PHOTO_ID) 
      throw new IllegalArgumentException
            ("URI invalid. Use an id-based URI only.");
   
   return openFileHelper(uri, mode);

Click here for detail

【讨论】:

【参考方案3】:

但是写入文件有问题。内容提供者如何知道写入是否完成?

当关闭通过 ContentResolver.openOutputStream() 获得的 OutputStream 时,我希望相应的(自定义)ContentProvider 执行一些自定义操作。 目前我正在使用在temporary file 上创建FileObserver 但这似乎是完成它的错误方法。 我的第一个想法是对ContentProvider.openFile() 方法生成的ParcelFileDescriptor 进行子类型化,但这似乎对我不起作用。

话虽如此,有一些错误导致了 MyParcelFileDescriptor 的使用。

我没有确保文件描述符不会过早地被垃圾回收。 我没有尝试覆盖 finally()(文档似乎表明可以这样做,但 close() 似乎是正确的选择给我。

底线,ContentResolver 和ContentProvider 看到的文件对象之间是否有任何交互?

【讨论】:

写入文件有问题。内容提供者如何知道写入是否完成? ***.com/questions/4147763/… docs 说:...返回的 ParcelFileDescriptor 归调用者所有,因此他们有责任在完成后关闭它。也就是说,这个方法的实现应该为每个调用创建一个新的 ParcelFileDescriptor。 所以你不必担心关闭ParcelFileDescriptor 实例。

以上是关于如何在 android 内容提供程序中存储大 blob?的主要内容,如果未能解决你的问题,请参考以下文章

5Android-跨程序共享数据--内容提供器

Android 跨程序共享数据,探究内容提供器

Android-内容提供器一(Android运行时权限)

如何在 Android 中找出内容提供者的调用活动?

简述android平台提供了哪些数据存储方法

Android开发学习之内容提供器