Android 图像共享意图适用于Gmail,但会使FB和twitter崩溃

Android 图像共享意图适用于Gmail,但会使FB和twitter崩溃,android,android-intent,android-sharing,android-fileprovider,Android,Android Intent,Android Sharing,Android Fileprovider,我正在尝试允许用户将图像共享到设备上的其他应用程序。图像位于我的应用程序内部存储区域的文件/子目录中。Gmail也可以,但Facebook和Twitter在回应我的意图时都崩溃了 编辑:Google+也可以正常工作 下面是代码的相关部分 在Application.xml中 <provider android:name="android.support.v4.content.FileProvider" android:authorities="org.iforce2d.my

我正在尝试允许用户将图像共享到设备上的其他应用程序。图像位于我的应用程序内部存储区域的文件/子目录中。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>
考虑到在Facebook和Twitter上共享图像是数百万人一整天都在做的事情,我很震惊它如此难以实现:/

有人能在这里发现我做错了什么吗?

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

此外,截至2015年1月,Facebook应用程序不再存在此漏洞(现在可与标准的
文件提供商
配合使用),但Twitter仍然存在此漏洞。希望它在未来会完全消失,而这两个都不是必需的


虽然
FileProvider
对象在理论上看起来很棒,但在与许多第三方应用程序共享时,它似乎不起作用(而谷歌的应用程序,如G+、Gmail和&c,工作得很好)。要么它没有公开足够的信息,要么那些应用程序只是假设您正在共享文件,从而崩溃

真令人沮丧/

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

例如:

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()

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;
    }
}
公共类FileProvider扩展了android.support.v4.content.FileProvider{
@凌驾
公共游标查询(Uri Uri、字符串[]投影、字符串选择、字符串[]selectionArgs、字符串排序器){
Cursor source=super.query(uri、投影、选择、选择、排序器);
String[]columnNames=source.getColumnNames();
String[]newColumnNames=columnNamesWithData(columnNames);
MatrixCursor=newMatrixCursor(newColumnNames,source.getCount());
来源.移动位置(-1);
while(source.moveToNext()){
MatrixCursor.RowBuilder行=cursor.newRow();
for(int i=0;i
效果很好,谢谢!我应该在六小时前问你。。。那么FileProvider方法是新的还是什么?FB/TW开发者是否只需要指手画脚就可以得到支持?它并不完全是新的,它是在2013年5月推出的--。我不知道为什么,但第三方应用程序似乎不支持它。真可惜!另一个解决方案看起来确实像是一个黑客。@matiash:“要么它没有公开足够的信息,要么那些应用程序只是假设您正在共享文件,从而导致崩溃”——关于崩溃的性质,您可以提供哪些信息?如果没有更多详细信息,我们无法修复
文件提供程序
(或其分支,如我的
StreamProvider
)。@Commonware我不确定如何获得除调用堆栈之外的其他信息。:/从
col-1
部分,我假设问题是ContentProvider缺少某个列(因此
getColumnIndex()
在某个地方返回-1)。FWIW,Twitter应用程序中的问题是,他们正在
query()
-ing查找
\u数据。有一些糟糕的代码到处流传,人们认为所有
内容://
Uri
值都可以神奇地转换成本地文件路径(涉及在
MediaStore
中查找
\u data
列,这从来都不正确,而且对于
文件提供商来说非常不正确。Facebook可能也在做同样的事情,但我不是Facebook用户,因此无法轻易测试这一理论。是的。我刚刚测试过。很抱歉,Android Java程序的经验不足明:我如何使用上面的代码?太好了!我的工作很有魅力。最好的部分是我不需要请求写外部存储权限。非常好的黑客!我已经发布了,这是对同一概念的重复。在我的例子中,我使用了
CursorWrapper
实现,以避免将
Cursor
内容复制到
MatrixC中ursor
。但除此之外,这是相同的基本想法,感谢您指出了这种方法!对于VHanded:我也有同样的问题。查看Android清单文件,并将Android.support.v4.content.FileProvider更改为FileProvider,应该可以。
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)
    ...
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);
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;
    }
}