如何实现ContentProvider为Gmail,Facebook,Evernote等提供图像

我之前的问题( 是否可以通过数据url在Android上共享图像? )与此问题相关。 我已经想出如何在没有将文件写入外部存储的许可的情况下将我的应用程序中的图像共享到另一个应用程序。 但是,我仍然会遇到许多问题行为:

  1. 当我尝试从手机(Android 2.2.2)共享图像时,接收应用程序中会出现致命错误,并且它们根本没有提供图像。 (这可能是我的应用程序中某些操作导致Android 2.2.2不支持的结果吗?或者这会导致我的应用程序而不是目标应用程序出错?)
  2. 当我尝试将图像分享到Evernote时,一切正常,但有时在保存笔记几秒后,我在应用程序屏幕的底部(来自Evernote应用程序)收到一条消息:“java.lang.SecurityException:权限拒绝:打开提供程序com.enigmadream.picturecode.PictureContentProvider来自ProcessRecord {413db6d0 1872:com.evernote / u0a10105}(pid = 1872,uid = 10105),不从uid 10104导出“
  3. 当我尝试将图片分享到Facebook时,图片中有一个矩形,但没有图片。

下面是我的ContentProvider代码。 必须有一种更简单和/或更合适的方式来实现基于文件的ContentProvider(尤其是查询function)。 我希望很多问题都来自查询实现。 有趣的是,在使用GMail时,这对我的Nexus 7非常有效。 它也会为附件选择正确的显示名称和大小。

public class PictureContentProvider extends ContentProvider implements AutoAnimate { public static final Uri CONTENT_URI = Uri.parse("content://com.enigmadream.picturecode.snapshot/picture.png"); private static String[] mimeTypes = {"image/png"}; private Uri generatedUri; @Override public int delete(Uri uri, String selection, String[] selectionArgs) { throw new RuntimeException("PictureContentProvider.delete not supported"); } @Override public String getType(Uri uri) { return "image/png"; } @Override public Uri insert(Uri uri, ContentValues values) { throw new RuntimeException("PictureContentProvider.insert not supported"); } @Override public boolean onCreate() { generatedUri = Uri.EMPTY; return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { long fileSize = 0; MatrixCursor result = new MatrixCursor(projection); File tempFile; try { tempFile = generatePictureFile(uri); fileSize = tempFile.length(); } catch (FileNotFoundException ex) { return result; } Object[] row = new Object[projection.length]; for (int i = 0; i < projection.length; i++) { if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DISPLAY_NAME) == 0) { row[i] = getContext().getString(R.string.snapshot_displaystring); } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.SIZE) == 0) { row[i] = fileSize; } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATA) == 0) { row[i] = tempFile; } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.MIME_TYPE)==0) { row[i] = "image/png"; } } result.addRow(row); return result; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new RuntimeException("PictureContentProvider.update not supported"); } @Override public String[] getStreamTypes(Uri uri, String mimeTypeFilter) { return mimeTypes; } private File generatePictureFile(Uri uri) throws FileNotFoundException { if (generatedUri.compareTo(uri)==0) return new File(getContext().getFilesDir(), "picture.png");; Context context = getContext(); String query = uri.getQuery(); String[] queryParts = query.split("&"); String pictureCode = "016OA"; int resolution = 36; int frame = 0; int padding = 0; for (String param : queryParts) { if (param.length() < 2) continue; if (param.substring(0,2).compareToIgnoreCase("p=") == 0) { pictureCode = param.substring(2); } else if (param.substring(0,2).compareToIgnoreCase("r=") == 0) { resolution = Integer.parseInt(param.substring(2)); } else if (param.substring(0, 2).compareToIgnoreCase("f=") == 0) { frame = Integer.parseInt(param.substring(2)); } else if (param.substring(0, 2).compareToIgnoreCase("a=") == 0) { padding = Integer.parseInt(param.substring(2)); } } Bitmap picture = RenderPictureCode(pictureCode, resolution, frame, padding); File tempFile = new File(context.getFilesDir(), "picture.png"); FileOutputStream stream; stream = new FileOutputStream(tempFile); picture.compress(CompressFormat.PNG, 90, stream); try { stream.flush(); stream.close(); } catch (IOException e) { e.printStackTrace(); throw new Error(e); } picture.recycle(); generatedUri = uri; return tempFile; } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { File tempFile = generatePictureFile(uri); return ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_ONLY); } ... } 

我还在AndroidManifest.xml文件中将其作为元素的兄弟:

     

创建intent的代码如下所示:

  resolution = mPicView.getWidth(); if (mPicView.getHeight() > resolution) resolution = mPicView.getHeight(); String paddingText = mPadding.getEditableText().toString(); int padding; try { padding = Integer.parseInt(paddingText); } catch (NumberFormatException ex) { padding = 0; } Uri uri = Uri.parse(PictureContentProvider.CONTENT_URI + "?p=" + Uri.encode(mPicView.getPictureCode()) + "&r=" + Integer.toString(resolution) + "&f=" + Integer.toString(mPicView.getFrame()) + "&a=" + Integer.toString(padding)); Intent share = new Intent(Intent.ACTION_SEND); share.setType("image/png"); share.putExtra(Intent.EXTRA_STREAM, uri); share.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_subject_made)); share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(Intent.createChooser(share, getString(R.id.menu_share))); 

编辑以下是我手机上发生错误时堆栈跟踪的前两行:

04-07 13:56:24.423:E / DatabaseUtils(19431):java.lang.SecurityException:Permission Denial:读取com.enigmadream.picturecode.PictureContentProvider uri content://com.enigmadream.picturecode.snapshot/picture.png? p = 01v131&r = 36&f = 0&a = 0来自pid = 19025,uid = 10062需要com.enigmadream.picturecode.snapshot

04-07 13:56:24.423:E / DatabaseUtils(19431):在android.content.ContentProvider $ Transport.enforceReadPermission(ContentProvider.java:271)

我不熟悉我或不要求的权限或如何禁用它们

尝试更换:

    

有:

   

你真的应该在第一个中有android:exported="true" ,但是我所指的权限更改代表了android:readPermission >的删除。

然后,摆脱addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 在您的Java代码中。

ContentProvider您的ContentProvider设置为世界可读的。 长期来看,这可能不是正确的答案。 短期内,它将有助于确定您的Android 2.2.2问题是否因为FLAG_GRANT_READ_URI_PERMISSION没有得到尊重。

由于query方法,我遇到了与某些电子邮件和消息传递客户端共享的问题。 某些收件人应用程序为projection参数发送null 。 发生这种情况时,您的代码会抛出NullPointerException 。 NPE很容易解决。 但是,发送null的应用程序仍然需要一些信息。 我仍然无法分享到Facebook,但我可以分享到我测试过的所有其他应用:

编辑我也无法使用Google Hangout。 至少,我得到一个祝酒词,表示You can't send this file on Hangouts. Try using a picture You can't send this file on Hangouts. Try using a picture 。 另请参阅此问题: 图片在通过Google环聊发送后被删除 。 我认为这是因为我使用私人内容而环聊不会/不会因某些原因接受它。

 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (projection == null) { projection = new String[] { MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns._ID, MediaStore.MediaColumns.MIME_TYPE }; } final long time = System.currentTimeMillis(); MatrixCursor result = new MatrixCursor(projection); final File tempFile = generatePictureFile(uri); Object[] row = new Object[projection.length]; for (int i = 0; i < projection.length; i++) { if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DISPLAY_NAME) == 0) { row[i] = uri.getLastPathSegment(); } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.SIZE) == 0) { row[i] = tempFile.length(); } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATA) == 0) { row[i] = tempFile; } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.MIME_TYPE)==0) { row[i] = _mimeType; } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATE_ADDED)==0 || projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATE_MODIFIED)==0 || projection[i].compareToIgnoreCase("datetaken")==0) { row[i] = time; } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns._ID)==0) { row[i] = 0; } else if (projection[i].compareToIgnoreCase("orientation")==0) { row[i] = "vertical"; } } result.addRow(row); return result; } 

有相同的问题,经过大量的研究和调查后,这是我的5美分:(在我的Android 2.3.3和4.2设备上)

  1. Facebook应用程序不会使用openFile(Uri uri, String mode)因此我们必须为其提供具有读取权限的真实URI(在projection [i] == MediaStore.MediaColumns.DATA中)。
  2. 你必须给Facebook每个投影都要求你,否则FB-app将只重复query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

它可能是一个hack,并且不会对某些敏感数据起作用,但你可以创建file.setReadable(true, false); 在你的代码中的某个地方的内部文件和Facebook将能够看到和发送img。 无需android外部存储权限。 如果img可读,则可能不需要内容提供者。 可以只在Intent中发送链接。 但我有没有提供商的奇怪的谷歌+预览行为,所以决定保留它。 没有很多设备可以测试和使用Twitter,FB,Google +,Skype,Gmail。 现在工作正常

这是GitHub演示