图片共享意图适用于Gmail,但崩溃了FB和Twitter

ifo*_*e2d 17 android android-intent android-sharing android-fileprovider

我试图允许用户将图像共享到设备上的其他应用程序.该图像位于我的应用程序内部存储区域的files /子目录中.它适用于Gmail,但Facebook和Twitter在响应我的意图时都崩溃了.

编辑:Google+也可以.

以下是相关的代码部分.

在Application.xml中

<provider 
    android:name="android.support.v4.content.FileProvider"
    android:authorities="org.iforce2d.myapp.MyActivity"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths" />
</provider>
Run Code Online (Sandbox Code Playgroud)

XML/filepaths.xml

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="shared" path="shared"/>
</paths>
Run Code Online (Sandbox Code Playgroud)

以下是我的活动中的共享代码:

File imagePath = new File(getContext().getFilesDir(), "shared");
File newFile = new File(imagePath, "snapshot.jpg");
Uri contentUri = FileProvider.getUriForFile(getContext(),
                     "org.iforce2d.myapp.MyActivity", newFile);    

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

List<ResolveInfo> resInfos = 
    getPackageManager().queryIntentActivities(shareIntent,
                                              PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo info : resInfos) {
    getContext().grantUriPermission(info.activityInfo.packageName, 
                                    contentUri,
                                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

startActivity(Intent.createChooser(shareIntent, "Share image..."));
Run Code Online (Sandbox Code Playgroud)

contentUri记录时的值是:

content://org.iforce2d.myapp.MyActivity/shared/snapshot.jpg

我只使用Gmail,Facebook和Twitter作为接收应用程序进行了检查,但结果在各种操作系统版本(从2.2.1到4.4.3)中都非常一致,7个设备包含Kindle.

Gmail效果很好.图像缩略图显示在邮件撰写中,并在发送时成功附加到邮件.

Twitter和Facebook都崩溃了,如下所示.

以下是来自logcat的堆栈跟踪,显示了这两个应用程序所遇到的问题,对于它们来说似乎是同样的问题(这取自4.4.3,但错误实际上一直回到2.2. 1尽管错误消息的措辞略有不同):

Caused by: 
  java.lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow. 
  Make sure the Cursor is initialized correctly before accessing data from it.
    at android.database.CursorWindow.nativeGetString(Native Method)
    at android.database.CursorWindow.getString(CursorWindow.java:434)
    at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:51)
    at android.database.CursorWrapper.getString(CursorWrapper.java:114)
    at com.twitter.library.media.util.f.a(Twttr:95)
    ...

Caused by: 
  java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow.
  Make sure the Cursor is initialized correctly before accessing data from it.
    at android.database.CursorWindow.nativeGetString(Native Method)
    at android.database.CursorWindow.getString(CursorWindow.java:434)
    at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:51)
    at android.database.CursorWrapper.getString(CursorWrapper.java:114)
    at com.facebook.photos.base.media.MediaItemFactory.b(MediaItemFactory.java:233)
    ...
Run Code Online (Sandbox Code Playgroud)

鉴于在Facebook和Twitter上分享图像是数百万人整天都在做的事情,我很震惊它实施起来很难:/

任何人都可以发现我在这里做错了吗?

Ste*_*sek 33

Twitter(错误地)假设将有一个MediaStore.MediaColumns.DATA列.从KitKat开始,MediaStore返回null,幸运的是,Twitter优雅地处理空值,并做正确的事情.

public class FileProvider extends android.support.v4.content.FileProvider {

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Cursor source = super.query(uri, projection, selection, selectionArgs, sortOrder);

        String[] columnNames = source.getColumnNames();
        String[] newColumnNames = columnNamesWithData(columnNames);
        MatrixCursor cursor = new MatrixCursor(newColumnNames, source.getCount());

        source.moveToPosition(-1);
        while (source.moveToNext()) {
            MatrixCursor.RowBuilder row = cursor.newRow();
            for (int i = 0; i < columnNames.length; i++) {
                row.add(source.getString(i));
            }
        }

        return cursor;
    }

    private String[] columnNamesWithData(String[] columnNames) {
        for (String columnName : columnNames)
            if (MediaStore.MediaColumns.DATA.equals(columnName))
                return columnNames;

        String[] newColumnNames = Arrays.copyOf(columnNames, columnNames.length + 1);
        newColumnNames[columnNames.length] = MediaStore.MediaColumns.DATA;
        return newColumnNames;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 优秀的黑客!我已经发布了[一个`LegacyCompatCursorWrapper`](https://github.com/commonsguy/cwac-provider#usage-legacycompatcursorwrapper),这是一个关于相同概念的即兴演奏.在我的例子中,我使用了一个`CursorWrapper`实现,以避免将`Cursor`内容复制到`MatrixCursor`中.但除此之外,它是同一个基本想法,并感谢指出这种方法! (6认同)
  • 很抱歉Android Java编程经验不足:如何使用上面的代码? (2认同)
  • 大!我的工作就像魅力.最好的部分是我不必要求写外部存储权限. (2认同)
  • 致VHanded:我遇到了同样的问题.查看Android Manifest文件并将android.support.v4.content.FileProvider更改为FileProvider,应该这样做. (2认同)

mat*_*ash 3

编辑 - 这个答案中描述的解决方法有效,但Stefan 提供的解决方案更优雅。这应该是公认的答案。

此外,截至 2015 年 1 月,Facebook 应用程序不再有此错误(现在可以使用标准FileProvider),但 Twitter 仍然存在。希望它将来会完全消失,而这些都没有必要。


虽然该FileProvider对象在理论上看起来很棒,但在共享到许多第三方应用程序时它似乎不起作用(而 Google 的应用程序,如 G+、Gmail 等则可以完美地工作)。要么它没有公开足够的信息,要么这些应用程序只是假设您正在共享文件,从而崩溃。

真是令人沮丧!:/

我发现共享图像文件的唯一可靠方法是将它们复制到外部存储目录(您可以创建一个.nomedia子目录以避免它们出现在图库应用程序中)。

例如:

Bitmap bitmapToShare = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

File pictureStorage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File noMedia = new File(pictureStorage, ".nomedia");
if (!noMedia.exists())
    noMedia.mkdirs();

File file = new File(noMedia, "shared_image.png");
saveBitmapAsFile(bitmapToShare, file);

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
shareIntent.setType("image/png");

startActivity(shareIntent);
Run Code Online (Sandbox Code Playgroud)

附录:当然,您还应该getExternalStorageState()在尝试在那里写入/复制文件之前进行检查。

  • FWIW,Twitter 应用程序中的问题是它们对“_data”进行“query()”处理。有一些糟糕的代码,人们认为所有 `content://` `Uri` 值都可以神奇地转换为本地文件路径(涉及在 `MediaStore` 中查找 `_data` 列,这从来都是不正确的,并且对于“FileProvider”来说非常不正确。Facebook 可能会做同样的事情,但我不是 Facebook 用户,因此无法轻易测试该理论。 (2认同)