Загрузите файл Firebase Storage в папку Pictures или Mov ie в Android Q - PullRequest
0 голосов
/ 02 апреля 2020

Мне удалось загрузить файл из Firebase Storage в storage/emulated/0/Pictures, который является папкой по умолчанию для изображений, которая используется большинством популярных приложений, таких как Facebook или Instagram. Теперь, когда Android Q имеет много поведенческих изменений при хранении и доступе к файлу, мое приложение больше не сможет загружать файл из корзины при запуске в Android Q.

Это код которые записывают и загружают файл из хранилища Firebase Storage в папки по умолчанию для большого хранилища, такие как «Изображения», «Фильмы», «Документы» и т. д. c. Он работает на Android M, но на Q он не будет работать.

    String state = Environment.getExternalStorageState();

    String type = "";

    if (downloadUri.contains("jpg") || downloadUri.contains("jpeg")
        || downloadUri.contains("png") || downloadUri.contains("webp")
        || downloadUri.contains("tiff") || downloadUri.contains("tif")) {
        type = ".jpg";
        folderName = "Images";
    }
    if (downloadUri.contains(".gif")){
        type = ".gif";
        folderName = "Images";
    }
    if (downloadUri.contains(".mp4") || downloadUri.contains(".avi")){
        type = ".mp4";
        folderName = "Videos";
    }


    //Create a path from root folder of primary storage
    File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + Environment.DIRECTORY_PICTURES + "/MY_APP_NAME");
    if (Environment.MEDIA_MOUNTED.equals(state)){
        try {
            if (dir.mkdirs())
                Log.d(TAG, "New folder is created.");
        }
        catch (Exception e) {
            e.printStackTrace();
            Crashlytics.logException(e);
        }
    }

    //Create a new file
    File filePath = new File(dir, UUID.randomUUID().toString() + type);

    //Creating a reference to the link
    StorageReference httpsReference = FirebaseStorage.getInstance().getReferenceFromUrl(download_link_of_file_from_Firebase_Storage_bucket);

    //Getting the file from the server
    httpsReference.getFile(filePath).addOnProgressListener(taskSnapshot ->                                 

showProgressNotification(taskSnapshot.getBytesTransferred(), taskSnapshot.getTotalByteCount(), requestCode)

);

При этом он будет загружать файлы с сервера в хранилище вашего устройства с путем storage/emulated/0/Pictures/MY_APP_NAME, но с Android Q это будет больше не работает, так как многие API стали устаревшими, как Environment.getExternalStorageDirectory().

Использование android:requestLegacyExternalStorage=true является временным решением, но больше не будет работать в ближайшее время на Android 11 и выше.

Так что мой вопрос Как я могу загружать файлы с помощью Firebase Storage API по умолчанию Изображение или Mov ie, которое находится в root вместо Android/data/com.myapp.package/files.

MediaStore и ContentResolver есть решение для этого? Какие изменения мне нужно применить?

Ответы [ 2 ]

1 голос
/ 03 апреля 2020

Вот мое решение:

Скачать файл с Glide

public void downloadFile(Context context, String url){
    String mimeType = getMimeType(url);
    Glide.with(context).asFile().load(url).listener(new RequestListener<File>() {
        @Override
        public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
            return false;
        }

        @Override
        public boolean onResourceReady(File resource, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
            saveFile(context,resource, mimeType);
            return false;
        }
    }).submit();
}

Получить файл mimeType

 public static String getMimeType(String url) {
    String mimeType = null;
    String extension = MimeTypeMap.getFileExtensionFromUrl(url);
    if (extension != null) {
        mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
    }
    return mimeType;
}

и сохранить файл на внешний хранилище

public  Uri saveFile(Context context, File file, String mimeType) {
    String folderName = "Pictures";
    String extension = ".jpg";
    if(mimeType.endsWith("gif")){
        extension = ".gif";
    }else if(mimeType.startsWith("image/")){
        extension = ".jpg";
    }else if(mimeType.startsWith("video/")){
        extension = ".mp4";
        folderName = "Movies";
    }else if(mimeType.startsWith("audio/")){
        extension = ".mp3";
        folderName = "Music";
    }else if(mimeType.endsWith("pdf")){
        extension = ".pdf";
        folderName = "Documents";
    }
    if (android.os.Build.VERSION.SDK_INT >= 29) {
        ContentValues values = new ContentValues();
        values.put(MediaStore.Files.FileColumns.MIME_TYPE, mimeType);
        values.put(MediaStore.Files.FileColumns.DATE_ADDED, System.currentTimeMillis() / 1000);
        values.put(MediaStore.Files.FileColumns.DATE_TAKEN, System.currentTimeMillis());
        values.put(MediaStore.Files.FileColumns.RELATIVE_PATH, folderName);
        values.put(MediaStore.Files.FileColumns.IS_PENDING, true);
        values.put(MediaStore.Files.FileColumns.DISPLAY_NAME, "file_" + System.currentTimeMillis() + extension);
        Uri uri = null;
        if(mimeType.startsWith("image/")){
            uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        }else if(mimeType.startsWith("video/")){
            uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
        }else if(mimeType.startsWith("audio/")){
            uri = context.getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
        }else if(mimeType.endsWith("pdf")){
            uri = context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
        }
        if (uri != null) {
            try {
                saveFileToStream(context.getContentResolver().openInputStream(Uri.fromFile(file)), context.getContentResolver().openOutputStream(uri));
                values.put(MediaStore.Video.Media.IS_PENDING, false);
                context.getContentResolver().update(uri, values, null, null);
                return uri;
            } catch (IOException e) {
                e.printStackTrace();
            }

            return null;
        }
    }
    return null;
}

