ссылка:
Приложения могут сохранять файлы двух разных типов на внешнем хранилище:
// преданный外部хранилище Оглавление
// /storage/emulated/0/Android/data/com.companyname.app/files/
Android.Content.Context.GetExternalFilesDir(string type)
// Основное внешнее хранилище
// /storage/emulated/0/
Android.OS.Environment.ExternalStorageDirectory
Android Считайте внешнее хранилище опасными разрешениями, которые обычно требуют от пользователя предоставления разрешения на доступ к ресурсу. Пользователи могут отозвать это разрешение в любое время. Это означает, что запрос разрешения во время выполнения должен выполняться перед любым доступом к файлу. Приложениям автоматически предоставляется разрешение на чтение и запись своих личных файлов. существоватьпользовательРазрешение полученоИзназад,Приложения могут читать и записывать файлы, принадлежащие другим приложениям.
//global::Android.OS.Environment.ExternalStorageDirectory.AbsolutePath :Установить Android из root Оглавление
//Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)::Получить данные AndroidОглавление
var path = global::Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
// создаватьдокумент
System.IO.Directory.CreateDirectory(path);
Все приложения Android должны объявить одно из двух разрешений для внешнего хранилища в AndroidManifest.xml.
Чтобы определить разрешения, необходимо указать следующие два: uses-permission
Один из элементов, добавленных в AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Примечание. Ниже есть ошибка.
Как указано выше, в файле свойств проекта Android имеется файл AndroidManifest.xml. существовать
<application android:label="cardionNet2.Android"></application>
Симога
Этот визуальный осмотр неправильный, просто добавьте его прямо, не надо сюда ставить;
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.demoapp" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
<application android:label="DemoApp.Android" android:theme="@style/MainTheme"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>
Первый шаг перед записью на внешнее хранилище — проверить, доступно ли оно для чтения или записи.
Android.OS.Environment.ExternalStorageState
Свойство содержит строку, определяющую состояние внешнего хранилища. Это свойство возвращает строку, представляющую статус.
bool isReadonly = Environment.MediaMountedReadOnly.Equals(Environment.ExternalStorageState);
bool isWriteable = Environment.MediaMounted.Equals(Environment.ExternalStorageState);
полностью квалифицированный
bool isReadonly = Android.OS.Environment.MediaMountedReadOnly.Equals(Android.OS.Environment.ExternalStorageState);
bool isWriteable = Android.OS.Environment.MediaMounted.Equals(Android.OS.Environment.ExternalStorageState);
ссылка:
Application
Базовый класс предоставляет следующую функциональность:
OnStart
、OnSleep
и OnResume
。PageAppearing
、PageDisappearing
。ModalPushing
、ModalPushed
、ModalPopping
и ModalPopped
。методы жизненного цикла
Application
Класс содержит три виртуальных метода, которые можно переопределить в ответ на изменения жизненного цикла:
OnStart
- существоватьзапускатьприложениечасвызовэто。OnSleep
- Каждыйкогдаприложениеперевод вназадбашнячасвызовэто。OnResume
- приложениеотправить вназадбашняназадвосстанавливатьсячасвызов。ссылка:
ссылка:
Shell -> FlyoutItem / TabBar -> Tab -> ShellContent -> ContentPage FloutItem: всплывающее окно TabBar: нижняя вкладкастолбец Tab: Содержимое группы когда
Tab
Есть несколькоShellContent
,час,Будут снова распределены внутри компании, нравитьсяTab
родительTabBar
,Затем отобразится страница существования. Верхние вкладки навигации, соответствующие несколькимShellContent
, нравитьсяTab
родительFlyoutItem
,волясуществоватьпереписываться Отображение нескольких подпанелей под панелью (ShellContent
) нравитьсясуществоватьFloutItem / TabBar
Пишите прямо вShellContent
,воля Воля Каждый个ShellContent
Неявно завернутый вTab
серединаПополнить: и
TabBar
класс этоShellItem
псевдоним для класса, в то время какTab
класс этоShellSection
Псевдоним для класса. Поэтому также возможно Shell -> FlyoutItem / ShellItem -> ShellSection -> ShellContent -> ContentPage Поэтому дляFlyoutItem
Объекты должны быть переопределены при создании пользовательских средств визуализации.CreateShellItemRenderer
метод, дляTab
Объекты должны быть переопределены при создании пользовательских средств визуализации.CreateShellSectionRenderer
метод.
<!-- When the Flyout is visible this will be a menu item you can tie a click behavior to -->
<MenuItem Text="Logout" StyleClass="MenuItemLayoutStyle" Clicked="OnMenuItemClicked">
</MenuItem>
Кнопка выхода из системы появляется сбоку
когда сторона (Flyout) всплывающее окно час,MenItem
будет отображаться
MenuItem
: всплывающее окноиз пункта меню
ссылка:
Доступ к нему можно получить с помощью значка или проведя пальцем по краю экрана. всплывающее окно по необязательному заголовку, врагее Элемент окна, дополнительный пункт меню и дополнительный нижний колонтитул состоит из:
<!--
When the Flyout is visible this defines the content to display in the flyout.
FlyoutDisplayOptions="AsMultipleItems" will create a separate flyout item for each child element
https://docs.microsoft.com/dotnet/api/xamarin.forms.shellgroupitem.flyoutdisplayoptions?view=xamarin-forms
-->
<FlyoutItem Title="первая страница" Icon="icon_about.png">
<ShellContent Route="HomePage" ContentTemplate="{DataTemplate local:HomePage}" />
</FlyoutItem>
<FlyoutItem Название="Список" Icon="icon_feed.png">
<ShellContent Route="ItemsPage" ContentTemplate="{DataTemplate local:ItemsPage}" />
</FlyoutItem>
<FlyoutItem Title="настраивать" Icon="icon_setting.png">
<ShellContent Route="SettingPage" ContentTemplate="{DataTemplate local:SettingPage}" />
</FlyoutItem>
неявное преобразование
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xaminals.Controls"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<FlyoutItem Title="Cats"
Icon="cat.png">
<Tab>
<ShellContent ContentTemplate="{DataTemplate views:CatsPage}" />
</Tab>
</FlyoutItem>
<FlyoutItem Title="Dogs"
Icon="dog.png">
<Tab>
<ShellContent ContentTemplate="{DataTemplate views:DogsPage}" />
</Tab>
</FlyoutItem>
</Shell>
Эквивалентно следующему:
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xaminals.Controls"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<ShellContent Title="Cats"
Icon="cat.png"
ContentTemplate="{DataTemplate views:CatsPage}" />
<ShellContent Title="Dogs"
Icon="dog.png"
ContentTemplate="{DataTemplate views:DogsPage}" />
</Shell>
этотнеявное преобразованиеавтоматический Воля Каждый个 ShellContent
Объект завернут в Tab
объект середина, в то время как Tab
затем упаковано в FlyoutItem
объектсередина。
Прямо сейчас
Shell
серединапо умолчаниюFlyoutItem
,FlyoutItem
/TabBar
серединапо умолчаниюTab
Примечание подкласс
Shell
объектсерединаизвсеFlyoutItem
объекты автоматически добавляются вShell.FlyoutItems
собирать, Определение коллекции Волясуществоватьвсплывающее окносередина отображает список элементов из.
добавив Shell.ItemTemplate
Дополнительные атрибуты DataTemplate
Настраивается для каждого FlyoutItem
Появление:
<Shell ...>
...
<Shell.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="0.2*,0.8*">
<Image Source="{Binding FlyoutIcon}"
Margin="5"
HeightRequest="45" />
<Label Grid.Column="1"
Text="{Binding Title}"
FontAttributes="Italic"
VerticalTextAlignment="Center" />
</Grid>
</DataTemplate>
</Shell.ItemTemplate>
</Shell>
FontAttributes="Italic"
В этом примере каждое слово отображается курсивом.FlyoutItem
Название объекта:
Shell.ItemTemplate
является прикрепленным атрибутом, поэтому к определенному элементу можно прикрепить разные шаблоны. FlyoutItem
объект.
Всплывающее сообщение означает «агрессивнее» содержимое окна, вы можете выбрать Воля заменить его на свое собственное содержимое от Воля Shell.FlyoutContent
Можетобязательностьсвойствонастраиватьдля object
:
<Shell ...
x:Name="shell">
...
<Shell.FlyoutContent>
<CollectionView BindingContext="{x:Reference shell}"
IsGrouped="True"
ItemsSource="{Binding FlyoutItems}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Title}"
TextColor="White"
FontSize="Large" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Shell.FlyoutContent>
</Shell>
существоватьэтот Примерсередина,Волявсплывающее замена содержимого окнадля
CollectionView
,этопоказывать ПонятноFlyoutItems
Коллекция середина каждого предмета из названия.
Кроме того, вы можете добавить Shell.FlyoutContentTemplate
Можетобязательностьсвойствонастраиватьдля DataTemplate
Приходитьопределениевсплывающее окносодержание:
<Shell ...
x:Name="shell">
...
<Shell.FlyoutContentTemplate>
<DataTemplate>
<CollectionView BindingContext="{x:Reference shell}"
IsGrouped="True"
ItemsSource="{Binding FlyoutItems}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Title}"
TextColor="White"
FontSize="Large" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</DataTemplate>
</Shell.FlyoutContentTemplate>
</Shell>
Сценарий: иногда нет необходимости отображать первый по умолчанию.
первый запускиспользоватьвсплывающее окноиз Shell приложениечас,Shell.CurrentItem
свойство Волянастраиватьдляподкласс Shell
объектсерединаиз Нет.один FlyoutItem
объект. Однако этот объект недвижимости может быть отремонтирован для другого. FlyoutItem
,Как показано в следующем примере:
<Shell ...
CurrentItem="{x:Reference aboutItem}">
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
...
</FlyoutItem>
<ShellContent x:Name="aboutItem"
Title="About"
Icon="info.png"
ContentTemplate="{DataTemplate views:AboutPage}" />
</Shell>
Этот пример будет
CurrentItem
свойствонастраиватьдляимядляaboutItem
изShellContent
Объект, к которому ведет Воля, выбирается середина и показывает объект. существоватьэтот Примерсередина,неявное преобразованиеиспользуется для ВоляShellContent
Объект завернут вTab
объектсередина,назадили упаковкасуществоватьFlyoutItem
объектсередина。
Предположим, существует файл с именем aboutItem
из ShellContent
объект,тогда Эквивалентиз C# Код:
CurrentItem = aboutItem;
существоватьэтот Примерсередина,CurrentItem
свойство是существоватьподкласс Shell
добрыйсерединанастраиватьиз。 Альтернативно, вы можете пройти Shell.Current
静态свойствосуществовать任何добрыйсерединанастраивать CurrentItem
свойство:
Shell.Current.CurrentItem = aboutItem;
неожиданно возникнутьсуществоватьвсплывающее окносерединапо умолчанию Может Видеть。 Однако можно использовать FlyoutItemIsVisible
свойство Воляэлемент скрытсуществоватьвсплывающее окносередина,ииспользовать IsVisible
свойство Воля Это отвсплывающее окносередина Удалить:
bool
из FlyoutItemIsVisible
Указывает, существует ли предмет всплывающее. окносередина但仍Может以проходить GoToAsync
Метод навигации для доступа. Этот атрибут имеет значение по умолчанию для true
。bool
из IsVisible
Указывает, следует ли удалить элемент из визуального дерева середина, чтобы он не существовалвсплывающее. окносередина шоу. Это значение по умолчанию для true
。Примечание Есть еще один
Shell.FlyoutItemIsVisible
Дополнительные объекты доступны по адресуFlyoutItem
、MenuItem
、Tab
иShellContent
на объектенастраивать该свойство。
<Shell ...
FlyoutIsPresented="{Binding IsFlyoutOpen}">
</Shell>
Shell.Current.FlyoutIsPresented = false;
ссылка:
<TabBar>
<ShellContent Title="About" Icon="icon_about.png" Route="AboutPage" ContentTemplate="{DataTemplate local:AboutPage}" />
<ShellContent Title="Browse" Icon="icon_feed.png" ContentTemplate="{DataTemplate local:ItemsPage}" />
</TabBar>
Визуальный осмотр, без дополнений Title
, Icon
будет скрыт, то в настоящее время вы можете перемещаться сюда только через код.
<!--
If you would like to navigate to this content you can do so by calling
await Shell.Current.GoToAsync("//LoginPage");
-->
<TabBar>
<ShellContent Route="LoginPage" ContentTemplate="{DataTemplate local:LoginPage}" />
</TabBar>
TabBar
серединатолько одинShellContent
,не будет отображатьсянижняя вкладка Панель навигации
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab>
<ShellContent ContentTemplate="{DataTemplate views:CatsPage}" />
</Tab>
</TabBar>
</Shell>
еслинравиться单个 TabBar
Есть несколько объектов середина Tab
объект, тогда Tab
Рендеринг объектовдлянижняя вкладка:
Тип string
из Title
Свойство, определяющее заголовок вкладки. Тип ImageSource
из Icon
Свойства для определения значка вкладки:
если TabBar
Есть более пяти извкладок,тогда покажи "еще"вкладка,Доступны для доступа другие вкладки:
еслиодин Tab
объект Есть несколько ShellContent
объект, то Волясуществоватьнижняя вкладкасередина добавить одну верхнюю панель вкладки, применить По панели вкладки можно перемещаться ContentPage
Объект:
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab Title="Domestic"
Icon="paw.png">
<ShellContent Title="Cats"
ContentTemplate="{DataTemplate views:CatsPage}" />
<ShellContent Title="Dogs"
ContentTemplate="{DataTemplate views:DogsPage}" />
</Tab>
<Tab Title="Monkeys"
Icon="monkey.png">
<ShellContent ContentTemplate="{DataTemplate views:MonkeysPage}" />
</Tab>
</TabBar>
</Shell>
Первый запуск Shell приложениечас,Shell.CurrentItem
свойство Волянастраиватьдляподкласс Shell
объектсерединаиз Нет.один Tab
объект. Однако этот объект недвижимости может быть отремонтирован для другого. Tab
,Как показано в следующем примере:
<Shell ...
CurrentItem="{x:Reference dogsItem}">
<TabBar>
<ShellContent Title="Cats"
Icon="cat.png"
ContentTemplate="{DataTemplate views:CatsPage}" />
<ShellContent x:Name="dogsItem"
Title="Dogs"
Icon="dog.png"
ContentTemplate="{DataTemplate views:DogsPage}" />
</TabBar>
</Shell>
ссылка:
Нет возможности напрямуюсуществоватьShellсередина,такой жечасявныйопределение
FlyoutItem
иTabBar
могу только пройтиFlyoutItem
неявный эффектУведомление: не в
FlyoutItem
Использовать наFlyoutDisplayOptions="AsMultipleItems"
, Это приведет кпервая страница、игра、канал、динамичный
также показатьсуществоватьсторонапоявлятьсястолбец
<!-- показыватьсуществовать Нижняя панель навигации -->
<FlyoutItem Title="первая страница" Icon="icon_about.png">
<Tab Title="первая страница" Icon="icon_about.png">
<ShellContent x:Name="HotPageItem"
Название="Горячий"
ContentTemplate="{DataTemplate local:HotPage}" />
<ShellContent x:Name="RecomPageItem"
Название="Рекомендуется"
ContentTemplate="{DataTemplate local:RecomPage}" />
<ShellContent x:Name="LastPageItem"
Название="Последний"
ContentTemplate="{DataTemplate local:LastPage}" />
</Tab>
<Tab Название="Игра" Icon="icon_feed.png">
<ShellContent ContentTemplate="{DataTemplate local:ItemsPage}" />
</Tab>
<Tab Название="Канал" Icon="icon_feed.png">
<ShellContent ContentTemplate="{DataTemplate local:ItemsPage}" />
</Tab>
<Tab Название="динамический" Icon="icon_feed.png">
<ShellContent ContentTemplate="{DataTemplate local:ItemsPage}" />
</Tab>
</FlyoutItem>
<!-- показыватьсуществоватьсторонапоявлятьсястолбец -->
<FlyoutItem Название="О программе" Icon="icon_about.png">
<ShellContent ContentTemplate="{DataTemplate local:HomePage}" />
</FlyoutItem>
<FlyoutItem Title="настраивать" Icon="icon_setting.png">
<ShellContent ContentTemplate="{DataTemplate local:SettingPage}" />
</FlyoutItem>
Пополнить
позволятьпервая страницапо умолчанию选середина второй рекомендовать
,существовать первая страница
использование предмета CurrentItem
<Tab Title="первая страница" Icon="icon_about.png" CurrentItem="{x:Reference RecomPageItem}">
<ShellContent x:Name="HotPageItem"
Название="Горячий"
ContentTemplate="{DataTemplate local:HotPage}" />
<ShellContent x:Name="RecomPageItem"
Название="Рекомендуется"
ContentTemplate="{DataTemplate local:RecomPage}" />
<ShellContent x:Name="LastPageItem"
Название="Последний"
ContentTemplate="{DataTemplate local:LastPage}" />
</Tab>
ссылка:
Официально не реализовано Нижняя панель навигации параметров (включая дочернюю верхнюю панель навигации) Скользящая анимация для переключения страниц Видеть [Feature] Swipe left/right to navigate between upper/bottom tabs of Shell · Issue #12435 · xamarin/Xamarin.Forms
ссылка:
просмотр прокрутки ScrollView существоватьXamarin.Formsсередина,просмотр прокруткиScrollView используется для отображения длинной прокрутки контента. Хотя свойствоScrollViewizContent может иметь только одно значение, Прямо СейчасScrollView может содержать только один дочерний элемент, но на самом деле это элемент управления «один макет», специальный элемент «один измакет». существоватьиспользоватьизчасждать,ScrollView требует, чтобы родительский контейнер выделил ему фиксированный размер.,такой жечас子元素и且有固定изразмер。так,ScrollView может рассчитать сумму прокрутки исходя из их соответствующих размеров. ScrollView не только обеспечивает прежнюю сумму прокрутки ScrollXиScrollY,Также предоставляется общий объем контента ContentSize. так,Разработчики могут рассчитать ход прокрутки,показывать给пользователь。такой жечас,Используйте ScrollView, чтобы предоставить конечное событие Scrolled,Пользователям могут быть предложены,Или загрузите новый контент.
ссылка:
V**
ссылка:
ссылка:
ссылка:
Xamarin.Android получает номер предыдущей версии, когда
Android
public string GetVersion()
{
// https://stackoverflow.com/questions/47353986/xamarin-forms-forms-context-is-obsolete
var context = Android.App.Application.Context;
return context.PackageManager.GetPackageInfo(context.PackageName, 0).VersionName;
}
Ошибка ниже,
Activity
наследоватьContext
,так выйти изactivity=null
public string GetVersion()
{
var activity=Xamarin.Forms.Forms.Context as Activity;
return activity.PackageManager.GetPackageInfo(activity.PackageName, 0).VersionName;
}
iOS
public string GetVersion()
{
return NSBundle.MainBundle.InfoDictionary["CFBundleShortVersionString"].ToString();
}
Установить локальный APK
public void InstallAPK(string filePath)
{
try
{
Java.IO.File apkFile = new Java.IO.File(filePath);
Intent intent = new Intent(Intent.ActionView);
intent.SetFlags(ActivityFlags.NewTask);
// Уведомление: 直接从документсередина Установить APK и Установить из менеджера загрузок середина нет то же самое
// Получить Скачать документизUri
if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.N)
{
// Android 7.0+
Android.Net.Uri apkFileUri = Android.Support.V4.Content.FileProvider.GetUriForFile(_mContext, "github.yiyungent.onetree.fileprovider", apkFile);
intent.AddFlags(ActivityFlags.GrantReadUriPermission);
intent.SetDataAndType(apkFileUri, "application/vnd.android.package-archive");
}
else
{
Android.Net.Uri apkFileUri = Android.Net.Uri.FromFile(apkFile);
intent.SetDataAndType(apkFileUri, "application/vnd.android.package-archive");
}
_mContext.StartActivity(intent);
}
catch (Exception ex)
{
}
}
> AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="0.4.2" package="github.yiyungent.onetree" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
<application android:label="OneTree" android:theme="@style/MainTheme">
<provider android:name="android.support.v4.content.FileProvider" android:authorities="github.yiyungent.onetree.fileprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
</application>
<!-- доступ Разрешения на состояние сети -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 读Писать Разрешения на внешнее хранилище -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- доступ Сетевые разрешения -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- существоватьSDCardсерединасоздаватьиудалитьдокумент Разрешения -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- Разрешить установку пакетов из неизвестных источников -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
</manifest>
Resources/xml/file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--Context.getFilesDir() 位于/data/data/Установить Оглавление-->
<files-path name="internalPath" path="file" />
<!--Context.getCacheDir()-->
<cache-path name="cachePath" path="file" />
<!--Environment.getExternalStorageDirectory()-->
<external-path name="externalPath" path="file" />
<!--Context.getExternalFilesDir(null)-->
<external-files-path name="externalFPath" path="file" />
<root-path
name="root-path"
path="." />
</paths>
ссылка:
Изменения разрешений для Android8.0 и выше,Загрузите и установите установочный пакет назад из нравитьсяapk.,Во-первых, вам необходимо подтвердить, есть ли у вас разрешение на установку приложения из неизвестных источников.
Во-первых, вам необходимо добавить следующие разрешения в файл манифеста:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
На Android 8 или выше код Intent вызывает открытие apk, но интерфейс установки не вызывается.
решать:
//Загружаем на локальный диск и выполняем установку
private void InstallAPK()
{
// Получить Скачать документизUri
Android.Net.Uri downloadFileUri = _downloadManager.GetUriForDownloadedFile(_downloadId);
if (downloadFileUri != null)
{
Intent intent = new Intent(Intent.ActionView);
intent.SetDataAndType(downloadFileUri, "application/vnd.android.package-archive");
//intent.AddFlags(ActivityFlags.NewTask);
intent.SetFlags(ActivityFlags.NewTask);
intent.AddFlags(ActivityFlags.GrantReadUriPermission);
_mContext.StartActivity(intent);
}
}
Примечание. Оба предложения ниже должны быть включены.
intent.SetFlags(ActivityFlags.NewTask);
intent.AddFlags(ActivityFlags.GrantReadUriPermission);
Разрешения Уведомление:
<!-- Разрешить установку пакетов из неизвестных источников -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
такой жечас,Помнитьсуществоватькодсерединапроситьэтот Разрешения
ссылка:
ссылка:
ссылка:
IsEnabled="False"
,Волясделаю Button
Вернуться к стилю по умолчаниюссылка:
ссылка:
ссылка:
ссылка:
Уведомление:
splash_screen.xml
По умолчанию файл имеет значение TransformFile
,Это приведет к тому, что файл перестройки не найден.
решать:
Изменить на: AndroidResource
Прямо сейчас,OneTree.Android.csproj
,Его середина такова:
<ItemGroup>
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
</ItemGroup>
ссылка:
ссылка:
ссылка:
AndroidManifest.xml
<application
android:label="@string/ApplicationName"
android:theme="@style/MainTheme"
android:icon="@mipmap/icon"
android:networkSecurityConfig="@xml/network_security_config">
</application>
xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Android 9.0+ 必须использоватьHTTPS -->
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
ссылка:
ссылка:
ссылка:
ссылка:
ссылка:
App.xaml
<Style x:Key="Separator" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="HorizontalOptions" Value="FillAndExpand" />
<Setter Property="Color" Value="Gray" />
<Setter Property="Margin" Value="0, 5, 0, 5" />
<Setter Property="Opacity" Value="0.5" />
</Style>
<BoxView Style="{StaticResource Separator}" />
ссылка:
Visual Stuido 2019
Найти документ с подписью yiyun.keystore
1. Щелкните правой кнопкой мыши, чтобы войти в «Просмотр архивов».
если не было создано ранее apk (Архив), запустите его один раз Archive
archive.xml
<?xml version="1.0" encoding="utf-8"?>
<Archive>
<Name>OneTree</Name>
<PackageName>github.yiyungent.onetree</PackageName>
<PackageVersionCode>1</PackageVersionCode>
<PackageVersionName>0.4.0</PackageVersionName>
<PackageFormat>apk</PackageFormat>
<CreationDate>637619313170861804</CreationDate>
<SolutionName>OneTree.App</SolutionName>
<SolutionPath>F:\Com\me\Repos\OneTree.App\OneTree.App.sln</SolutionPath>
<Status></Status>
<Configuration>
<DebugMode>false</DebugMode>
</Configuration>
<Comment />
<LastUsedKeystore>C:\Users\yiyun\AppData\Local\Xamarin\Mono for Android\Keystore\yiyun\yiyun.keystore</LastUsedKeystore>
<TimeStampingAuthority />
<LastInsightsUploadDate>0</LastInsightsUploadDate>
</Archive>
Чтосередина
LastUsedKeystore
Прямо сейчасдлязнакдокументпуть
используйте этот знак-документ, чтобы охладить подарок из Не знакapk (
CoolApkDevVerify_no_sign.apk
) подпись, генерировать знакизsigned.apk
jarsigner -verbose -keystore yiyun.keystore -signedjar signed.apk CoolApkDevVerify_no_sign.apk yiyun
jarsigner -verbose -keystore [Your signature storage path] -signedjar [signed filename] [unsigned filename] [Your alias key]
Пополнить:
Проверять alias key
,Что实就是тыкогдачассоздаватьсекретный ключчасизпользовательимя
keytool -keystore yiyun.keystore -list -v
keytool -keystore [your key store] -list -v
yiyun.keystore: представляет знак документа вашего проекта. подписанный.apk: представляет ваш пакет apkiznak. CoolApkDevVerify_no_sign.apk: предоставляет вам пакет от имени CoolApkDevVerify_no_sign.apk. Введите приведенную выше команду назад, и ваш рабочий стол будет загружен в Kuan isapk и станет знаком (а установочный пакет isниктознак, предоставленный iKuan, имеет примерно такой же размер)
Фактически, это будет Крутая безопасность для вас
CoolApkDevVerify_no_sign.apk
, используйте секретный ключ, который вы дали себе изapkзнакиз, а затем дайте этот проверочный apk Подпишите это
Собственно, именно того, что ниже, у меня нет настроек, поэтому его у меня нет.
ссылка:
ссылка:
// Javascript код
console.log('{"width": "750"}');
// C#
public class TorchWebChromeClient : Android.Webkit.WebChromeClient
{
public override void OnConsoleMessage(string message, int lineNumber, string sourceID)
{
// message Прямо сейчасдля JS Сообщение передается, и сообщение оценивается для определения метода вызова.
base.OnConsoleMessage(message, lineNumber, sourceID);
}
}
самый распространенный метод,Удобно и лаконично,Но единственныйиз Недостатком являетсясуществовать 4.2 В системе ниже есть уязвимости
Добавьте сопоставление объектов с помощью метода addJavascriptInterface.
Этот метод на самом деле предназначен для js экологический контекст ( Window ) вводить для js вызов Фактически, следующее window середина Введенный
jsBridge.invokeAction
иinvokeCSharpAction
,назад — это первое из инкапсуляции,实际начальствоты也Может以直接использоватьjsBridge.invokeAction
,Но вы должны обеспечить существованиеOnPageFinished
назад
public class JSBridge : Java.Lang.Object
{
readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
public JSBridge(HybridWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
// Открытое имя метода: invokeAction
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
HybridWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
// вызов WebView
((HybridWebView)hybridRenderer.Element).InvokeAction(data);
}
}
}
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace TorchView4Droid.Components
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe from event handlers
Control.RemoveJavascriptInterface("jsBridge");
((HybridWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
// 1.WebViewClient
var webViewClient = new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}");
Control.SetWebViewClient(webViewClient);
// незащищенныйсуществовать jsBridge на объекте
// Итак, наконец: jsBridge.invokeAction
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
}
}
}
}
public class JavascriptWebViewClient : FormsWebViewClient
{
string _javascript;
public JavascriptWebViewClient(HybridWebViewRenderer renderer, string javascript) : base(renderer)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
// Инкапсулированная функция существует здесь Гарантировано выполнение один раз для инъекции
view.EvaluateJavascript(_javascript, null);
}
}
js вызовC#
function invokeCSCode(data) {
try {
log("Sending Data:" + data);
invokeCSharpAction(data);
} catch (err) {
log(err);
}
}
недостаток: Протокол ограничений должен быть задокументирован в одной спецификации документа, и js Не могу стоять Прямо сейчас获取 C# из возвращаемого значения, обязательно C# снова возьмите инициативу в свои руки js передать возвращаемое значение
public class JavascriptWebViewClient : FormsWebViewClient
{
public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
{
// перехватыватьurl, исследовать Url.Scheme 是否длятыдляjsвызовопределениеиз Scheme
if (request.Url != null && request.Url.Scheme != null && request.Url.Scheme.ToLower() == "js")
{
// вызов C#
// Можно получить из Query серединаанализироватьпереданные данные
var queryPars = request.Url.QueryParameterNames;
// Пример: Открыть локальную страницу
// url = "js://openActivity?arg1=111&arg2=222"
/ ...
}
return base.ShouldOverrideUrlLoading(view, request);
}
}
// JavaScript
function openActivity(){
document.location = "js://openActivity?arg1=111&arg2=222";
}
недостаток: Не могу получить это C# возвращаемое значение,
нравиться js Хотите получить возвращаемое значение метода, могу только пройти WebView из loadUrl метод для выполнения js 方法把возвращаемое значение传递回去,Связанныйизкод如Вниз:
webView.LoadUrl("javascript:returnResult(" + result + ")");
// JavaScript
function returnResult(result){
alert("result is" + result);
}
prompt Метод диалогового окна может возвращать строковый тип возвращаемого значения,недостаток: Трудно сформулировать соглашение.,Требуется подробная документация,Но проблемы уязвимости не будет.
перехватывать js серединаиз Существует несколько методов подсказки, то есть несколько стилей диалоговых окон. js середина имеет три общих метода диалога:
// JavaScript
function clickPrompt(){
// преимущество: Могу получить это C# возвращаемое значение метода
var result = prompt("js://openActivity?arg1=111&arg2=222");
alert("open activity " + result);
}
public class TorchWebChromeClient : Android.Webkit.WebChromeClient
{
#region js Три диалоговых окна
public override bool OnJsAlert(WebView view, string url, string message, JsResult result)
{
return base.OnJsAlert(view, url, message, result);
}
public override bool OnJsConfirm(WebView view, string url, string message, JsResult result)
{
return base.OnJsConfirm(view, url, message, result);
}
public override bool OnJsPrompt(WebView view, string url, string message, string defaultValue, JsPromptResult result)
{
// Уведомление: js Передайте данные о существовании message
// message = "js://openActivity?arg1=111&arg2=222"
Android.Net.Uri uri = Android.Net.Uri.Parse(message);
string scheme = uri.Scheme;
if (scheme != null && scheme.ToLower() == "js")
{
IList<string> queryPars = uri.QueryParameterNames?.ToList();
// Представляет собой завершение внутренней обработки приложения.
result.Confirm("success");
return true;
}
return base.OnJsPrompt(view, url, message, defaultValue, result);
}
#endregion
}
только
OnJsPrompt
Метод может возвращать строковый тип из значения, установленного в существующем виде.result (JsPromptResult)
середина,таквыбиратьперехватыватьэто
недостаток: C# вызов js ,Не могу стоять Прямо сейчас获取 jsiz возвращаемое значение, могу только пройти js снова вызов C# передать возвращаемое значение, loadUrl изExecution приведет к однократному обновлению страницы
// C#
mWebView.LoadUrl("javascript:show(" + result + ")");
// JavaScript
function show(result){
alert("result"=result);
return "success";
}
Уведомление Имя метода соответствует кроме,js извызов должен существовать обратный вызов функции WebViewClient.OnPageFinished для вызова,В противном случае это потерпит неудачу.
Google существовать Android4.4 для Мы добавили новый метод один, этот метод лучше, чем loadUrl Этот метод более удобен и краток, и он лучше, чем loadUrl более эффективен, потому что loadUrl при выполнении страница будет обновлена один раз, но этот метод не будет, потому что этот метод существует 4.4 Версия была представлена только из,так我们использоватьизчасждать需хотеть添加版本изсуждение
string jsFuncStr = "";
if ((int)Build.VERSION.SdkInt < 18)
{
webView.LoadUrl(jsFuncStr);
}
else
{
var jsCallback = new JsFuncValueCallback();
webView.EvaluateJavascript(jsFuncStr, jsCallback);
}
#region JsFuncValueCallback
public class JsFuncValueCallback : Android.Webkit.IValueCallback
{
public void OnReceiveValue(Object? value)
{
// value для js Возврат результатов
// Конвертировать для string Метод записи происходит из: Xamarin.Forms.Platform.Android.JavascriptResult.
string data = ((Java.Lang.String)value)?.ToString();
// TODO: js Обработка возвращаемого значения
}
// ...
}
#endregion
Как правило, наиболее распространенным методом является первый метод.,Но первый метод более затруднителен для получения возвращаемого значения.,Второй метод был представлен в версии 4.4.,Таким образом, ограничения относительно велики.
Вариант 1:
file://xxxx/index.html
Категорически не рекомендуется
Вариант 2: существовать本地запускатьодин Веб-сервер, прослушивайте определенный порт, используйте URL-адрес http://localhost:12531
Вариант 3:
ссылка:
Настройте префикс URL-адреса или HTTP Url.Scheme, Url.Host, затем передайте override WebViewClient ,перехватыватьurlпросить
public override WebResourceResponse ShouldInterceptRequest( WebView view, IWebResourceRequest request ) {
}
<URL>
ссылка:
Чтение и запись двоичных файлов в текстовом режиме может привести к повреждению содержимого. Двойной метод очень прост: при чтении документа все содержимое документа будет считано без изменений. При записи содержимое буфера памяти также будет записано в документсередина без изменений. Текстовый режим нет то же самое Понятно,существовать Писатьдокументчас,Будет ли Воля символ новой строки CRLF (0x0D 0x0A) все преобразуются в один из0x0A, и когда встречается конечный символ CTRLZ (0x1A), считается, что документ закончился. Соответственно, при записи документа Волявсеиз0x0A будет заменено на 0x0D0x0A. Поэтому при открытии двойного документа в текстовом режиме легко получить неполное прочтение документа или неправильное содержание из-за ошибок. Прямо сейчас Используйте текстовый режим, чтобы открыть текстдокумент,Также будьте осторожны, используйте,напримеркопироватьдокумент,Не следует использовать текстовый режим.
<URL>
".{Java.Lang.IllegalStateException: AssetInputStream is closed
at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualIntMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0008e] in <8b3b636835d84984ba4604c1f57b1983>:0
at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeNonvirtualInt32Method (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x0001f] in <8b3b636835d84984ba4604c1f57b1983>:0
at Android.Content.Res.AssetManager+AssetInputStream.Read (System.Byte[] b, System.Int32 off, System.Int32 len) [0x00052] in <44e54a86dea24313a2bdb807df77c27a>:0
at Android.Runtime.InputStreamInvoker.Read (System.Byte[] buffer, System.Int32 offset, System.Int32 count) [0x00006] in <44e54a86dea24313a2bdb807df77c27a>:0
at System.IO.Stream.CopyTo (System.IO.Stream destination, System.Int32 bufferSize) [0x0001f] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corert/src/System.Private.CoreLib/shared/System/IO/Stream.cs:179
at System.IO.Stream.CopyTo (System.IO.Stream destination) [0x00007] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corert/src/System.Private.CoreLib/shared/System/IO/Stream.cs:168
at (wrapper remoting-invoke-with-check) System.IO.Stream.CopyTo(System.IO.Stream)
at TorchView.WebServer.TaskProc (System.Object obj) [0x000ac] in F:\Com\me\Repos\TorchView\src\TorchView\WebServer.cs:144
--- End of managed Java.Lang.IllegalStateException stack trace ---
java.lang.IllegalStateException: AssetInputStream is closed
at android.content.res.AssetManager$AssetInputStream.ensureOpen(AssetManager.java:1364)
at android.content.res.AssetManager$AssetInputStream.read(AssetManager.java:1303)
}
ссылка:
Альтернативно добавьте На фигуру ProGuard файл конфигурации для реализации ProGuard Инструменты и больше контроля. Например, вы можете захотеть сохранить явное уведомление из класса. ProGuard。 Для этого создайте новый .cfg документ,исуществовать Обозреватель решений из панели свойств серединного приложения ProGuardConfiguration
Операция сборки:
Например, если используется Tencent Bugly, то
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
для большинства Xamarin.Android Приложение, Xamarin.Android Предоставляется по умолчанию ProGuard Конфигурациядокументдостаточныйудалитьвсе(только)еще нетиспользоватьизкод。 нравитьсяхотеть Проверятьпо умолчанию ProGuard конфигурация, пожалуйста, откройте **obj_xamarin.cfg** В издокументе.
Имейте в виду, что этот файл конфигурации не заменяет файл Xamarin.Android proguard_xamarin.cfg, поскольку ProGuard будет использовать оба файла.
переписываться OneTree.Android.csproj
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<AotAssemblies>false</AotAssemblies>
<EnableLLVM>false</EnableLLVM>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
<BundleAssemblies>true</BundleAssemblies>
<AndroidLinkTool>proguard</AndroidLinkTool>
</PropertyGroup>
<ItemGroup>
<ProguardConfiguration Include="ProGuard.cfg" />
</ItemGroup>
существовать Android При разработке приложения вы будете использовать Java Протокол линии отладки (JDWP) Выполните отладку. Это технология, которая позволяет adb и другие инструменты для целей отладки JVM коммуникация. Пара по умолчанию Xamarin.Android приложениеиз отладочной версии включено JDWP。 Хотя JDWP Существующий процесс разработки середина очень непродолжителен, но он может вызвать проблемы с безопасностью выпущенного изприложения.
важный Пожалуйста, всегда отключайте состояние отладки выпущенного приложениясерединаиз, так как если оно не отключает это состояние, то это возможно (проходить JDWP) получено Java 进程изполностьюдоступ Разрешенияисуществоватьприложениеизначальство Вниз文серединавыполнять произвольныйкод。
Android Список содержит android:debuggable
Свойство, определяющее возможность отладки приложения. Воля android:debuggable
свойствонастраиватьдля false
Это считается хорошей практикой. Самый простой способ сделать это — существовать AssemblyInfo.cs середина Добавить условиякомпилироватьзаявление:
#if DEBUG
[assembly: Application(Debuggable=true)]
#else
[assembly: Application(Debuggable=false)]
#endif
этот选项启用час,сборкавстреча В комплекте с этой машиной共享Библиотекасередина。 Это позволяет сжать сборку и уменьшить .apk
документизразмер。 Сжатие сборки также обеспечивает минимальную форму запутывания; на такое запутывание не следует полагаться;
Этот вариант требует Enterprise лицензия,толькокогда“использовать Быстрое развертывание”Запрещатьчас才Может用。 “Объединение сборок в машинный «существовать код» отключен по умолчанию.
пожалуйста Уведомление,“В комплекте с этой машинойкод”选项执行Нет意味着сборкавстречакомпилироватьк этой машинекодсередина。 нет в наличии AOT-компиляцияВолясборкакомпилироватьдлялокальная машинакод。
переписываться OneTree.Android.csproj
<PropertyGroup>
<BundleAssemblies>true</BundleAssemblies>
</PropertyGroup>
Уведомление: Я пробовал назад, и размер APK варьировался от 14MB увеличить до 25МБ, не знаю почему, не уменьшилось, а увеличилось.
Уведомление: Обнаружить,такой же一套код,Та же конфигурация, что и у одного Пакета., Visual Studio 2019 Professional Объем упаковки 13.5 MB Visual Studio 2019 Enterprise Объем упаковки 19.2 MB, Собственно корпоративная версия Объем упаковка больше, в то время как только Enterprise Edition имеет
into Native Code
Using ProGuard with the D8 DEX compiler is no longer supported. Please set the code shrinker to 'r8' in the Visual Studio project property pages or edit the project file in a text editor and set the 'AndroidLinkTool' MSBuild property to 'r8'.
решать:
ProGuard
нельзя использовать с d8
используются вместе или использовать ProGuard
,Просто измените это d8
для dx
,
Или не используйте его ProGuard
,Скорее использовать r8
и d8
ссылка:
ссылка:
ссылка:
Спасибо за помощь!
Автор этой статьи: yiyun
Ссылка на эту статью: https://moeci.com/posts/category-dotnet/xamarin/
Заявление об авторских правах: Если не указано иное, во всех статьях этого блога используются BY-NC-SA Лицензионное соглашение. При перепечатке просьба указывать источник!