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/ファイルパス.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 を受信アプリとして確認しただけですが、幅広い OS バージョン (2.2.1 から 4.4.3 まで) で結果は非常に一貫しており、7 台のデバイスには Kindle が含まれています。

Gmail はうまく機能します。画像のサムネイルがメール作成に表示され、送信時にメールに正常に添付されます。

以下のように、Twitter と Facebook の両方がクラッシュします。

これは、これら 2 つのアプリで発生している問題を示す 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 で画像を共有することは、何百万人もの人々が一日中行っていることであることを考えると、実装が非常に難しいことにかなりショックを受けています :/

ここで私が間違っていることを誰かが見つけることができますか?

4

2 に答える 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;
    }
}
于 2014-07-29T16:44:20.690 に答える
3

編集 - この回答で説明されている回避策は機能しますが、ステファンが提供するソリューションはよりエレガントです。それは受け入れられた答えであるべきです。

また、2015 年 1 月の時点で、Facebook アプリにはこのバグはなくなりました (現在は標準で動作しますFileProvider) が、Twitter にはまだあります。うまくいけば、将来的には完全になくなり、これらのいずれも必要なくなります。


このFileProviderオブジェクトは理論的には素晴らしいように見えますが、多くのサードパーティ アプリケーションと共有すると機能しないようです (G+、Gmail などの Google アプリケーションは完全に機能します)。十分な情報が公開されていないか、これらのアプリケーションがファイルを共有していると想定してクラッシュするかのいずれかです。

本当にイライラします!:/

画像ファイルを共有する唯一の確実な方法は、画像ファイルを外部ストレージ ディレクトリにコピーすることです (.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()そこにファイルを書き込んだりコピーしたりする前にも確認する必要があります。

于 2014-06-13T00:56:52.463 に答える