Обмен фотографиями Android с FileProvider

Я прочитал весь ответ по этому аргументу, но всегда получаю сообщение об ошибке приложения, которое получает мою фотографию.
Единственный способ, который работал для меня, для всех приложений, был следующим (это работает, потому что файлы SD-карты являются общедоступными для всех приложений). ):

final File tmpFile = new File(context.getExternalCacheDir(), "exported.jpg");
Uri tmpFileUri = Uri.fromFile(tmpFile);

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setDataAndType(tmpFileUri, "image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, tmpFileUri);
context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share_image)));



Теперь я застрял на том, как поделиться файлом, который находится в личной папке. Я использовал код, предоставленный документацией Google:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.test.myapp.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths" />
</provider>
...
...
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="internal_files" path="/"/>
    <cache-path name="internal_cache" path="/" />
</paths>


Это код для обмена файлами с помощью FileProvider, но он не работает ни с одним приложением, кроме Whats Up:

final File tmpFile = new File(context.getCacheDir(), "exported.jpg");
Uri tmpFileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", tmpFile);
//Remove the uri permission because we overwrite the file
context.revokeUriPermission(tmpFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

saveBitmapToPath(bitmap, tmpFile);
bitmap.recycle();

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setDataAndType(tmpFileUri, "image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, tmpFileUri);
//Grant again the permissions
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share_image)));

Почему я постоянно получаю ошибки в других приложениях, например:
java.lang.SecurityException: Permission Denial: content://com.test.myapp.fileprovider/internal_cache/exported.jpg (pid=675, uid=10052) requires null
Или
IllegalArgumentException: Failed to find configuration root that contains content://com.test.myapp.fileprovider/internal_cache/exported.jpg


person Marco Di Scala    schedule 16.07.2014    source источник


Ответы (1)


Наконец, изучив исходный код принимающего приложения, я нашел решение.
Это полный рабочий код, которым я делюсь.
Я надеюсь помочь кому-нибудь:

<!-- AndroidManifest.xml -->
<provider
    android:name="com.test.myapp.fileprovider.FileProvider"
    android:authorities="com.test.myapp.fileprovider"
    android:exported="true"
    tools:ignore="ExportedContentProvider" />


//EntryPoint
private void mySharer() {
    ArrayList<Uri> streamUris = new ArrayList<Uri>();
    for (int i = 0; i < 10; i++) {
        File tmpFile = new File(getContext().getCacheDir(), "tmp" + i + ".jpg");
        Uri tmp = FileProvider.getUriForFile("com.test.myapp.fileprovider", tmpFile);
        streamUris.add(tmp);
    }
}

//Share Intent creator
public final void shareUris(ArrayList<Uri> streamUris) {
    if (!streamUris.isEmpty()) {
        Intent shareIntent = new Intent();
        shareIntent.putExtra(ShareCompat.EXTRA_CALLING_PACKAGE, getPackageName());
        shareIntent.putExtra(ShareCompat.EXTRA_CALLING_ACTIVITY, getComponentName());
        shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        shareIntent.setType("image/jpeg");

        if (streamUris.size() == 1) {
            shareIntent.setAction(Intent.ACTION_SEND);
            shareIntent.putExtra(Intent.EXTRA_STREAM, streamUris.get(0));
        } else {
            shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
            shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, streamUris);
        }

        //For multiple images copy all images in the baseDir and use startActivityForResult
        startActivityForResult(Intent.createChooser(shareIntent, getString(R.string.share_image)), 500);
    }
}

//onResult you can delete all temp images/files with specified extensions
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case 500:
            getContentResolver().delete(FileProvider.getUriForFile(getPackageName() + ".fileprovider", null), FileProvider.WHERE_EXTENSION, new String[]{"jpg"});
            break;
        default:
            break;
    }
}

/**
 * This class extends the ContentProvider
 */
abstract class AbstractFileProvider extends ContentProvider {

    private final static String OPENABLE_PROJECTION_DATA = "_data";
    private final static String[] OPENABLE_PROJECTION = { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, OPENABLE_PROJECTION_DATA };

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        if (projection == null) {
            projection = OPENABLE_PROJECTION;
        }

        final MatrixCursor cursor = new MatrixCursor(projection, 1);
        MatrixCursor.RowBuilder b = cursor.newRow();

        for (String col : projection) {
            if (OpenableColumns.DISPLAY_NAME.equals(col)) {
                b.add(getFileName(uri));
            } else if (OpenableColumns.SIZE.equals(col)) {
                b.add(getDataLength(uri));
            } else if (OPENABLE_PROJECTION_DATA.equals(col)) {
                b.add(getFileName(uri));
            } else {
                b.add(null);
            }
        }

        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        return URLConnection.guessContentTypeFromName(uri.toString());
    }

    protected String getFileName(Uri uri) {
        return uri.getLastPathSegment();
    }

    protected long getDataLength(Uri uri) {
        return AssetFileDescriptor.UNKNOWN_LENGTH;
    }

    @Override
    public Uri insert(Uri uri, ContentValues initialValues) {
        throw new RuntimeException("Operation not supported");
    }

    @Override
    public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
        throw new RuntimeException("Operation not supported");
    }

    @Override
    public int delete(Uri uri, String where, String[] whereArgs) {
        throw new RuntimeException("Operation not supported");
    }
}

/**
 * This class extends the AbstractFileProvider
 */
public class FileProvider extends AbstractFileProvider {

    public static final String CONTENT_URI = "content://";
    private File baseDir;

    @Override
    public boolean onCreate() {
        baseDir = getContext().getCacheDir();

        if (baseDir != null && baseDir.exists()) {
            return true;
        }

        Log.e("FileProvider", "Can't access cache directory");
        return false;
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        File f = new File(baseDir, uri.getPath());

        if (f.exists()) {
            return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
        }

        throw new FileNotFoundException(uri.getPath());
    }

    @Override
    protected long getDataLength(Uri uri) {
        File f = new File(baseDir, uri.getPath());

        return f.length();
    }

    public static Uri getUriForFile(String authority, File file) {
        return Uri.parse(CONTENT_URI + authority + "/" + file.getName());
    }
}



-------------EDIT: 11.05.16--------------
Добавлена ​​поддержка нескольких изображений:

  1. Скопируйте все изображения в папку baseDir
  2. Реализовать метод delete() в FileProvider
  3. Используйте startActivityForResult
  4. Слушай onActivityResult
  5. Теперь вы можете удалить все временные изображения

Для вложения электронной почты необходимо дождаться отправки электронной почты, прежде чем удалять файл, иначе вы отправите пустое вложение

person Marco Di Scala    schedule 24.07.2014
comment
Значит, вы не использовали xml с путями? У меня проблема с размещением нескольких путей. Один путь работает, когда я ставлю два, это не так. - person dangalg; 12.08.2015
comment
как обрабатывать файлы, совместно используемые другими приложениями? - person Usman Rana; 09.07.2019