图像共享意图在Gmail中可用,但会导致Facebook和Twitter崩溃。

17

我正在尝试允许用户将图片分享到设备上的其他应用程序。该图片位于我的应用程序内部存储区域的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>

xml/filepaths.xml

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="shared" path="shared"/>
</paths>

这是我的活动中的共享代码:

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..."));

当记录日志时,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)
    ...

鉴于在Facebook和Twitter上分享图片是数百万人每天都在做的事情,我很震惊它如此难以实现 :/
有人能看出我在这里做错了什么吗?
2个回答

33

Twitter(错误地)假定将有一个MediaStore.MediaColumns.DATA列。从KitKat开始,MediaStore返回null,所以幸运的是,Twitter优雅地处理了null,并采取了正确的措施。

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

1
是的,我刚刚测试过了。 - Stefan Rusek
2
抱歉我在Android Java编程方面经验不足:我该如何使用上述代码? - VHanded
2
太好了!它的效果非常棒。最棒的部分是,我不必请求写入外部存储权限。 - hiepnd
6
太棒了!我已经发布了一个名为LegacyCompatCursorWrapper的东西,这个东西是在同样的概念基础上创建的。在我的情况下,我使用了一个 CursorWrapper 实现,以避免将 Cursor 内容复制到 MatrixCursor 中。但除此之外,它是相同的基本想法,感谢您指出这种方法! - CommonsWare
2
回复 VHanded:我也遇到过同样的问题。请查看 Android Manifest 文件,并将 android.support.v4.content.FileProvider 更改为 FileProvider,这应该可以解决问题。 - Erika

3

编辑 - 本答案中提供的解决方法可行,但是Stefan提供的解决方案更加优雅。应该采用这个答案。

另外,截至2015年1月,Facebook应用程序不再存在此错误(现在可以使用标准的FileProvider),但Twitter仍然存在。希望它将来会完全消失,这样就不需要任何一个了。


虽然FileProvider对象在理论上看起来很棒,但在共享给许多第三方应用程序时似乎无法工作(而像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);

附加说明:当然,在尝试将文件写入/复制到外部存储器之前,您还应检查getExternalStorageState()

@matiash:“要么它没有提供足够的信息,要么那些应用程序只是假设你正在共享文件,因此崩溃了”-- 你能提供有关崩溃性质的什么信息吗?如果没有更多细节,我们无法修复FileProvider(或其分支,例如我的StreamProvider)。 - CommonsWare
@CommonsWare 我不确定除了调用堆栈本身之外如何获取其他信息。 :/ 从 col -1 部分,我推断问题是 ContentProvider 缺少某些列(因此 getColumnIndex() 在某个地方返回 -1)。 - matiash
2
就 Twitter 应用程序而言,问题在于它们正在对 _data 进行 query()。有一些糟糕的代码漂浮在周围,人们认为所有 content:// Uri 值都可以神奇地转化为本地文件路径(涉及在 MediaStore 中查找 _data 列,这从来不正确,并且在 FileProvider 中非常不正确)。Facebook 可能也在做同样的事情,但我不是 Facebook 用户,因此无法轻松测试这个理论。 - CommonsWare
@CommonsWare 是的,我也怀疑是那些应用程序的错误假设。:/ 只是好奇,您是如何确定该列的呢?通过在返回的“Cursor”中设置断点并查看Twitter正在访问什么吗? - matiash
@CommonsWare 很有教育意义。谢谢。 - matiash
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接