XAML in Xamarin.Forms 基礎篇 電子書

XAML in Xamarin.Forms 基礎篇 電子書
XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書
顯示具有 Xamarin.Android 標籤的文章。 顯示所有文章
顯示具有 Xamarin.Android 標籤的文章。 顯示所有文章

2018/06/11

Xamarin.Android 專案封存與三個 Android 選項 AOT LLVM Bundle Assembly 測試

當我們想要進行 Xamarin.Android 專案封存 Archive 作業,並且要產生出可以發布佈署的 .apk 檔案,我們可以雙擊 Android 專案的 Properties 節點,進入到 Android 專案的屬性頁面;在這個頁面中,切換到 Android 選項標籤頁次,我們可以看到這三個選項
  • 將組譯碼組合成機器碼
  • 符合 AOT 規範 (實驗性)
  • 使用 LLVM 最佳化編譯器
關於這三個項目的說明,可以參考準備可供發行的應用程式
Xamarin.Android Option
我曾在 2017.06 也寫過一篇類似的文章 Xamarin.Android 各種封存屬性測試 。當時所採用的測試專案為一個工作管理的範例專案,不過,今天,我們將會採用 Prism Template Pack 所提供的專案範本所產生的一個專案,這個專案內將沒有再去撰寫任何 C# / XAML 的程式碼。
我的測試環境為 Visual Studio Enterprise 2017 15.7.2 版本,我將會切到 Release 組態模式下,並且調整這三個選項:將組譯碼組合成機器碼 / 符合 AOT 規範 (實驗性) / 使用 LLVM 最佳化編譯器,最後,使用封存功能,產生出尚未進行程式碼簽名的 .apk 檔案,我們將會來觀察這些不同 Android 選項下所產生的 .apk 檔案,他的檔案大小為何?
Xamarin.Android Option
透過這次的實驗,我發現到現在所使用 Visual Studio 2017 15.7.2 版本,不論使用上面三種選項的哪種組合,所產生出來的 .apk 檔案都已經大幅降低了許多,並且最佳化的處理也都表現得相當優異。

實驗 1 : 將組譯碼組合成機器碼 (無) / 符合 AOT 規範 (實驗性) (無) / 使用 LLVM 最佳化編譯器 (無)

在此模式下產生的 .apk 檔案大小為 : 17.7 MB
在這個 .apk 檔案的 assemblies 目錄下,存在這這些 .NET 組件檔案
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,僅有這些檔案
Xamarin.Android apk file

實驗 2 : 將組譯碼組合成機器碼 (無) / 符合 AOT 規範 (實驗性) (有) / 使用 LLVM 最佳化編譯器 (無)

在此模式下產生的 .apk 檔案大小為 : 29.2 MB
在這個 .apk 檔案的 assemblies 目錄下,存在這這些 .NET 組件檔案,這些檔案內容與大小,皆與沒有設定 AOT 選項所產生的內相同。
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,卻發現到更多的檔案出現,由副檔案名稱為 .so ,我們可以知道這些是 Android 系統下的 Native Code。因此,我們也就知道了當設定 AOT 模式,所產生的 .apk 檔案內,已經有這些 .NET 組件的機器碼 Native Code 產生在裡面了,因為,當實際執行這個 Xamarin.Android 專案的時候,我們就不再需要透過 JIT 來即時轉譯 IL 碼到 Native Code,因為,這些 Native Code 都已經產生好了,所以,按照理論上來說,執行效能應該會更好。
Xamarin.Android apk file

實驗 3 : 將組譯碼組合成機器碼 (無) / 符合 AOT 規範 (實驗性) (有) / 使用 LLVM 最佳化編譯器 (有)

在此模式下產生的 .apk 檔案大小為 : 26.7 MB
在這個 .apk 檔案的 assemblies 目錄下,存在這這些 .NET 組件檔案,這些檔案內容與大小,皆與沒有或者有設定 AOT 選項所產生的內相同。
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,我們發現到這些 .so 檔案數量不變,不過,您可以發現到這些檔案的大小卻明顯的變小了許多,這些檔案變小,都是有助於 LVVM 有啟用的關係。
Xamarin.Android apk file

實驗 4 : 將組譯碼組合成機器碼 (有) / 符合 AOT 規範 (實驗性) (無) / 使用 LLVM 最佳化編譯器 (無)

