IIS express Xamarin.Android でapkのアップデート機能（自前）

#要件
自作アプリのapkをアプリ内から、ダウンロード、インストールさせたい。

#実装
###server
サーバーのルート直下にApkフォルダを作成し、そこに最新のapkを配置した場合のコードです。

```C#
/// <summary>
    /// APKコントローラー
    /// </summary>
    public class ApkController : BaseApiController
    {
        // ====================================================================================================
        #region Get

        /// <summary>
        /// ファイル情報クラス
        /// </summary>
        public class ApkResult : IHttpActionResult
        {
            /// <summary>
            /// ファイルパス
            /// </summary>
            public string FilePath { get; }

            /// <summary>
            /// コンストラクタ
            /// </summary>
            /// <param name="filePath">ファイルパす</param>
            public ApkResult(string filePath)
            {
                if (filePath == null)
                    throw new ArgumentNullException(nameof(filePath));

                FilePath = filePath;
            }

            /// <summary>
            /// HTTP応答メッセージ作成
            /// </summary>
            /// <param name="cancellationToken">キャンセルトークン</param>
            public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StreamContent(File.OpenRead(FilePath));
                var contentType = MimeMapping.GetMimeMapping(Path.GetExtension(FilePath));
                response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);

                return Task.FromResult(response);
            }
        }

        /// <summary>
        /// Apk取得
        /// </summary>
        [Route("api/Apk")]
        public IHttpActionResult GetApk()
        { 
            var strFile = HttpContext.Current.Server.MapPath("~/") + "your/api/path/app.apk";
            
            return new ApkResult(strFile);
        }

        #endregion
    }
```

###client
今回はPLC内から、android固有の機能を使う必要があるのでこちらを参考に実装します。
https://qiita.com/tenjoufire/items/c5e4206f33cce925b494

PLC側にInterfaceフォルダを作成し、IAppVersionUpService.csを作成します。

```C#
namespace MyApp.Interface
{
    public interface IAppVersionUpService
    {
        public async void Install()
        {
        }
    }
}
```
Droid側に以下を作ります(namespaceは環境に合わせて変えてください)

```C#
[assembly: Dependency(typeof(AppVersionUpService))]
namespace MyApp.Droid.Service
{
    class AppVersionUpService : IAppVersionUpService
    {
        public async void Install()
        {
        }
    }
}
```

Doroid側のInstall関数内に実装を追加します。
HttpClientを利用してapiをたたいた場合

```C#

// APKのダウンロード
HttpClientManageHandler handler = new HttpClientManageHandler();
var Client = new HttpClient(handler);
var apkRes = await Client.GetAsync("https://myurl/getApk");

// APKファイルの保存ディレクトリの取得
string saveDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal),
                "AppData/Apk");
// ディレクトリの作成
if (!Directory.Exists(path))
{
     Directory.CreateDirectory(path);
}

// 保存ファイル名、保存パスの取得
string fileName = Android.App.Application.Context.PackageName + ".apk";
string saveFilePath = Path.Combine(saveDirectory, fileName);

// レスポンスから取り出し、ファイルを保存
using (var stream = apkRes.Content.ReadAsStreamAsync().Result)
using (var fileStream = new FileStream($"{saveFilePath}", FileMode.Create, FileAccess.Write, FileShare.None))
{
    stream.CopyTo(fileStream);
}
```
ここまででダウンロードができました。
その下にインストール処理を追記していきます。
今回アプリ内で取得したファイルを端末側にあるインストーラー機能に共有してあげる必要があります。androidのバージョンが上がって、共有方法が複雑になったみたいです。FileProviderというクラスを使って共有していきます。

```C#
// 共有URIの取得
var file = new Java.IO.File(saveFilePath);
var uri = FileProvider.GetUriForFile(Android.App.Application.Context,
            Android.App.Application.Context.PackageName + ".provider",
            file);

// インストーラーの起動
Intent intent = new Intent(Intent.ActionView);
intent.SetDataAndType(uri, "application/vnd.android.package-archive");
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.GrantReadUriPermission);
Android.App.Application.Context.StartActivity(intent);

// 強制するならインストールがキャンセルされたらループ
while(true)
{
    Android.App.Application.Context.StartActivity(intent);
}
```
FileProviderの設定
AndroidManifest.xmlのapplicationタグ内に追記します。
こちらを参考にしてます。
http://kit-lab.hatenablog.jp/entry/2017/01/08/011428#fileURI-%E3%81%AE%E7%A6%81%E6%AD%A2

```xml

<application android:label="MyApp">
	<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true">
		<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
    </provider>
</application>
```

DroidのResourceフォルダ内にxmlフォルダを作ってその中にfile_paths.xmlを作成します

```xml
<?xml version="1.0" encoding="utf-8" ?>
<paths>
  <external-path name="external" path="." />
  <files-path name="share" path="." />
  <cache-path name="caches" path="." />
  <root-path name="sdcard" path="." />
</paths>
```

これでインストールすると「すでにこの名前のアプリがインストールされています」とエラーがでたのですが、どうやらデバッグビルドで出力されるapkに署名をしてないのが原因でした。（リリースビルドしたapkとデバッグビルドのapkで同じ署名をする必要がある）
以下を参考にapkを出力したら
http://ponta666.hatenablog.com/entry/2017/06/04/005744

Droidプロジェクトファイルを右クリックして＞プロパティ＞Android パッケージへの署名＞ここで、上記のURLの手順でapkを作成していると、キーストアが出来上がっているはずなので選択します。

同じ署名をしたapkどうしなら、インストールで上書きができます。

## Android 8.0 api26からは以下の設定が必要
AnadroidManifest.xmlのmanifestタグ内に追加

```
 <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> 
```
ただ8.0以降はGoogleStore以外から配信されたアプリをインストールするのに、
設定から、「提供元が不明のアプリのインストールを許可」をONにする必要があるので、微妙
https://developer.android.com/distribute/marketing-tools/alternative-distribution?hl=ja