Android studio копирование файла
В работе над своим проектом больше всего времени я убил на то, чтобы разобраться, как правильно сохранить файл в общедоступную папку, например, Download. Мне не удалось найти четкого и ясного объяснения в интернете. Собирал информацию по частям и доходил до результата методом проб и ошибок.
Виной всей этой сложности - множество факторов. Языковой барьер: русский - английский, Kotlin - Java. Различия в способах копирования в разных версиях Android. Разобраться было не просто. В итоге - пишу этот гайд, чтобы облегчить жизнь тем, кто пойдет за мной следом.
11 Answers 11
To copy a file and save it to your destination path you can use the method below.
On API 19+ you can use Java Automatic Resource Management:
Thank you. after banging my head I found the problem was missing permission to write to external storage. now it works fine.
@mohitum007 if the file fails to copy then an exception is thrown. use a try catch block when calling the method.
If an exception is thrown, the streams would not be closed until they're garbage collected, and that's not good. Consider closing them in finally .
Please, close both Streams inside a finally, if there is an Excepcion, your streams memory won't be collected.
Alternatively, you can use FileChannel to copy a file. It might be faster than the byte copy method when copying a large file. You can't use it if your file is bigger than 2GB though.
transferTo can throw an exception and in that case you are leaving streams open. Just like Pang and Nima commented in accepted answer.
Also, transferTo should be called inside of loop since it does not guarantee that it will transfer the total amount requested.
I tried your solution and it fails for me with the exception java.io.FileNotFoundException: /sdcard/AppProj/IMG_20150626_214946.jpg: open failed: ENOENT (No such file or directory) at the FileOutputStream outStream = new FileOutputStream(dst); step. According to the text I realize, that the file doesn't exist, so I check it and call dst.mkdir(); if needed, but it still doesn't help. I also tried to check dst.canWrite(); and it returned false . May this is the source of the problem? And yes, I have
@ViktorBrešan After API 19 you can use Java automatic resource management by defining the in and out streams in the opening of your try try ( FileInputStream inStream = new FileInputStream(src); FileOutputStream outStream = new FileOutputStream(dst) )
Is there any way to make this solution publish its progress to onProgressUpdate , so I could show it in a ProgressBar? In the accepted solution I can calculate progress in the while loop, but I can't see how to do it here.
I have a few files in the assets folder. I need to copy all of them to a folder say /sdcard/folder. I want to do this from within a thread. How do I do it?
Compression
First you need to ensure that the file is stored uncompressed. The packaging system may choose to compress any file with an extension that is not marked as noCompress, and compressed files cannot be memory mapped, so you will have to rely on AssetManager.open in that case.
You can add a '.mp3' extension to your file to stop it from being compressed, but the proper solution is to modify your app/build.gradle file and add the following lines (to disable compression of PDF files)
Ответ
Создание копии базы данных работает на всех устройствах Android. Мое приложение не поддерживает версии меньше 6.0 Marshmellow (SdkVersion 23), поэтому на самых ранних версиях я и не проверял.
Очень надеюсь, что мое описание процесса сэкономит вам время, ибо я его в таком полном виде в интернете не встречал.
🍀 Творите добро, пусть мир будет лучше!
🤝 Задавайте вопросы, пишите комментарии. Нужно ли что-нибудь пояснить более подробно?
Do I really need to open the contents of the file and write it to another file?
What is the best way to do so?
Задача
Я понял, что все данные приложения, в том числе и файл базы данных находятся во внутренней папке, к которой доступ из-вне получить невозможно. Посему, нужно каким-то образом сделать копию файла базы данных из внутреннего хранилища в общедоступную папку. Я выбрал папку Download.
Finding the correct part of the packed file
Once you've ensured your file is stored uncompressed, you can use the AssetManager.openFd method to obtain an AssetFileDescriptor, which can be used to obtain a FileInputStream (unlike AssetManager.open, which returns an InputStream) that contains a FileChannel. It also contains the starting offset (getStartOffset) and size (getLength), which you need to obtain the correct part of the file.
Решение
1. Правлю файл Манифеста
Я так понял, что этого достаточно. При разрешенном доступе для записи, доступ для чтения можно не получать.
2. Обрабатываю исключение (exeption)
3. Проверяю версию Android
Смотрю информацию о версии в файле build.gradle (Module)
В моем проекте указано, что целевая версия - 30, минимальная - 23. Это значит, что устройства с такой минимальной версией должны справляться с моим приложением успешно. Для большей ясности о версиях Android можно посмотреть на следующий скриншот:
Нумерация версий Android с их сладкими названиями
В табличке выше смотрю столбец API Level. Android 6.0 Marshmellow - минимальная версия Android, 84,9% устройств будет поддерживаться.
Android 10 в коде имеет маркировку "Q" (Build.VERSION_CODES.Q) Оказывается, что копирование файлов, а именно организация доступа к общедоступным папкам, с этой версии Android и выше выглядит совершенно иначе!
Кусок кода с проверкой версии Android и некоторой лирикой от разработчиков Google
Зачем? Чтобы дать вам почувствовать вкус вашего будущего, предварительный просмотр того, что грядет. Con permiso, Capitan. Зал арендован, оркестр задействован. Пришло время посмотреть, умеешь ли ты танцевать.
Видимо я танцевать научился, иначе бы не писал эту статью. Я понял, что код для копирования файла в общедоступную папку для Android 10+ должен быть другим.
Засим в обязательном порядке проверяю версию и готовлюсь писать разный код. Печаль.
4. Копирую, если версия Android >= 10 (SdkVersion >= 29)
В этом случае никаких разрешений получать не надо. По-моему, даже в Манифесте можно ничего не указывать. Тут и правда мне пришлось, как у нас говорят - "потанцевать с бубном", пока я не разобрался что к чему.
Здесь я пользуюсь компонентом MediaStore. Его использование похоже на работу с базой данных. Сначала "запись" со всеми ее полями прописывается в contentValues. Я пишу тут только имя файла в колонке DISPLAY_NAME, хотя в примерах видел, что указывают еще размер SIZE и некоторые другие поля. Но у меня работает и так.
Потом при помощи contentResolver вставляю свою запись в таблицу MediaStore.Downloads.EXTERNAL_CONTENT_URI. В итоге получаю путь к файлу в формате Uri и только после этого по нему копирую данные.
5. Проверяю, получены ли разрешения
Для Android 9 и ниже мало указать в Манифесте разрешения для доступа. Их еще нужно получить. Доступ к общим папкам приложению может дать пользователь, если его об этом запросить или он сам в настройках указал разрешения.
Настройки -> Приложения -> Разрешения
Для того, чтобы проверить, есть ли у моего приложения разрешение доступа к хранилищу, пишу следующий код:
Попытки копировать без разрешения не увенчаются успехом.
6. Запрашиваю разрешение, если его нет
Можно попросить пользователя дать разрешение приложению на копирование файлов в общие папки. Выглядеть оно может примерно так:
Предоставить приложению доступ к фотографиям, медиа и файлам на устройстве?
Код запроса состоит из двух частей. Первая - спрашивает. Вторая - получает ответ (callback).
Этот нехитрый код покажет пользователю окно с вопросом из скриншота выше и предложит разрешить (allow) или запретить (deny) доступ. Request_code - любое число, с которым в последствии нужно свериться, чтобы убедиться, что пришел ответ именно для вашего приложения.
7. Обрабатываю ответ пользователя
Ответ буду получать в другой части программы при помощи функции onRequestPermissionsResult. Она запускается при нажатии пользователем "Allow" или "Deny".
Код выше даю в том виде, как он написан у меня без пропусков. Проверяю, что пришло именно разрешение WRITE_EXTERNAL_STORAGE и requestCode тоже совпадает. Тогда только смотрю результат. Если он - PERMISSION_GRANTED, то копирую, не забыв упаковать копирование в try.
8. Копирую, если версия Android < 10 (SdkVersion < 29)
Код копирования в данном случае упаковал в отдельную функцию, т.к. он запускается из разных частей программы. Если разрешение получено, то сразу копирую и после получения разрешения, если его еще не было.
Задача
Я понял, что все данные приложения, в том числе и файл базы данных находятся во внутренней папке, к которой доступ из-вне получить невозможно. Посему, нужно каким-то образом сделать копию файла базы данных из внутреннего хранилища в общедоступную папку. Я выбрал папку Download.
File packing
Note that the packager can still pack multiple files into one, so you can't just read the whole file the AssetManager gives you. You need to to ask the AssetFileDescriptor which parts you need.
22 Answers 22
If anyone else is having the same problem, this is how I did it
I would also not rely on sdcard being located at /sdcard, but retrieve the path with Environment.getExternalStorageDirectory()
Should i use: 16* 1024 (16kb) I tend to opt for 16K or 32K as a good balance between memory usage and performance.
@rciovati got this runtime error Failed to copy asset file: myfile.txt java.io.FileNotFoundException: myfile.txt at android.content.res.AssetManager.openAsset(Native Method)
For me this code works only if I add this: in = assetManager.open("images-wall/"+filename); where "images-wall" is my folder inside assets
Based on your solution, I did something of my own to allow subfolders. Someone might find this helpful:
assetManager.list(path) may be slow on device, to create assets paths list beforehand this snippet may be used from assets dir: find . -name "*" -type f -exec ls -l <> \; | awk '
Nice solution! The only required fix is to trim leading separators at the beginning of copyFileOrDir(): path= path.startsWith("/") ? path.substring(1) : path;
The solution above did not work due to some errors:
- directory creation did not work
- assets returned by Android contain also three folders: images, sounds and webkit
- Added way to deal with large files: Add extension .mp3 to the file in the assets folder in your project and during copy the target file will be without the .mp3 extension
Here is the code (I left the Log statements but you can drop them now):
EDIT: Corrected a misplaced ";" that was throwing a systematic "could not create dir" error.
I know this has been answered but I have a slightly more elegant way to copy from asset directory to a file on the sdcard. It requires no "for" loop but instead uses File Streams and Channels to do the work.
(Note) If using any type of compressed file, APK, PDF, . you may want to rename the file extension before inserting into asset and then rename once you copy it to SDcard)
A way to copy a file without having to loop through it.
Liked this over the other solutions, little bit neater. Slight modification on mine that includes creating missing fileFolders. cheers!
This wouldnt work past the file descriptor for me, This file can not be opened as a file descriptor; it is probably compressed -- it is a pdf file. Know how to fix that?
This assumes that inChannel.size() returns the size of the size of the file. It makes no such guarantee. I'm getting 2.5 MiB for 2 files that are 450 KiB each.
In addition to the above, the asset may not start at location 0 in the file descriptor. AssetFileDescriptor.getStartOffset() will return the starting offset.
This would be concise way in Kotlin.
try out this it is much simpler ,this will help u:
Here is a cleaned up version for current Android devices, functional method design so that you can copy it to an AssetsHelper class e.g ;)
Modified this SO answer by @DannyA
Preparations
in src/main/assets add folder with name fold
Usage
In to the external directory find all files and directories that are within the fold assets
Using some of the concepts in the answers to this question, I wrote up a class called AssetCopier to make copying /assets/ simple. It's available on github and can be accessed with jitpack.io:
Copy all files and directories from assets to your folder!
for copying better use apache commons io
//THIS IS MAIN METHOD FOR COPY
Based on Rohith Nandakumar's solution, I did something of my own to copy files from a subfolder of assets (i.e. "assets/MyFolder"). Also, I'm checking if the file already exists in sdcard before trying to copy again.
Based on Yoram Cohen answer, here is a version that supports non static target directory.
Invoque with copyFileOrDir(getDataDir(), "") to write to internal app storage folder /data/data/pkg_name/
- Supports subfolders.
- Supports custom and non-static target directory
Avoids copying "images" etc fake asset folders like
There are essentially two ways to do this.
First, you can use AssetManager.open and, as described by Rohith Nandakumar and iterate over the inputstream.
I will describe the openFd method here.
Implementation
An example implementation is given below:
This answer is based on JPM's answer.
You can do it in few steps using Kotlin, Here I am copying only few files instead of all from asstes to my apps files directory.
And here is the extension function,
LOGCAT
change parts of code like these:
the before example is for Pdfs, in case of to example .txt
Hi Guys I Did Something like this. For N-th Depth Copy Folder and Files to copy. Which Allows you to copy all the directory structure to copy from Android AssetManager :)
In the end Create a Asynctask:
call it From your activity:
Slight modification of above answer to copy a folder recursively and to accommodate custom destination.
For those who are updating to Kotlin:
Following this steps to avoid FileUriExposedExceptions , supposing user has granted WRITE_EXTERNAL_STORAGE permission and your file is in assets/pdfs/mypdf.pdf .
That is my personalized text extractor class, hope that will be usefull.
To use that you need traineddata file. You can download trainddata file from this link.
Once you’ve downloaded the traineddata file you want, you need to make an Android Resource directory named assets in your android project. In the newly created assets folder, you need to create a regular directory named “tessdata” where you can place your traineddata files. Finally you have to init the "TextExtractor" class in your MainActivity.
First parameter is the context, the second one is the name of directory just created and the last one is the language of traineddata just downloaded.
To extract text you have to call the "extractText" method:
Note that extractText need a BitMap image to work!! You can create a BitMap image from your drawable file with this line:
Use AssetManager, it allows to read the files in the assets. Then use regular Java IO to write the files to sdcard.
Google is your friend, search for an example.
As you can see, just create an instance of CopyAssets in your java class which has an activity. Now this part is important, as far as my testing and researching on the internet, You cannot use AssetManager if the class has no activity . It has something to do with the context of the java class.
Now, the c.copyAssets(getApplicationContext()) is an easy way to access the method, where c is and instance of CopyAssets class. As per my requirement, I allowed the program to copy all my resource files inside the asset folder to the /www/resources/ of my internal directory.
You can easily find out the part where you need to make changes to the directory as per your use. Feel free to ping me if you need any help.
Работа с настройками уровня activity и приложения позволяет сохранить небольшие данные отдельных типов (string, int), но для работы с большими массивами данных, такими как графически файлы, файлы мультимедиа и т.д., нам придется обращаться к файловой системе.
ОС Android построена на основе Linux. Этот факт находит свое отражение в работе с файлами. Так, в путях к файлам в качестве разграничителя в Linux использует слеш "/", а не обратный слеш "\" (как в Windows). А все названия файлов и каталогов являются регистрозависимыми, то есть "data" это не то же самое, что и "Data".
Приложение Android сохраняет свои данные в каталоге /data/data// и, как правило, относительно этого каталога будет идти работа.
Для работы с файлами абстрактный класс android.content.Context определяет ряд методов:
boolean deleteFile (String name) : удаляет определенный файл
String[] fileList () : получает все файлы, которые содержатся в подкаталоге /files в каталоге приложения
File getCacheDir() : получает ссылку на подкаталог cache в каталоге приложения
File getDir(String dirName, int mode) : получает ссылку на подкаталог в каталоге приложения, если такого подкаталога нет, то он создается
File getExternalCacheDir() : получает ссылку на папку /cache внешней файловой системы устройства
File getExternalFilesDir(String type) : получает ссылку на каталог /files внешней файловой системы устройства
File getFileStreamPath(String filename) : возвращает абсолютный путь к файлу в файловой системе
FileInputStream openFileInput(String filename) : открывает файл для чтения
FileOutputStream openFileOutput (String name, int mode) : открывает файл для записи
Все файлы, которые создаются и редактируются в приложении, как правило, хранятся в подкаталоге /files в каталоге приложения.
Для непосредственного чтения и записи файлов применяются также стандартные классы Java из пакета java.io.
Итак, применим функционал чтения-записи файлов в приложении. Пусть у нас будет следующая примитивная разметка layout:
Поле EditText предназначено для ввода текста, а TextView - для вывода ранее сохраненного текста. Для сохранения и восстановления текста добавлены две кнопки.
Теперь в коде Activity пропишем обработчики кнопок с сохранением и чтением файла:
При нажатии на кнопку сохранения будет создаваться поток вывода FileOutputStream fos = openFileOutput(FILE_NAME, MODE_PRIVATE)
В данном случае введенный текст будет сохраняться в файл "content.txt". При этом будет использоваться режим MODE_PRIVATE
Система позволяет создавать файлы с двумя разными режимами:
MODE_PRIVATE : файлы могут быть доступны только владельцу приложения (режим по умолчанию)
MODE_APPEND : данные могут быть добавлены в конец файла
Поэтому в данном случае если файл "content.txt" уже существует, то он будет перезаписан. Если же нам надо было дописать файл, тогда надо было бы использовать режим MODE_APPEND:
Для чтения файла применяется поток ввода FileInputStream :
В итоге после нажатия кнопки сохранения весь текст будет сохранен в файле /data/data/название_пакета/files/content.txt
Где физически находится созданный файл? Чтобы увидеть его на подключенном устройстве перейдем в Android Stud в меню к пункту View -> Tool Windows -> Device File Explorer
После этого откроектся окно Device File Explorer для просмотра файловой системы устройства. И в папке data/data/[название_пакета_приложения]/files мы сможем найти сохраненный файл.
Одним из наиболее распространенных источников ресурсов являются файлы изображений. Android поддерживает следующие форматы файлов: .jpg (предпочтителен), .jpg (приемлем), .jpg (нежелателен). Для графических файлов в проекте уже по умолчанию создана папка res/drawable . По умолчанию она уже содержит ряд файлов - пару файлов иконок:
При добавлении графических файлов в эту папку для каждого из них Android создает ресурс Drawable . После этого мы можем обратиться к ресурсу следующим образом в коде Java:
Например, добавим в проект в папку res/drawable какой-нибудь файл изображения. Для этого скопируем на жестком диске какой-нибудь файл с расширением png или jpg и вставим его в папку res/drawable (для копирования в проект используется простой Copy-Paste)
Далее нам будет предложено выбрать папку - drawable или drawable-24 . Для добавления обычных файлов изображений выберем drawable :
Здесь сразу стоит учесть, что файл изображения будет добавляться в приложение, тем самым увеличивая его размер. Кроме того, большие изображения отрицательно влияют на производительность. Поэтому лучше использовать небольшие и оптимизрованные (сжатые) графические файлы. Хотя, также стоит отметить, что все файлы изображений, которые добавляются в эту папку, могут автоматически оптимизироваться с помощью утилиты aapt во время построения проекта. Это позволяет уменьшить размер файла без потери качества.
При копировании файла нам будет предложено установить для него новое имя.
Можно изменить название файла, а можно оставить так как есть. В моем случае файл называется dubi2.jpg . И затем нажмем на кнопку Refactor. И после этого в папку drawable будет добавлен выбранный нами файл изображения.
Для работы с изображениями в Android можно использовать различные элементы, но непосредственно для вывода изображений предназначен ImageView . Поэтому изменим файл activity_main.xml следующим образом:
В данном случае для отображения файла в ImageView у элемента устанавливается атрибут android:src . В его значении указывается имя графического ресурса, которое совпадает с именем файла без расширения. И после этого уже в Preview или в режиме дизайнере в Android Studio можно будет увидеть применение изображения, либо при запуске приложения:
Если бы мы создавали ImageView в коде java и из кода применяли бы ресурс, то activity могла бы выглядеть так:
В данном случае ресурс drawable напрямую передается в метод imageView.setImageResource() , и таким образом устанавливается изображение. В результате мы получим тот же результат.
Однако может возникнуть необходимость как-то обработать ресурс перед использованием или использовать его в других сценариях. В этом случае мы можем сначала получить его как объект Drawable и затем использовать для наших задач:
Для получения ресурса применяется метод ResourcesCompat.getDrawable() , в который передается объект Resources, идентификатор ресурса и тема. В данном случае тема нам не важна, поэтому для нее передаем значение null. Возвращается ресурс в виде объекта Drawable :
Затем, например, можно также передать ресурс объекту ImageView через его метод setImageDrawable()
Читайте также: