На прошлой неделе мы говорили про отправку файлов в приложение Xamarin.Forms для iOS, как и обещали, во второй части речь пойдёт про Android.
Отправка файлов в приложение Xamarin.Forms: часть 1 (iOS) и часть 2 (Android).
Сценарий
Снова сценарий, о котором пойдет речь:
- Написание Xamarin.Forms приложения.
- Это приложение позволяет обрабатывать файлы определенного типа (в демо-приложении, рассмотренном в этом посте это PDF файлы).
- ОС должна знать, что приложение способно оперировать с файлами данного типа.
- Приложение должно появиться в списке приложений, чтобы отправить файл.
Приступим.
Регистрация в Android
Также как Intent’ы используются для обмена данными между компонентами Activity, можно использовать Intent’ы для того же между приложениями.
Явные и неявные Intent’ы
Когда точно понятно какой компонент Activity необходимо открыть в приложении – нужно создать явный Intent. Когда создается явный Intent – указывается тип класса компонента Activity, который должен быть запущен, затем начинает работать ОС, когда находит StartActivity().
Достаточно просто. Но что происходит, когда разработчик исходного приложения не знает какой компонент Activity должен быть запущен, так как этот компонент Activity находится в отдельном приложении? Приложение «высылает» уведомление, что имеется что-то, что необходимо доставить в другое приложение и «спрашивает», может ли выполнить действие над этим.
Вот тут на помощь приходит неявный Intent. Разработчик создает неявный Intent, не специфицируя компонент Activity, который должен быть запущен.
Однако неявный Intent всё ещё нуждается в достаточной информации, чтобы ОС Android могла определить, какое приложение должно получить информацию. Здесь не будет рассматриваться создание неявных Intent’ов, а только их применение. В этой статье рассказано об Intent’ах подробно.
Intent-фильтры
Вся информация о явных и неявных Intent’ах приводит к концепции Intent-фильтров. Intent-фильтры – это способ, с помощью которого приложение, принимающее файл, регистрируется в Android для обработки входящих данных.
Intent-фильтры ассоциируются с компонентами Activity в приложении, которые должны запуститься, когда получен файл. Описание Intent-фильтра появляется в файле AndroidManifest.xml вместе с описанием для компонента Activity.
Чтобы определить, какое приложение будет запущено и отображено в опциях, когда пользователь посылает файл определенного типа в другое приложение, Android «просматривает» все Intent-фильтры, зарегистрированные ОС и фильтрует приложения по принципу сравнения критериев Intent-фильтров с критериями, объявленными в неявном Intent’е.
Как создать Intent-фильтр?
Объявление Intent -фильтра
Как было отмечено, описание Intent-фильтра появляется вместе с описанием компонента Activity, которые должны быть запущены, когда получен файл.
Пример объявления компонента Activity и Intent-фильтра в файле AndroidManifest.xml выглядит следующим образом:
<activity android:configChanges="orientation|screenSize" android:icon="@drawable/icon" android:label="OpenFiles.Droid" android:theme="@style/MyTheme" android:name="md5d37a78bf190038b83be9873b4223f8d1.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/pdf" />
</intent-filter>
</activity>
Здесь нужно отметить пару моментов. Первый – один компонент Activity может иметь более одного Inent-фильтра. Второй – ключевые слова, включенные в узел <intent-filter>
. Эти ключевые слова помогают Android отфильтровать приложения, которые могут выполнять определенные операции. Вот объяснение значений в данном случае получения файла:
<action android:name="android.intent.action.SEND" />
– компонент Activity только собирается отреагировать на неявный Intent, то есть послать данные в другие приложения.<category android:name="android.intent.category.DEFAULT" />
– это всегда необходимо. Также, всегда должно быть такое значение для получения неявного Intent’а.<data android:mimeType="application/pdf" />
– это тип данных. Или тип MIME, который может обрабатывать приложение.
На этом работа с Android-частью закончена. Настало время a Xamarin.Forms-приложения.
Интеграция с Xamarin.Forms
Сначала необходимо понять, как добавить Intent-фильтр в Android-часть Xamarin.Forms-приложения. Xamarin обеспечивает множество инструментов, так что нет необходимости часто касаться файла AndroidManifest.xml. Если понадобиться добавить значение в этом файле, можно обратиться к визуальному дизайнеру (дважды кликнув файл) или к различным атрибутам классов.
Так случилось, что Intent-фильтры можно добавить с помощью класса IntentFilterAttribute
. Так как в Forms-приложении только один компонент Activity, необходимо класс MainActivity
дополнить этим атрибутом. Чтобы сгенерировать XML-код в файле AndroidManifest.xml, идентичный рассмотренному выше, объявление класса MainActivity
должно выглядеть следующим образом:
[Activity(Label = "OpenFiles.Droid", Icon = "@drawable/icon", Theme = "@style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = @"application/pdf")]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity {
Есть еще класс ActivityAttribute
, который создаст узел Activity в манифесте. Затем класс IntentFilterAttribute
добавляет Intent-фильтр в этот узел Activity. Полную документацию по классу IntentFilterAttribute
можно найти здесь. Он определяет action, category и тип входящих данных. Action и category – это массивы, и они могут быть определены. В них могут быть еще параметры, помогающие фильтровать по совпадающим критериям. С помощью такого дополнения класса Android признает приложение как способное обрабатывать PDF-файлы.
Обработка входящего файла
Теперь, когда приложение опознано Android как подходящее, необходимо рассмотреть ситуацию, когда что-то попадает в него.
Когда приходит файл, вызывается функция OnCreate
в MainActivity
. Свойство Activity Intent
содержит информацию, которая необходима для получения базового файла. Во время получения файла можно ожидать необходимую информацию в объекте ClipData
свойства Intent
.
Эта информация – Android.Net.Uri
, которая используется с помощью ContentResolver
, чтобы запустить поток данных. Затем можно сохранить этот поток данных в приложении как локальный файл.
В зависимости от приложения, отправляющего данные, также можно посмотреть URI отправляемого файла в Extras Intent’а – Intent.ExtraStream
.
Вне зависимости от того, откуда получен Uri
, необходимо удостовериться, что файл сохранен в приложении. В этом случае появится уверенность, что к файлу будет полный доступ.
Весь OnCreate
показан ниже.
App _mainForms;
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
var mainForms = new App();
LoadApplication(mainForms);
if (Intent.Action == Intent.ActionSend)
{
// Пример данных хранящийхся в Extras
var uriFromExtras = Intent.GetParcelableExtra(Intent.ExtraStream) as Android.Net.Uri;
var subject = Intent.GetStringExtra(Intent.ExtraSubject);
// Получаем данные из ClipData
var pdf = Intent.ClipData.GetItemAt(0);
// Открываем поток из URI
var pdfStream = ContentResolver.OpenInputStream(pdf.Uri);
// Сохраняем
var memOfPdf = new System.IO.MemoryStream();
pdfStream.CopyTo(memOfPdf);
var docsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
var filePath = System.IO.Path.Combine(docsPath, "temp.pdf");
System.IO.File.WriteAllBytes(filePath, memOfPdf.ToArray());
mainForms.DisplayThePDF(filePath);
}
}
Это обычный Xamarin.Forms OnCreate
. Значительное отличие состоит в том, что после вызова Xamarin.Forms LoadApplication
, он проверяет, был ли загружен Intent
информацией из другого приложения, вызывая неявный Intent.
Если он находит данные в свойстве Intent
, которое получает действие Intent.ActionSend
(то есть файл отсылается в приложение), то файл копируется в локальное хранилище приложения, затем вызывается функция DisplayThePDF()
.
DisplayThePDF()
отображает модальную страницу в web-обозревателе, и это web-обозреватель отображает загруженный файл PDF. Этот обозреватель работает через кастомный рендерер. Код для этого рендерера написан с помощью документации Xamarin.
Когда все будет сделано приложение Android будет отображать PDF-файлы как на изображении справа.
Итог
В целом, поняв то, как Android обменивается данными между приложениями, что похоже на обмен данными между компонентами Activity в одном приложении, задача получения файлов в Android становится легко разрешимой. Необходимо объявить Intent-фильтр в классе MainActivity
в Android-проекте в Xamarin.Forms, используя IntentFilterAttribute
. После этого соответствующая разметка разместиться в файле AndroidManifest.xml, и теперь всё, что нужно сделать – обработать входящие данные с помощью функции OnCreate()
, которая в идеале просто передаст работу функции, уже имеющейся в проекте Xamarin.Forms.
Благодарим за перевод
Александр Алексеев — Xamarin-разработчик, фрилансер. Работает с .NET-платформой с 2012 года. Участвовал в разработке системы автоматизации закупок в компании Digamma. C 2015 года ушел во фриланс и перешел на мобильную разработку с использованием Xamarin. В текущее время работает в компании StecPoint над iOS приложением.
Ведет ресурс XamDev.ru и сообщества «Xamarin Developers» в социальных сетях: VK, Facebook, Telegram.
Автор: Microsoft