在此模式下產生的 .apk 檔案大小為 : 8.83 MB
在這裡,我們要來檢測 將組譯碼組合成機器碼 選項,根據微軟官方文件上的說明:啟用此選項時,系統會將組件組合成原生共用程式庫。 此選項會保護程式碼的安全。它會將受控組件內嵌於原生二進位檔來予以保護。
現在,因為這些 .NET 組件被保護起來了,因此,我們在這個 .apk 檔案內,也就無法發現到 assemblies 目錄存在
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,僅有這些檔案,比起實驗 1,多了一個 libmonodroid_bundle_app.so 檔案,我們可以知道,這個就是被保護起來的 .NET 組件檔案。
Xamarin.Android apk file

實驗 5 : 將組譯碼組合成機器碼 (有) / 符合 AOT 規範 (實驗性) (有) / 使用 LLVM 最佳化編譯器 (無)

在此模式下產生的 .apk 檔案大小為 : 20.3 MB
現在,因為這些 .NET 組件被保護起來了,因此,我們在這個 .apk 檔案內,也就無法發現到 assemblies 目錄存在
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,多了取多檔案,這些都是 .NET 組件的 Native Code,不過,比起實驗 1,多了一個 libmonodroid_bundle_app.so 檔案,我們可以知道,這個就是被保護起來的 .NET 組件檔案。
Xamarin.Android apk file

實驗 6 : 將組譯碼組合成機器碼 (有) / 符合 AOT 規範 (實驗性) (有) / 使用 LLVM 最佳化編譯器 (有)

在此模式下產生的 .apk 檔案大小為 : 17.8 MB
現在,因為這些 .NET 組件被保護起來了,因此,我們在這個 .apk 檔案內,也就無法發現到 assemblies 目錄存在
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,可以看得出來,經過 LLVM 的最佳化處理,這些 .NET 組件所產生的 Native Code,也都變小了許多。
Xamarin.Android apk file



2018/06/04

Xamarin.Android 執行時期的權限處理說明 3 使用手機內其他 PDF App 來開啟在 download 資料夾內的檔案

範例專案 : XFFileProvider

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式
在上兩篇文章 Xamarin.Android 執行時期的權限處理說明 2 使用 Plugin.Permissions / Xamarin.Android 執行時期的權限處理說明 1 最低的 Android 版本 / 目標 Android 版本 / 目標 Framework 中,我們說明了 Android API Level 23 以上的作業系統,如何進行處理執行時期的權限授權處理方法,在這篇文章中,我們將會產生一個檔案到 Android 檔案系統中,不過,我們不是要將檔案儲存 App Sandbox 沙箱目錄內,而是要建立在 Download 資料夾內,這個資料夾內存在的檔案,連使用者也可以自行查看與開啟,不過,我們將要使用手機內已將安裝的其他,使用這些 App 來開啟這個檔案,在這裡,我們會建立一個 PDF 檔案,不過,檔案內容卻是一般文字,接者,可以讓使用者選取要使用哪個已安裝的 PDF 檢視軟體來開啟這個檔案。
不過,由於在 Android SDK 24 以上的版本,在使用 Intent 來開啟檔案的時候,加入了開啟檔案的權限判斷,因此,我們之前所使用的 Android.Net.Uri.FromFile(file); 方法,將會需要修改成為 Android.Support.V4.Content.FileProvider.GetUriForFile(Android.App.Application.Context,Android.App.Application.Context.PackageName + ".fileprovider", file); 這樣的方法呼叫,不過,還不只如此,我們還需要在 AndroidManifest 檔案內,加入額外的宣告,並且另外新增一個 XML 檔案。
因此,在這篇文章中,我們將會說明要如何做到這樣的事情,我們會設計一個頁面,裡面有兩個按鈕,第一個按鈕,將會取得使用者授權這個 App 可以使用 Storage 使用權限,第二個按鈕,將會在 download 目錄下產生一個 pdf 檔案,緊接著,將會使用手機內可以開啟 pdf 的檔案,幫助我們開啟這個 PDF 檔案與看到這些內容,當然,若您的手機中,可以開啟 pdf 的檔案 App 超過一個以上,Android 系統,將會顯示一個小對話窗,列出您手機中可以開啟 pdf 檔案的所有 App,您可以選擇任何一個來開啟這個 pdf 檔案。

