Создание удобного OpenFileDialog для Android
Наверное, как и многие разработчики под Android, столкнулся на днях с необходимостью реализовать в своем приложении выбор файла пользователем. Так как изначально в Android такого функционала нет, обратился к великому и ужасному. Это показалось мне странным, но из вороха вопросов на stackoverflow и небольшого числа отечественных форумов можно выделить всего три основных источника:
- Android File Dialog – почти все ссылки из stackoverflow ведут сюда. В принципе, неплохое решение, но реализовано через отдельную activity, а хотелось чего-то в духе OpenFileDialog’а из .Net.
- В данной статье речь идет вообще об отдельном файл-менеджере, и почерпнуть какие-то идеи из неё не удалось.
- Идея же отсюда очень понравилась, однако, как мне показалось реализовать все это можно несколько красивее.
В результате, начав реализовывать своё решение, я столкнулся с некоторыми трудностями решать которые показалось очень интересно. А посему, решил описать в данной статье не просто готовое решение, а все шаги, которые к нему привели. Желающие пройти их вместе –
Итак, приступим! В любой привычной среде (я использую IntelliJ IDEA) создадим новое приложение. На главной activity расположим одну единственную кнопку и напишем к ней, пока пустой, обработчик нажатия:
Создадим новый класс с конструктором:
а в обработчике кнопки вызовем диалог:
Кнопки показались, теперь надо бы и сами файлы найти. Начнем поиск с корня sdcard, для чего определим поле:
и реализуем следующий метод:
(так как главное требование к классу – работать сразу у любого разработчика, без подключения дополнительных библиотек, — то никаких google-collections использовать не будем, и с массивами приходится работать по старинке), а в конструкторе к вызову setNegativeButton добавим .setItems(getFiles(currentPath), null).
Что же, неплохо, однако файлы не отсортированы. Реализуем для этого дела Adapter как внутренний класс, заменим setItems на setAdapter и немного перепишем getFiles:
Еще лучше, но нам надо по клику на папке идти внутрь. Можно достучаться до встроенного listview, но я просто подменил его собственным (это потом пригодится). Плюс, изменения adapter’а внутри обработчика listview вызывало exception, и список файлов пришлось вынести в отдельное поле:
Отлично, вот только нажав на папку Android мы получаем список всего из одного каталога data, и наше окно тут же уменьшается в размере.
Возможно это нормально, но мне это не понравилось, и я стал искать возможности размер сохранить. Единственный найденный мною вариант – это установка setMinimumHeight. Установка этого свойства для listview вызвала дополнительные проблемы, но они решились оберткой его в LinearLayout:
Результат, все равно получился немного не таким, каким хотелось бы: при старте диалог развернут на весь экран, а после перехода в каталог Android – уменьшается до 750px. Да еще и экраны разных устройств имеют разную высоту. Решим сразу обе этих проблемы, установив setMinimumHeight в максимально возможную для текущего экрана:
Не нужно пугаться того, что мы устанавливаем в setMinimumHeight полный размер экрана, сама система уменьшит значение до максимально допустимого.
Теперь появляется проблема понимания пользователя, в каком каталоге он сейчас находится, и возврата вверх. Давайте разберемся с первой. Вроде все легко — установить значение title в currentPath и менять его при изменении последнего. Добавим в конструктор и в метод RebuildFiles вызов setTitle(currentPath).
А нет – заголовок не изменился. Почему не срабатывает setTitle после показа диалога, документация молчит. Однако мы может это исправить, создав свой заголовок и подменив им стандартный:
И снова не все ладно: если пройти достаточно далеко, то строка в заголовок влезать не будет
Решение с установкой setMaximumWidth не верно, так как пользователь будет видеть только начало длинного пути. Не знаю, насколько верно мое решение, но я сделал так:
Решим теперь проблему с возвратом. Это достаточно легко, учитывая, что у нас есть LinearLayout. Добавим в него еще один TextView и немного отрефракторим код:
Возможность возвращаться на шаг вверх, может привести пользователя в каталоги, к которым ему доступ запрещен, поэтому изменим функцию RebuildFiles:
(cообщение пока не очень информативное, но вскоре мы добавим разработчику возможность исправить это).
Ни один OpenFileDialog не обходится без фильтра. Добавим и его:
Обратите внимание — фильтр принимает регулярное выражение. Казалось бы – все хорошо, но первая выборка файлов сработает в конструкторе, до присвоения фильтра. Перенесем её в переопределенный метод show:
Осталось совсем чуть-чуть: вернуть выбранный файл. Опять же, я так и не понял зачем нужно устанавливать CHOICE_MODE_SINGLE, а потом все равно писать лишний код для подсветки выбранного элемента, когда он (код) и так будет работать без CHOICE_MODE_SINGLE, а потому обойдемся без него:
Источник
Правильная работа с файлами в Android
Сегодня я бы хотел рассказать вам о правильной работе с файлами в ОС Android. Итак, чаще всего у новичков возникают ситуации, когда обычные Java функции не могут корректно создать тот или иной файл в системе Android.
Во-первых, вам нужно обратить внимание на интересную особенность ОС:
когда вы устанавливаете apk приложение в эмулятор или телефон, система Linux (на которой базируется ядро Android) выделяет ему специальный User-ID, который является неким ключом доступа к (sandbox). То есть другие приложения в телефоне не смогут получить доступ к чтению файлов вашего приложения просто так. Кончено, всё это сделано в целях безопасности.
В общем, если вы запустите следующий код:
FileWriter f = new FileWriter(«impossible.txt»);
То этот код вызовет исключение: ‘java.io.FileNotFoundException: /impossible.txt ‘
Тогда как должен в случае отсутствия файла создать его.
Далее стоит отметить, что данное ограничение не распространяется на файлы, записываемые на SDCard. Туда можно писать любые файлы без всяких проблем, правда предварительно нужно добавить в AndroidManifest разрешение на запись:
Код файла на карту:
File fileName = null;
String sdState = android.os.Environment.getExternalStorageState();
if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) <
File sdDir = android.os.Environment.getExternalStorageDirectory();
fileName = new File(sdDir, «cache/primer.txt»);
> else <
fileName = context.getCacheDir();
>
if (!fileName.exists())
fileName.mkdirs();
try <
FileWriter f = new FileWriter(fileName);
f.write(«hello world»);
f.flush();
f.close();
> catch (Exception e) <
>
Как уже ранее было сказано мною, android приложение находится в некой песочнице, изолированной от воздействия со стороны других приложений по умолчанию. Для того, чтобы создать файл внутри этой песочницы, следует использовать функцию openFileOutput(). Хочу отметить 2 аргумента:
1. имя файла
2. режим доступа к нему со стороны чужих приложений
С первым аргументом все ясно, что касается второго, то режимов существует два: MODE_WORLD_READABLE и/или MODE_WORLD_WRITEABLE.
И ещё, чтобы записать файл можно использовать следующий код:
final String TESTSTRING = new String(«Hello Android»);
FileOutputStream fOut = openFileOutput(«samplefile.txt», MODE_WORLD_READABLE);
OutputStreamWriter osw = new OutputStreamWriter(fOut);
// записываем строку в файл
osw.write(TESTSTRING);
/* проверяем, что все действительно записалось и закрываем файл */
osw.flush();
osw.close();
Для чтения файлов используется метод openFileInput():
FileInputStream fIn = openFileInput(«samplefile.txt»);
InputStreamReader isr = new InputStreamReader(fIn);
char[] inputBuffer = new char[TESTSTRING.length()];
isr.read(inputBuffer);
String readString = new String(inputBuffer);
Для удаления используется метод deleteFile() в контексте приложения/активити. На этом я бы хотел закончить полезный пост, спасибо за внимание!
Источник
Open files using storage access framework
Android 4.4 (API level 19) introduces the Storage Access Framework (SAF). The SAF makes it simple for users to browse and open documents, images, and other files across all of their preferred document storage providers. A standard, easy-to-use UI lets users browse files and access recents in a consistent way across apps and providers.
Cloud or local storage services can participate in this ecosystem by implementing a DocumentsProvider that encapsulates their services. Client apps that need access to a provider’s documents can integrate with the SAF with just a few lines of code.
The SAF includes the following:
- Document provider—A content provider that allows a storage service (such as Google Drive) to reveal the files it manages. A document provider is implemented as a subclass of the DocumentsProvider class. The document-provider schema is based on a traditional file hierarchy, though how your document provider physically stores data is up to you. The Android platform includes several built-in document providers, such as Downloads, Images, and Videos.
- Client app—A custom app that invokes the ACTION_CREATE_DOCUMENT , ACTION_OPEN_DOCUMENT , and ACTION_OPEN_DOCUMENT_TREE intent actions and receives the files returned by document providers.
- Picker—A system UI that lets users access documents from all document providers that satisfy the client app’s search criteria.
Some of the features offered by the SAF are as follows:
- Lets users browse content from all document providers, not just a single app.
- Makes it possible for your app to have long term, persistent access to documents owned by a document provider. Through this access users can add, edit, save, and delete files on the provider.
- Supports multiple user accounts and transient roots such as USB storage providers, which only appear if the drive is plugged in.
Overview
The SAF centers around a content provider that is a subclass of the DocumentsProvider class. Within a document provider, data is structured as a traditional file hierarchy:
Figure 1. Document provider data model. A Root points to a single Document, which then starts the fan-out of the entire tree.
Note the following:
- Each document provider reports one or more ‘roots’, which are starting points into exploring a tree of documents. Each root has a unique COLUMN_ROOT_ID , and it points to a document (a directory) representing the contents under that root. Roots are dynamic by design to support use cases like multiple accounts, transient USB storage devices, or user login/logout.
- Under each root is a single document. That document points to 1 to N documents, each of which in turn can point to 1 to N documents.
- Each storage backend surfaces individual files and directories by referencing them with a unique COLUMN_DOCUMENT_ID . Document IDs must be unique and not change once issued, since they are used for persistent URI grants across device reboots.
- Documents can be either an openable file (with a specific MIME type), or a directory containing additional documents (with the MIME_TYPE_DIR MIME type).
- Each document can have different capabilities, as described by COLUMN_FLAGS . For example, FLAG_SUPPORTS_WRITE , FLAG_SUPPORTS_DELETE , and FLAG_SUPPORTS_THUMBNAIL . The same COLUMN_DOCUMENT_ID can be included in multiple directories.
Control flow
As stated above, the document provider data model is based on a traditional file hierarchy. However, you can physically store your data however you like, as long as you can access it by using DocumentsProvider API. For example, you could use tag-based cloud storage for your data.
Figure 2 shows how a photo app might use the SAF to access stored data:
Figure 2. Storage Access Framework Flow
Note the following:
- In the SAF, providers and clients don’t interact directly. A client requests permission to interact with files (that is, to read, edit, create, or delete files).
- The interaction starts when an application (in this example, a photo app) fires the intent ACTION_OPEN_DOCUMENT or ACTION_CREATE_DOCUMENT . The intent can include filters to further refine the criteria—for example, «give me all openable files that have the ‘image’ MIME type.»
- Once the intent fires, the system picker goes to each registered provider and shows the user the matching content roots.
- The picker gives users a standard interface for accessing documents, even though the underlying document providers may be very different. For example, figure 2 shows a Google Drive provider, a USB provider, and a cloud provider.
Figure 3 shows a picker in which a user searching for images has selected the Downloads folder. It also shows all of the roots available to the client app.
Figure 3. Picker
After the user selects the Downloads folder, the images are displayed. Figure 4 shows the result of this process. The user can now interact with these images in the ways that the provider and client app support.
Figure 4. Images stored in the Downloads folder, as viewed in the system picker
Writing a client app
On Android 4.3 and lower, if you want your app to retrieve a file from another app, it must invoke an intent such as ACTION_PICK or ACTION_GET_CONTENT . The user must then select a single app from which to pick a file and the selected app must provide a user interface for the user to browse and pick from the available files.
On Android 4.4 (API level 19) and higher, you have the additional option of using the ACTION_OPEN_DOCUMENT intent, which displays a system-controlled picker UI controlled that allows the user to browse all files that other apps have made available. From this single UI, the user can pick a file from any of the supported apps.
On Android 5.0 (API level 21) and higher, you can also use the ACTION_OPEN_DOCUMENT_TREE intent, which allows the user to choose a directory for a client app to access.
Note: ACTION_OPEN_DOCUMENT is not intended to be a replacement for ACTION_GET_CONTENT . The one you should use depends on the needs of your app:
- Use ACTION_GET_CONTENT if you want your app to simply read or import data. With this approach, the app imports a copy of the data, such as an image file.
- Use ACTION_OPEN_DOCUMENT if you want your app to have long term, persistent access to documents owned by a document provider. An example would be a photo-editing app that lets users edit images stored in a document provider.
For more information on how to support browsing for files and directories using the system picker UI, see the guide on how to access documents and other files.
Additional resources
For more information about document providers, take advantage of the following resources:
Источник