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

发布于 2024-09-26 12:12:37 字数 2235 浏览 12 评论 0 原文

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

如果您公开的是字节数据 太大了,无法放入桌子本身 - 例如一个大的位图文件 - 向客户端公开数据的字段 实际上应该包含一个内容:URI 细绳。这是给出的字段 客户端访问数据文件。这 记录还应该有另一个字段, 名为“_data”,列出了确切的 该文件在设备上的文件路径。 该字段不适合读取 由客户,但由 内容解析器。客户会打电话 ContentResolver.openInputStream() 上 保存 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));

观察/问题...

  1. 如何正确重建存储/恢复的 uri? (它是表中的文本)
  2. setImageURI 实现使用内容解析 openInputStream,因此这应该可以工作。

    字符串方案 = mUri.getScheme();
    ...
    } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
            || ContentResolver.SCHEME_FILE.equals(scheme)) {
      尝试 {
        d = Drawable.createFromStream(
                mContext.getContentResolver().openInputStream(mUri),
                无效的);
    

    --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;
}

I have some large files (images and video) which I need to store in a content provider.
The android documentation indicates...

If you are exposing byte data that's
too big to put in the table itself —
such as a large bitmap file — the
field that exposes the data to clients
should actually contain a content: URI
string. This is the field that gives
clients access to the data file. The
record should also have another field,
named "_data" that lists the exact
file path on the device for that file.
This field is not intended to be read
by the client, but by the
ContentResolver. The client will call
ContentResolver.openInputStream() on
the user-facing field holding the URI
for the item. The ContentResolver will
request the "_data" field for that
record, and because it has higher
permissions than a client, it should
be able to access that file directly
and return a read wrapper for the file
to the client.
-- http://developer.android.com/guide/topics/providers/content-providers.html#creating

I am having some difficulty finding an example.
In particular I wish to use the bitmap in the context an ImageView.
Consider the following code quasi-code (it doesn't work)...

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

Observations/Problems...

  1. How can the stored/recovered uri be reconstructed correctly? (it is text in the table)
  2. The setImageURI implementation makes use of the content resolve openInputStream so this should work.

    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

I got it working.
I took a hint from the MediaStore and MediaProvider.
The files which contain the data are named based on the content provider (directory), the column name, the row id and the media type.
The content resolver then acquires the file descriptor like so...

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

...and the content provider responds in kind...

@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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

清风挽心 2024-10-03 12:12:37

问题下半部分给出的解决方案基本上是正确的。我将尝试在这里添加更多细节。

当您执行getContentResolver().openInputStream(...)时,内容解析器将转到您的内容提供程序并调用其openFile方法。这是 openFileContentProvider.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);
}

The solution phreed gives in the bottom half of question is basically correct. I'll try to add some more details here.

When you do getContentResolver().openInputStream(...), content resolver will go to your content provider and call its openFile method. This is how the openFile looks in ContentProvider.java:

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

So this explains where the "No files supported ..." error exactly comes from! You get around this by overriding openFile method in your subclass and providing your own implementation. It's neat: you get perfect control of where your files get placed when any client does openInputStream or openOutputStream.

Code sample in phreed's question gives a hint how the implementation could look like. Here's my slightly modified version which also creates directories and files as needed. I'm novice at this stuff so this might not be the optimal way of doing things, but it gives an idea. For one thing, it should probably check if external storage is available.

@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);
}
雨落□心尘 2024-10-03 12:12:37

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);
}

点击此处了解详情

Android provides the helper method openFileHelper() that makes implementing the openFile() method very easy. All you have to do, to use this method, is to provide the location of the file in a column named “_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

远昼 2024-10-03 12:12:37

但写入文件时出现问题。内容提供商如何知道写入已完成?

关闭通过 ContentResolver 获取的 OutputStream 时。 openOutputStream() 我希望相应的(自定义)ContentProvider 执行一些自定义操作。
目前我正在使用 FileObserver 创建一个 临时文件
但这似乎是错误的完成方式。
我的第一个想法是对 ParcelFileDescriptor 进行子类型化“http://developer.android.com/reference/android/content/ContentProvider.html#openFile%28android.net.Uri,%20java.lang.String%29” rel="nofollow">ContentProvider.openFile()< /a> 方法但这似乎对我不起作用。

话虽这么说,有一些错误导致了 MyParcelFileDescriptor 的使用。

  • 我不确保文件描述符不会被过早地垃圾收集。
  • 我没有尝试重写 finally() (文档似乎表明是执行此操作的地方,但 close() 对我来说似乎是正确的选择。

最重要的是,文件对象之间是否存在任何交互,如 ContentResolver 以及 ContentProvider

There is a problem writing files though. How does the content provider know then the write is complete?

When closing an OutputStream obtained via a ContentResolver.openOutputStream() I wish for the corresponding (custom) ContentProvider to perform some custom action.
Presently I am using creating a FileObserver on a temporary file
but it seems like the wrong way to get it done.
My first thought was to subtype the ParcelFileDescriptor produced by the ContentProvider.openFile() method but that didn't seem to work for me.

That being said there were some errors which resulted in the MyParcelFileDescriptor usage.

  • I didn't ensure that the file descriptor would not be garbage collected prematurely.
  • I didn't try to overriding finally() (the documentation seems to suggest that being the place to do this but close() seemed to be the right choice to me.

Bottom line, is there any interaction between the file objects as seen by the ContentResolver and those of the ContentProvider?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文