在 Download 目錄下建立一個 PDF 檔案

在這個按鈕的命令委派方法中,我們透過相依性注入方法,取得了 download 目錄的路徑,並且包裝成為 PCLStorage 套件中使用的 IFolder 物件,如此,我們針對這個 IFolder 注入後的物件,也就是下面程式碼中的 rootFoler 變數,就可以在這個 download 目錄下來產生任何檔案了。我們將會在這裡產生一個 PDF.pdf 名稱的檔案,不過,該檔案內容僅是一個文字內容。
最後,我們使用相依性注入方法,取得了在 Android 平台下實作的 IOpenFileByName 物件,我們就可以針對該物件進行使用別的 App 來開啟這個檔案的動作了。
OpenPDFCommand = new DelegateCommand(async () =>
{
    string filename = "PDF.pdf";
    IFolder rootFolder = _publicFileSystem.PublicDownloadFolder;
    IFile file = await rootFolder.CreateFileAsync(filename, CreationCollisionOption.OpenIfExists);
    await file.WriteAllTextAsync("123");
    _openFileByName.OpenFile(file.Path);
});

開啟 Downoad 目錄下的檔案

底下的程式碼將會需要在 Android 原生專案內實作,因為需要使用到各種 Android SDK API,我們將透過傳送過來的檔案完整路徑,建立一個 Java.IO.File 物件,接著檢查 TargetSDK 是否小於 24,若是大於等於 API Level 24,我們將會需要 這個方法,來取得該檔案的 URI : Android.Support.V4.Content.FileProvider.GetUriForFile(Android.App.Application.Context,Android.App.Application.Context.PackageName + ".fileprovider", file);
接著,當然需要建立一個 Intent 物件,最後,呼叫這個方法 Android.App.Application.Context.StartActivity(intent); 來開啟這個檔案。
很不幸的,看似一切完美,可以當您實際執行這個專案,並點下按鈕之後,您將會得到這個例外異常錯誤訊息 : Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
public void OpenFile(string fullFileName)
{
    try
    {
        Java.IO.File file = new Java.IO.File(fullFileName);
        file.SetReadable(true);

        string application = "";

        application = "application/pdf";

        var targetSdk = ResolvePackageTargetSdkVersion();
        if (targetSdk < 24)
        {
            Android.Net.Uri uri = Android.Net.Uri.FromFile(file);
            Intent intent = new Intent(Intent.ActionView);
            intent.SetDataAndType(uri, application);
            intent.SetFlags(ActivityFlags.ClearWhenTaskReset | ActivityFlags.NewTask);

            Android.App.Application.Context.StartActivity(intent);
            try
            {
                Android.App.Application.Context.StartActivity(intent);
            }
            catch (Exception)
            {
                Toast.MakeText(Android.App.Application.Context, "No Application Available to View this file.", ToastLength.Short).Show();
            }
        }
        else
        {
            var foo = Android.App.Application.Context.PackageName + ".fileprovider";
            Android.Net.Uri uri = Android.Support.V4.Content.FileProvider.GetUriForFile(
                Android.App.Application.Context,
                foo, file);

            Intent intent = new Intent(Intent.ActionView);
            intent.SetDataAndType(uri, application);
            intent.AddFlags(ActivityFlags.GrantReadUriPermission);
            intent.AddFlags(ActivityFlags.NoHistory);
            intent.AddFlags(ActivityFlags.ClearWhenTaskReset | ActivityFlags.NewTask);

            try
            {
                Android.App.Application.Context.StartActivity(intent);
            }
            catch (Exception)
            {
                Toast.MakeText(Android.App.Application.Context, "No Application Available to View this file.", ToastLength.Short).Show();
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

其他設定

  • 請使用滑鼠雙擊您的 Android 專案 Properties 目錄,切換到 Android 資訊清單頁次,記錄下您這個 Android 專案的套件名稱。
  • 展開 Android 專案的 Properties 節點,您將會看到 AndroidManifest.xml 檔案,請打開他
  • 在 AndroidManifest.xml 檔案中加入底下的宣告
  • 在 android:authorities 屬性中,請使用您實際專案的套件名稱,替換掉這個字串: [Android 專案的套件名稱]
    <provider 
      android:name="android.support.v4.content.FileProvider" 
      android:authorities="[Android 專案的套件名稱].fileprovider" 
      android:exported="false" 
      android:grantUriPermissions="true">
      <meta-data 
        android:name="android.support.FILE_PROVIDER_PATHS" 
        android:resource="@layout/file_provider_path" />
    </provider>
  • 另外,我們需要建立一個 xml 檔案,在這裡,我們建立到 layout 目錄下,他的檔案內容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <external-path name="Download" path="Download/"/>
</paths>
好的,現在我們可以再度執行這個應用程式,當我們點選了 開啟 PDF 檔案,此時,螢幕最下方出現了 選擇開啟工具 對話窗,您可以選擇要使用哪個 App 來開啟這個檔案。
小米 Max 小米 Max



2018/05/31

Xamarin.Android 執行時期的權限處理說明 1 最低的 Android 版本 / 目標 Android 版本 / 目標 Framework

這篇文章的說明範例專案,可以從 這裡 取得

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式
當我們在進行 Xamarin.Android 專案建置的時候,我們可以從 Android 專案內的 Properties 中,也就是這個專案的屬性視窗內,看到關於這三個名詞:最低的 Android 版本 / 目標 Android 版本 / 目標 Framework。若您想要更進一步的瞭解這三者的差異,可以參考 了解 Android API 層級 ,底下根據這篇文章內容,列出這三者的說明
  • 目標 Framework (也稱為 compileSdkVersion)
    是在建置階段編譯您的應用程式特定的 Android 架構版本 (API 層級)
  • 最低的 Android 版本 (也稱為 minSdkVersion)
    是 Android OS (亦即,最低 API 層級),可以安裝和執行應用程式的舊版本
  • Android 的版本為目標 (也稱為 targetSdkVersion)
    是應用程式開發介面層級的 Android 裝置,應用程式預期執行。
由於 從 2018年 8 月之後 ,Google Play Console 將會需要新的應用程式目標應用程式開發介面層級 26 (Android 8.0) 或更高版本,這裡所指的是 目標 Android 版本。 現有的應用程式必須以 API 層級 26 或更高版本。如需詳細資訊,請參閱 改善應用程式安全性和效能
因此,在這裡,我們要來做些小測試,了解如何進行執行時期的權限檢查與請求使用者授予使用這些權限的程式設計方法。

建立測試專案

  • 首先,使用 Xamarin.Forms for Prism 建立一個專案
  • 滑鼠雙擊 Android 專案內的 Properties 節點,此時,這個專案的屬性視窗會顯示出來
  • 請切換到 應用程式 標籤頁次,點選 使用下列 Android 版本編譯: (目標 Framework) 下拉選單,確定選擇的是 使用最新平台,這裡是會設定 compileSdkVersion
  • Android Target Version compileSdkVersion
    這裡是英文版的 Visual Studio 2017 看到的內容,我們可以看到這個欄位名稱為 Compile using Android version: (Target Framework)
    Android Target Version compileSdkVersion
  • 請切換到 Android 資訊清單 (Manifest) 標籤頁次,點選 目標 Android 版本 下拉選單,根據文件建議,請在這裡選擇最新的 SDK 版本,這裡是會設定 targetSdkVersion
  • Android Target Version compileSdkVersion
    這裡是英文版的 Visual Studio 2017 看到的內容,我們可以看到這個欄位名稱為 Target Android version
    Android Target Version compileSdkVersion
  • 請打開 Android 專案內的 MainActivity.cs 檔案,在組件層級,也就是命名空間之外,輸入底下程式碼,我們在這宣告了這個 Android 應用程式,需要使用這四種權限 Permission
[assembly: UsesPermission(Name = Android.Manifest.Permission.ReadExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.Camera)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.CallPhone)]
  • 現在,請開始執行這個專案,您可以使用 Android SDK 6.0 以上的模擬器或者是實體手機來進行測試。
  • 當這個應用程式執行完成後,請中止執行,並且在 Android 裝置或模擬器上,打開設定中的應用程式頁面,請在清單中找到您剛剛執行成功的應用程式名稱,並且點選它
  • 當這個應用程式打開之後,您會看到這個應用程式明細頁面中,有個 Permissions 選項,請點選它,接著您就會發現到,這裡有三個 Permissions,不過,他們都是沒有被啟用的。
    Visual Studio for Android Emulator Visual Studio for Android Emulator
    Visual Studio for Android Emulator Visual Studio for Android Emulator
  • 此時,若您的應用程式中,剛好有執行與使用到這三個權限相關的 API,不幸的是,您的應用程式,就會造成閃退的問題。

加入執行時期的所需權限檢查

  • 建立一個 RuntimePermissionEvent 類別,用來定義 事件聚合器需要訂閱的事件類別
public class RuntimePermissionEvent : PubSubEvent<RuntimePermissionPayload>
{

}

public class RuntimePermissionPayload
{

}
  • 請將測試頁面的 XAML 修改成為如下
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFRunPermission.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Button Text="Permission 檢查與授權" Command="{Binding CheckPermissionCommand}" />
    </StackLayout>

</ContentPage>
  • 請將該頁面的 ViewModel,修改成為底下 C# 程式碼
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XFRunPermission.Events;

namespace XFRunPermission.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        private readonly IEventAggregator _eventAggregator;
        public DelegateCommand CheckPermissionCommand { get; set; }
        public MainPageViewModel(INavigationService navigationService,
            IEventAggregator eventAggregator) 
            : base (navigationService)
        {
            _eventAggregator = eventAggregator;
            CheckPermissionCommand = new DelegateCommand(() =>
            {
                _eventAggregator.GetEvent<RuntimePermissionEvent>().Publish(
                    new RuntimePermissionPayload());
            });
            Title = "Main Page";
        }
    }
}
  • 在 Android 專案內找到 MainActivity.cs 節點,將這個檔案內的內容,使用底下程式碼來替換
using Android.App;
using Android.Content.PM;
using Android.OS;
using Prism;
using Prism.Events;
using Prism.Ioc;
using XFRunPermission.Events;

[assembly: UsesPermission(Name = Android.Manifest.Permission.ReadExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.Camera)]
[assembly: UsesPermission(Name = Android.Manifest.Permission.CallPhone)]
namespace XFRunPermission.Droid
{
    [Activity(Label = "XFRunPermission", Icon = "@mipmap/ic_launcher", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        IEventAggregator fooIEventAggregator;
        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            global::Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication(new App(new AndroidInitializer()));

            var fooContainer = ((App.Current) as Prism.Unity.PrismApplication).Container;
            fooIEventAggregator = fooContainer.Resolve<IEventAggregator>();
            fooIEventAggregator.GetEvent<RuntimePermissionEvent>().Subscribe(x =>
            {
                CheckPermissions();
            });
        }

        void CheckPermissions()
        {
            if ((int)Build.VERSION.SdkInt < 23)
            {
                return;
            }

            var fooRead = CheckSelfPermission(Android.Manifest.Permission.ReadExternalStorage);
            var fooWrite = CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);
            if ((fooRead == (int)Permission.Granted) && (fooWrite == (int)Permission.Granted))
            {
                var foo = 0;
            }
            else
            {
                Android.Support.V4.App.ActivityCompat.RequestPermissions(this,
                    new string[] { Android.Manifest.Permission.ReadExternalStorage, Android.Manifest.Permission.WriteExternalStorage,
                    Android.Manifest.Permission.CallPhone, Android.Manifest.Permission.Camera}, 4);
            }
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
        {
            var foo = 1;
        }
    }

    public class AndroidInitializer : IPlatformInitializer
    {
        public void RegisterTypes(IContainerRegistry container)
        {
            // Register any platform specific implementations
        }
    }
}

開始進行測試

  • 請再度執行這個專案,這個時候,首頁會看到這個畫面,請點選螢幕上的按鈕
    Visual Studio for Android Emulator
  • 當按鈕按下去之後,您將會看到了下圖畫面,系統將會要求您同意授權給予三種應用程式使用權限,在這裡,請在第一個權限使用授權點選 DENY,另外兩個,可以點選 ALLOW
  •  Visual Studio for Android Emulator Visual Studio for Android EmulatorVisual Studio for Android Emulator
  • 完成後,您就可以關閉這個應用程式,回到 設定 > 應用程式,來查看這個應用程式可以使用權限的說明,現在,我們可以看到這個應用程式,經過使用者授權,已經可以使用 Camera & Phone 兩種 Permission 了。
  •  Visual Studio for Android Emulator