Как использовать Intent.ACTION_OPEN_DOCUMENT в Android Pie

Я делаю функцию изменения фотографии профиля с помощью Retrofit в Android Pie.

Так что мне удалось загрузить на сервер фотографии, сделанные камерой. Но я не знаю, как перенести фотографии, выбранные из галереи, на мой сервер. (Меня устраивает любой код на Java Kotlin.)

Я загружу видео позже.

Я много искал в Google, но мне было трудно получить нужную информацию.

Хорошо сделано в документе Google, но я не знаю, как это сделать. https://developer.android.com/guide/topics/providers/document-provider

Документ Google показывает примеры использования растрового изображения или потока ввода или чего-то еще.

Требуется ли мне растровое изображение или входной поток для загрузки фотографий с помощью Retrofit?

Мне действительно нужен действующий uri.

public void performFileSearch() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/*");
        startActivityForResult(intent, PICTURES_DIR_ACCESS_REQUEST_CODE);
}


@Override
public void onActivityResult(int requestCode, int resultCode,Intent resultData) {
    if (requestCode == READ_REQUEST_CODE && resultCode ==Activity.RESULT_OK) {
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            Log.i(TAG, "Uri: " + uri.toString());
            showImage(uri);
        }
    }
}


public void Edit_Profile (String Image_Uri) {
    File file = new File(Image_Uri);
    RequestBody requestBody = RequestBody.create(file, MediaType.parse("image/*"));

    MultipartBody.Part body = MultipartBody.Part.createFormData("uploaded_file", Num+ID+".jpg", requestBody);
}

Фактически, onActivityResult возвращает следующий тип uri.

content://com.android.providers.media.documents/document/image%3A191474

Поэтому, когда я пытаюсь отправить его на свой сервер, используя этот uri, я получаю ошибку FileNotFoundException.


person kim.h.w    schedule 24.10.2019    source источник
comment
Если вы можете получить и отобразить изображение, перед отправкой на сервер попробуйте преобразовать его в кодировку base64 и передать полученную строку.   -  person    schedule 25.10.2019
comment
Используйте InputStreamRequestBody из второго фрагмента кода этого комментария к проблеме GitHub создать RequestBody для Uri.   -  person CommonsWare    schedule 25.10.2019
comment
Можете ли вы помочь мне перечислить все файлы в android-Q из каталога загрузки? stackoverflow.com/questions/63593112/   -  person jazzbpn    schedule 27.08.2020


Ответы (2)


Это ограничение конфиденциальности введено в Android-Q. Прямой доступ к общим / внешним устройствам хранения не рекомендуется, если приложение нацелено на API 29 и путь, возвращенный из getExternalStorageDirectory больше не доступен приложениям напрямую. Используйте каталог приложения для записи и чтения файлов.

По умолчанию приложениям, ориентированным на Android 10 и более поздних версий, предоставляется доступ с ограниченным объемом . во внешнее хранилище или в хранилище с ограниченным объемом. Такие приложения могут просматривать следующие типы файлов на внешнем запоминающем устройстве без необходимости запрашивать у пользователя разрешения, связанные с хранилищем:

Файлы в каталоге приложения, доступ к которому осуществляется с помощью getExternalFilesDir (). Фотографии, видео и аудиоклипы, созданные приложением из магазина мультимедиа.

  • Файлы в каталоге конкретного приложения, доступ к которым осуществляется с помощью getExternalFilesDir ().
  • Фотографии, видео и аудиоклипы, созданные приложением из хранилища мультимедиа.

Просмотрите документацию. Открытие файлов с использованием инфраструктуры доступа к хранилищу

Переходя к контексту, вы можете сделать то, что, как предлагает CommonsWare, использовать InputStreamRequestBody. В противном случае скопируйте выбранный файл в IE папки изолированной программной среды вашего приложения, в каталог приложения, а затем получите доступ к файлу оттуда без какого-либо разрешения. Просто посмотрите приведенную ниже реализацию, которая работает в Android-Q и более поздних версиях.

Выполните поиск файлов

private void performFileSearch(String messageTitle) {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
        intent.setType("application/*");
        String[] mimeTypes = new String[]{"application/x-binary,application/octet-stream"};
        if (mimeTypes.length > 0) {
            intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
        }

        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(Intent.createChooser(intent, messageTitle), OPEN_DIRECTORY_REQUEST_CODE);
        } else {
            Log.d("Unable to resolve Intent.ACTION_OPEN_DOCUMENT {}");
        }
    }

onActivityResult возвращено

@Override
public void onActivityResult(int requestCode, int resultCode, final Intent resultData) {
        // The ACTION_OPEN_DOCUMENT intent was sent with the request code OPEN_DIRECTORY_REQUEST_CODE.
        // If the request code seen here doesn't match, it's the response to some other intent,
        // and the below code shouldn't run at all.
        if (requestCode == OPEN_DIRECTORY_REQUEST_CODE) {
            if (resultCode == Activity.RESULT_OK) {
                // The document selected by the user won't be returned in the intent.
                // Instead, a URI to that document will be contained in the return intent
                // provided to this method as a parameter.  Pull that uri using "resultData.getData()"
                if (resultData != null && resultData.getData() != null) {
                    new CopyFileToAppDirTask().execute(resultData.getData());
                } else {
                    Log.d("File uri not found {}");
                }
            } else {
                Log.d("User cancelled file browsing {}");
            }
        }
    }

Запись файла по указанному в приложении пути

public static final String FILE_BROWSER_CACHE_DIR = "CertCache";

@SuppressLint("StaticFieldLeak")
private class CopyFileToAppDirTask extends AsyncTask<Uri, Void, String> {
    private ProgressDialog mProgressDialog;

    private CopyFileToAppDirTask() {
        mProgressDialog = new ProgressDialog(YourActivity.this);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mProgressDialog.setMessage("Please Wait..");
        mProgressDialog.show();
    }

    protected String doInBackground(Uri... uris) {
        try {
            return writeFileContent(uris[0]);
        } catch (IOException e) {
            Log.d("Failed to copy file {}" + e.getMessage());
            return null;
        }
    }

    protected void onPostExecute(String cachedFilePath) {
        mProgressDialog.dismiss();
          if (cachedFilePath != null) {
                Log.d("Cached file path {}" + cachedFilePath);
            } else {
               Log.d("Writing failed {}");
         }

    }
}

private String writeFileContent(final Uri uri) throws IOException {
    InputStream selectedFileInputStream =
            getContentResolver().openInputStream(uri);
    if (selectedFileInputStream != null) {
        final File certCacheDir = new File(getExternalFilesDir(null), FILE_BROWSER_CACHE_DIR);
        boolean isCertCacheDirExists = certCacheDir.exists();
        if (!isCertCacheDirExists) {
            isCertCacheDirExists = certCacheDir.mkdirs();
        }
        if (isCertCacheDirExists) {
            String filePath = certCacheDir.getAbsolutePath() + "/" + getFileDisplayName(uri);
            OutputStream selectedFileOutPutStream = new FileOutputStream(filePath);
            byte[] buffer = new byte[1024];
            int length;
            while ((length = selectedFileInputStream.read(buffer)) > 0) {
                selectedFileOutPutStream.write(buffer, 0, length);
            }
            selectedFileOutPutStream.flush();
            selectedFileOutPutStream.close();
            return filePath;
        }
        selectedFileInputStream.close();
    }
    return null;
}

  // Returns file display name.
    @Nullable
    private String getFileDisplayName(final Uri uri) {
        String displayName = null;
        try (Cursor cursor = getContentResolver()
                .query(uri, null, null, null, null, null)) {
            if (cursor != null && cursor.moveToFirst()) {
                displayName = cursor.getString(
                        cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
                Log.i("Display Name {}" + displayName);

            }
        }

        return displayName;
    }
person Anoop M Maddasseri    schedule 29.10.2019
comment
Можете ли вы помочь мне перечислить все файлы в android-Q из каталога загрузки? stackoverflow.com/questions/63593112/ - person jazzbpn; 27.08.2020
comment
Я правильно это понимаю? Что с Android Q вам нужно 50+ строк кода, чтобы прочитать файл, скажем, из общей папки загрузок? - person quilkin; 22.12.2020
comment
В ответе, относящемся к Android-Q, почему вы относитесь к классу, который устарел в Oreo (ProgressDialog)? Не внушает уверенности в ответе .. - person quilkin; 23.12.2020

Вот, возможно, более общее решение, которое позволяет классу AsyncTask быть отдельным, а не встроенным в действие. Он также возвращает ответ на ваши действия, когда задача завершена, и использует ProgressBar, а не устаревший ProgressDialog.

Асинхронная задача:

public class FileLoader extends AsyncTask<Uri, Void, String>
{
    private WeakReference<Context> contextRef;
    public AsyncResponse delegate = null;

    public interface AsyncResponse {
    void fileLoadFinish(String result);
    }
    FileLoader(Context ctx , AsyncResponse delegate) {
        contextRef = new WeakReference<>(ctx);
        this.delegate =  delegate;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    protected String doInBackground(Uri... uris) {
        Context context = contextRef.get();
         ContentResolver contentResolver = context.getContentResolver();

        Uri uri = uris[0];
        try {
            String mimeType = contentResolver.getType(uri);
            Cursor returnCursor =
                contentResolver.query(uri, null, null, null, null);
            int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            returnCursor.moveToFirst();
            String fileName = returnCursor.getString(nameIndex);
            InputStream inputStream =  contentResolver.openInputStream(uri);

            File downloadDir = 
              context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
            File f = new File(downloadDir +  "/" + fileName);

            FileOutputStream out = new FileOutputStream(f);
            IOUtils.copyStream(inputStream,out);
            returnCursor.close();
            return  f.getPath();
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    protected void onPostExecute(String result) {
        delegate.fileLoadFinish(result);
        super.onPostExecute(result);
    }
}

и в вашей деятельности:

private static final int DIR_ACCESS_REQUEST_CODE = 13;
public void performFileSearch() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("application/*");
    String[] mimeTypes = new String[]{"application/gpx+xml","application/vnd.google-earth.kmz"};
    intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(Intent.createChooser(intent, "Choose KMZ or GPX file"), DIR_ACCESS_REQUEST_CODE);
    } else {
        Log.d("****File","Unable to resolve Intent.ACTION_OPEN_DOCUMENT");
    }
}


@Override
public void onActivityResult(int requestCode, int resultCode,Intent resultData)
{
    super.onActivityResult(requestCode, resultCode, resultData);
    if (requestCode == DIR_ACCESS_REQUEST_CODE && resultCode == Activity.RESULT_OK)
    {
        if (resultData != null)
        {
            Uri uri = resultData.getData();

            mProgressBar.setVisibility(View.VISIBLE);
            new FileLoader(this,
                new FileLoader.AsyncResponse(){
                    @Override
                    public void fileLoadFinish(String result){
                        processFile(new File(result));
                        mProgressBar.setVisibility(View.GONE);
                    }
                }).execute(uri);
         }
    }
}

Мой пример пытается найти файлы .kmz или .gpx. Индикатор выполнения (если он вам нужен, для длительных файловых операций) необходимо инициализировать (и скрыть) в OnCreate ():

mProgressBar = findViewById(R.id.progressbar);
mProgressBar.setVisibility(View.GONE);

Мой метод processFile () требует времени для манипулирования картой в основном действии, поэтому я дождался, пока это будет сделано, прежде чем скрыть ProgressBar.

Я по-прежнему удивлен, что для выполнения такой простой операции требуется столько кода: скопировать файл и сделать его доступным для использования!

person quilkin    schedule 23.12.2020