сохранить файл в поток

private void saveFileToStream(InputStream input, OutputStream outputStream) throws IOException {
    try {
        try (OutputStream output = outputStream ){
            byte[] buffer = new byte[4 * 1024]; // or other buffer size
            int read;

            while ((read = input.read(buffer)) != -1) {
                output.write(buffer, 0, read);
            }
            output.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    } finally {
        input.close();
    }
}

Я пробовал с эмулятором Android 29. Работает нормально.

Примечание : getExternalStorageDirectory () устарел на уровне API 29. Для улучшения конфиденциальности пользователей прямой доступ к общим / внешним устройствам хранения не рекомендуется. Когда приложение предназначается для Build.VERSION_CODES.Q, путь, возвращаемый этим методом, больше не доступен напрямую для приложений. Приложения могут по-прежнему получать доступ к содержимому, хранящемуся в общем / внешнем хранилище, путем перехода на другие альтернативы, такие как Context # getExternalFilesDir (String), MediaStore или Intent # ACTION_OPEN_DOCUMENT.

0 голосов
/ 03 апреля 2020

Наконец, после многих часов, мне удается это сделать, но в качестве крайней меры я использую .getBytes(maximum_file_size) вместо .getFile(file_object).

Большое спасибо @Kasim за то, что он выдвинул идею getBytes(maximum_file_size) также Пример кода, работающего с InputStream и OutputStream. Поиск по SO topi c, связанным с вводом / выводом, также очень помогает здесь и здесь

Идея в том, что .getByte(maximum_file_size) загрузит файл из корзины и вернет byte[] при обратном вызове addOnSuccessListener. Недостатком является то, что вы должны указать размер файла, который вам разрешено загружать, и никакие вычисления прогресса загрузки не могут быть выполнены AFAIK, если вы не поработали с outputStream.write(0,0,0); Я попытался записать его по частям, как здесь , но решение выдает ArrayIndexOutOfBoundsException, так как вы должны быть аккуратны при работе с индексом в массиве.

Итак, вот код, который позволяет вам сохранять файл из вашего хранилища Firebase в каталоги вашего устройства по умолчанию: storage/emulated/0/Pictures, storage/emulated/0/Movies, storage/emulated/0/Documents, you name it

//Member variable but depending on your scope
private ByteArrayInputStream inputStream;
private Uri downloadedFileUri;
private OutputStream stream;

//Creating a reference to the link
    StorageReference httpsReference = FirebaseStorage.getInstance().getReferenceFromUrl(downloadURL);

    Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    String type = "";
    String mime = "";
    String folderName = "";

    if (downloadURL.contains("jpg") || downloadURL.contains("jpeg")
            || downloadURL.contains("png") || downloadURL.contains("webp")
            || downloadURL.contains("tiff") || downloadURL.contains("tif")) {
        type = ".jpg";
        mime = "image/*";
        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        folderName = Environment.DIRECTORY_PICTURES;
    }
    if (downloadURL.contains(".gif")){
        type = ".gif";
        mime = "image/*";
        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        folderName = Environment.DIRECTORY_PICTURES;
    }
    if (downloadURL.contains(".mp4") || downloadURL.contains(".avi")){
        type = ".mp4";
        mime = "video/*";
        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        folderName = Environment.DIRECTORY_MOVIES;
    }
    if (downloadURL.contains(".mp3")){
        type = ".mp3";
        mime = "audio/*";
        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
        folderName = Environment.DIRECTORY_MUSIC;
    }

            final String relativeLocation = folderName + "/" + getString(R.string.app_name);

        final ContentValues contentValues = new ContentValues();
        contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, UUID.randomUUID().toString() + type);
        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mime); //Cannot be */*
        contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation);

        ContentResolver resolver = getContentResolver();
        Uri uriResolve = resolver.insert(contentUri, contentValues);

        try {

            if (uriResolve == null || uriResolve.getPath() == null) {
                throw new IOException("Failed to create new MediaStore record.");
            }

            stream = resolver.openOutputStream(uriResolve);

            //This is 1GB change this depending on you requirements
            httpsReference.getBytes(1024 * 1024 * 1024)
            .addOnSuccessListener(bytes -> {
                try {

                    int bytesRead;

                    inputStream = new ByteArrayInputStream(bytes);

                    while ((bytesRead = inputStream.read(bytes)) > 0) {
                        stream.write(bytes, 0, bytesRead);
                    }

                    inputStream.close();
                    stream.flush();
                    stream.close();
//FINISH

                } catch (IOException e) {
                    closeSession(resolver, uriResolve, e);
                    e.printStackTrace();
                    Crashlytics.logException(e);
                }
            });

        } catch (IOException e) {
            closeSession(resolver, uriResolve, e);
            e.printStackTrace();
            Crashlytics.logException(e);

        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...