Компания
27,61
рейтинг
13 марта 2015 в 15:04

Разработка → Особенности работы с файлами в приложениях на разных мобильных платформах

При разработке кросс-платформенного мобильного приложения, имеющего в своём функционале работу с файлами, встаёт вопрос об организации процессов работы с файлами на каждой платформе. С данным вопросом мы столкнулись при разработке новой версии Edusty, позволяющей делиться файлами со своими одногруппниками. В этой статье мы расскажем как происходит импорт и экспорт файлов в приложениях, работающих на операционных системах iOS, Android, Windows Phone.




iOS


В операционной системе iOS понятие файловая система скрыта для пользователя и взаимодействие с файлами осуществляется средствами самого приложения и только с файлами, расположенными в директории приложения. Импортировать файл в директорию приложения можно несколькими путями – с помощью iTunes File Sharing или регистрацией File Types для приложения.

При использовании iTunes File Sharing приложение будет отображено в iTunes в разделе «Общие файлы», где можно добавить файлы в приложение с компьютера. Файлы, добавленные таким способом, попадают в директорию /Documents приложения.



Приложение должно само контролировать эту директорию, на предмет появления новых файлов. Так же нужно иметь ввиду, что iTunes File Sharing фактически, открывает прямой доступ пользователю к документам, а это означает что файлы в данной директории в любой момент могут быть переименованы, удалены и т. д.

Для использования iTunes File Sharing необходимо добавить флаг UIFileSharingEnabled (Application supports iTunes file sharing) в info.plist файл приложения.

<key>UIFileSharingEnabled</key>
<true/>


При регистрации File Types, приложение появится в списке выбора для открытия файла, при нажатии на стандартное диалоговое меню “открыть с помощью”.



При открытии файла таким способом, его копия помещается в директорию /Documents/Inbox, а в приложении вызывается метод application:openURL:sourceApplication:annotation: протокола UIApplicationDelegate, в котором передаётся url открываемого файла.

Файлы в директории /Documents/Inbox можно читать и удалять, но нельзя изменять. Для изменения файл необходимо перенести в другую директорию, например в /Documents.

Для возможности импорта файлов данным способом необходимо добавить ключ CFBundleDocumentTypes (Document types) в info.plist файл приложения. Его значением является массив, каждый элемент которого словарь, используемый для описания каждого типа документа, поддерживаемого приложением. Полное описание возможных ключений и их значений можно найти в документации: developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html

Мы не стали делать ограничений на типы файлов, а сделали возможность импорта любых файлов. Info.plist при этом выглядит следующим образом:

<key>CFBundleDocumentTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeName</key>
			<string>All files</string>
			<key>CFBundleTypeRole</key>
			<string>Viewer</string>
			<key>LSHandlerRank</key>
			<string>Alternate</string>
			<key>LSItemContentTypes</key>
			<array>
				<string>public.data</string>
			</array>
		</dict>
	</array>


Для экспорта файлов из приложения используется тоже самое диалоговое меню “открыть с помощью”, которое инициализирует UIDocumentInteractionController. Есть возможность открыть сразу список приложений для открытия файла, открыть список приложений для открытия файла вместе с стандартными службами, такими как печать файла, пересылка по почте и т.д. а так же есть возможность открыть файл встроенным предпросмоторщиком, откуда так же доступна кнопка для открытия файла другим приложением.



Android


В Android приложения могут получить возможность доступа к файлам, кроме тех, что находятся в приватных директориях приложений.
Для выполнения таких операций как выбор файла, отправка email или открытие ссылки в браузере используются Intents (Намерения).
Чтобы выбрать файл из файловой системы, необходимо использовать действие ACTION_GET_CONTENT.
С помощью метода setType() можно указать какие типы файлов будут доступны для выбора. Например, если указать
setType(“audio/mp3”), то в приложении для просмотра файлов мы будем видеть только файлы с расширением .mp3, или указать “*/*”, чтобы отображались все файлы. После чего вызываем метод startActivityForResult(), где в качестве параметра передаем Intent.сreateChooser(), который создаёт диалог выбора приложения.
Так же в файле манифеста необходимо дописать необходимые разрешения:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />




После того, как мы выбрали файл в файловом менеджере мы попадаем в метод onActivityResult(int requestCode, int resultCode, Intent data), где в data будет Uri нашего файла.

Для открытия файлов из сервиса используется действие ACTION_VIEW. Выбор приложения для открытия файла происходит при помощи класса FileOpen (найденный на просторах StackOverFlow и немного переделанный), в котором проверяем какой тип файла мы пытаемся открыть, согласно его потенциально возможному расширению.

FileOpen.class
public class FileOpen {

    public static void openFile(Context context, File url) throws IOException {
        File file = url;
        Uri uri = Uri.fromFile(file);

        Intent intent = new Intent(Intent.ACTION_VIEW);

        if (url.toString().contains(".doc") || url.toString().contains(".docx") || url.toString().contains(".odt")) {
            // Word document
            intent.setDataAndType(uri, "application/msword");
        } else if (url.toString().contains(".pdf")) {
            // PDF file
            intent.setDataAndType(uri, "application/pdf");
        } else if (url.toString().contains(".ppt") || url.toString().contains(".pptx")) {
            // Powerpoint file
            intent.setDataAndType(uri, "application/vnd.ms-powerpoint");
        } else if (url.toString().contains(".xls") || url.toString().contains(".xlsx")) {
            // Excel file
            intent.setDataAndType(uri, "application/vnd.ms-excel");
        } else if (url.toString().contains(".zip") || url.toString().contains(".rar")) {
            // ZIP Files
            intent.setDataAndType(uri, "application/zip");
        } else if (url.toString().contains(".rtf")) {
            // RTF file
            intent.setDataAndType(uri, "application/rtf");
        } else if (url.toString().contains(".wav") || url.toString().contains(".mp3")) {
            // WAV audio file
            intent.setDataAndType(uri, "audio/x-wav");
        } else if (url.toString().contains(".gif")) {
            // GIF file
            intent.setDataAndType(uri, "image/gif");
        } else if (url.toString().contains(".jpg") || url.toString().contains(".jpeg") || url.toString().contains(".png")) {
            // JPG file
            intent.setDataAndType(uri, "image/jpeg");
        } else if (url.toString().contains(".txt")) {
            // Text file
            intent.setDataAndType(uri, "text/plain");
        } else if (url.toString().contains(".3gp") || url.toString().contains(".mpg") || url.toString().contains(".mpeg") || url.toString().contains(".mpe") || url.toString().contains(".mp4") || url.toString().contains(".avi")) {
            // Video files
            intent.setDataAndType(uri, "video/*");
        } else {
            intent.setDataAndType(uri, "*/*");
        }
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}


При нахождении совпадения, Используем метод setDataAndType(), чтобы указать Uri и тип MIME для передаваемых данных.



Если файл содержит формат, не рассмотренный нами, то в setDataAndType() в качестве типа указываем “*/*”. Таким образом система покажем нам все установленные на устройстве приложения, чтобы можно было самим выбрать с помощью чего открыть файл.

Windows Phone


Универсальным приложениям для Windows Store/Windows Phone Store практически полностью открыта файловая система, по крайней мере не системные папки. Класс OpenFilePicker позволяет на обеих платформах открывать файлы из любого доступного источника: память устройства, внешние носители и устройства, облака (если установлены соответствующие приложения). В связи с этим каких-либо проблем с открытием файлов на этих устройствах нет, стандартный класс даже позволяет сделать фото через меню выбора файла, что весьма удобно.

Для того, чтобы открыть файлы через стандартный диалог выбора файла, необходимо создать экземпляр класса OpenFilePicker и проинициилировать его несколькими параметрами:
List<string> .FileTypeFilter
— коллекция разрешённых расширений файлов (чтобы позволить выбирать любые файлы, нужно добавить запись "*").
 PickerLocationId .SuggestedStartLocation
— этот перечислимый тип позволяет указать локацию, которая откроется первой в диалоге (например: PickerLocationId.ComputerFolder).
PickerViewMode .ViewMode
— позволяет выбрать тип отображения файлов (список или сетка).

Вызов диалога выбора файла на Windows 8.1 и Windows Phone 8.1 различается, не смотря на универсальный API (как известно, он не на 100% универсальный).

Для Windows 8.1 приложения необходимо вызвать метод .PickMultipleFilesAsync() или .PickSingleFileAsync() в зависимости от того, необходимо выбрать один файл или несколько. Эти методы возвращают соответственно IReadOnlyList<StorageFile> и StorageFile (строго говоря, они возвращают Task, поэтому необходимо использовать async/await).



Для Windows Phone 8.1 всё несколько сложнее. Аналогичные методы называются .PickMultipleFilesAndContinue() и .PickSingleFileAndContinue(). Оба метода ничего не возвращают (void), однако после выбора файлов в диалоге вызывается метод ContinueFileOpenPicker(), который является членом интерфейса IFileOpenPickerContinuable. Этот интерфейс не встроен в стандартный API, однако его можно взять в классе ContinuationManager. Подробнее о том, как продолжить работу после вызова методов *AndContinue(), а также скачать класс ContinuationManager можно здесь. И так, после того как класс ContnuationManager добавлен к проекту по инструкции, необходимо класс, из которого вызывается метод .PickMultipleFilesAsync() или .PickSingleFileAsync(), унаследовать от интерфейса IFileOpenPickerContinuable. Затем нужно реализовать метод void ContinueFileOpenPicker(FileOpenPickerContinuationEventArgs args), где в args.Files содержится коллекция объектов StorageFile.



Открытие файлов из сервиса производится путём сохранения файла на диск во внутреннюю папку приложения и последующим указанием его в методе LaunchFileAsync() класса Launcher. После этого система либо открывает файл приложением по умолчанию для данного типа файла, либо предлагает выбрать приложение из установленных, либо предлагает найти его в магазине.
Автор: @SUDALV
Edusty
рейтинг 27,61
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Комментарии (4)

  • +7
    В Android полностью открытая файловая система, в связи с чем приложения имеют возможность получить доступ к файлам, находящимся в любой директории на устройстве.

    Это не так, вам открыто только external_storage, и только если у вас есть на это разрешение в манифесте. Кроме того, у каждого приложения есть своя приватная директория, куда вы можете класть файлы и быть уверенным, что их никто не тронет (пользователи с рутом должны понимать последствия сами).
    • +2
      Согласен с замечанием по поводу приватных директорий приложений (исправили в статье), однако система позволяет считать файлы из системных директорий, например /system (через приложение ES проводник)
  • +3
    Да, на часть директорий из корня есть права на чтение, но это сделано только для работы приложений, они же должны иметь доступ к шрифтам, системным библиотекам, компонентам фреймворка и т.д.

    Но для прикладных целей там ничего интересного :)
  • 0
    Мне не сильно нравится лесенка условий в примере FileOpen.class. Для данного подхода можно воспользоваться уже имеющимся MimeTypeMap классом, и если чего то там вам не хватает, то всегда можно его расширить. (MimeTypeMap)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка