Skip to main content

GeneralUpdate.Avalonia.Android

Namespace: GeneralUpdate.Avalonia.Android | Main Entry: GeneralUpdateBootstrap.CreateDefault() | NuGet Package: GeneralUpdate.Avalonia.Android

1. Component Overview

1.1 Overview

GeneralUpdate.Avalonia.Android is an Android auto-update component designed for Avalonia applications. It provides version comparison, resumable APK download with HTTP Range support, SHA256 integrity verification, and Android Package Installer launch orchestration.

Unlike desktop applications where files can be directly replaced, Android APK installation must go through the system Package Installer. This component encapsulates these platform differences, allowing Avalonia developers to implement updates simply by providing package metadata.

Core Capabilities:

CapabilityDescription
Version ComparisonBuilt-in SystemVersionComparer with custom strategy support via IVersionComparer
Resumable DownloadHttpResumableApkDownloader with HTTP Range headers and progress reporting
SHA256 VerificationAutomatic file hash verification against the server-provided hash on download completion
APK InstallationAndroidApkInstaller launches system Package Installer via FileProvider URI
HTTP Transport ConfigHttpDownloadOptions supports SSL/TLS policies, proxy, timeout, retry, and authentication
Multi-Protocol AuthHMAC-SHA256, Bearer Token, API Key, and HTTP Basic authentication
Event NotificationsVersion discovery, download progress, completion, and failure events
State SnapshotGetSnapshot() for current update state, failure reason, and message
UI Thread DispatchingIUpdateEventDispatcher for dispatching events to UI thread
Progressive DownloadTemporary .part files and Sidecar .json metadata for process-level resume

Solved Business Pain Points:

  • Avalonia apps on Android need reliable in-app updates, but APK installation must go through the system installer
  • Large APK downloads can be interrupted — resume support reduces re-download traffic
  • Downloaded APKs need integrity checks to prevent corruption or tampering
  • Flexible authentication schemes are needed for various server requirements
Android Update ≠ Desktop File Replacement

On Android, APKs must be installed via the Android Package Installer, which handles signature verification, permission granting, and app replacement. This component encapsulates these platform differences, but APK signing and version incrementation must still be handled in your build pipeline.

Use Cases:

  • Avalonia cross-platform app APK auto-update on Android
  • Enterprise in-house Android app distribution
  • CDN / OSS distributed APK packages

1.2 Environment & Dependencies

ItemDescription
NuGet PackageGeneralUpdate.Avalonia.Android
Target Frameworknet10.0-android (min Android API 26+)
DependenciesXamarin.AndroidX.Core (FileProvider support)
Platform SupportAndroid 8.0 (API 26) and above

2. Feature List

FeatureDescriptionTypeRequiredNotes
Version CheckCompare current vs target versionCoreYesValidateAsync, supports custom comparers
APK Resume DownloadResumable download with HTTP RangeCoreYesDownloadAndVerifyAsync
SHA256 VerificationAuto-verification on completionCoreAutoVia IHashValidator
APK Install TriggerLaunch system Package InstallerCoreYesLaunchInstallerAsync
HTTP Transport ConfigSSL, proxy, timeout, retry, authExtensionOptionalHttpDownloadOptions
Multi-Protocol AuthHMAC/Bearer/ApiKey/BasicExtensionOptionalGlobal or per-package
Progress NotificationSpeed, bytes, percentageCoreOptionalAddListenerDownloadProgressChanged
Completion NotificationVersion found, download done, install doneCoreOptionalAddListenerUpdateCompleted
Failure NotificationFailure reason, exception, package infoCoreOptionalAddListenerUpdateFailed
Validation NotificationUpdate available eventCoreOptionalAddListenerValidate
State SnapshotGet current update state anytimeExtensionOptionalGetSnapshot()
UI Thread DispatchCustom event dispatch strategyExtensionOptionalImplement IUpdateEventDispatcher
Custom DownloaderCustom download implementationExtensionOptionalImplement IUpdateDownloader
Custom Hash ValidatorCustom hash algorithmExtensionOptionalImplement IHashValidator
Custom Version ComparerCustom version comparisonExtensionOptionalImplement IVersionComparer
Custom APK InstallerCustom install implementationExtensionOptionalImplement IApkInstaller
Custom File StorageCustom file I/OExtensionOptionalImplement IFileStorage
Custom SSL PolicyCustom certificate validationExtensionOptionalImplement ISslValidationPolicy
Custom HTTP AuthCustom request authenticationExtensionOptionalImplement IHttpAuthProvider

3. API Configuration

3.1 Properties

AndroidUpdateOptions:

FieldTypeDefaultRequiredDescription
DownloadDirectoryPathstring"" (auto: CacheDir/update or TempPath/update)OptionalDownload directory path
FileProviderAuthoritystring""YesFileProvider authority matching AndroidManifest
TemporaryFileExtensionstring".part"OptionalTemp file extension
SidecarExtensionstring".json"OptionalResume metadata file extension
DownloadBufferSizeint65536 (64 KB)OptionalDownload buffer size (bytes)
SpeedSmoothingWindowSecondsint4OptionalSpeed smoothing window (seconds)

HttpDownloadOptions:

FieldTypeDefaultRequiredDescription
SslValidationPolicyISslValidationPolicy?nullOptionalCustom SSL validation; null uses system default
RequestTimeoutTimeSpan30sOptionalPer-request timeout
DownloadTimeoutTimeSpan10minOptionalOverall download timeout
ProxyIWebProxy?nullOptionalHTTP proxy
UseProxyboolfalseOptionalWhether to use configured proxy
MaxRetryAttemptsint3OptionalMax retry count (3 = 1 initial + 2 retries)
RetryBaseDelayTimeSpan1sOptionalExponential backoff base: baseDelay * 2^attempt
AuthProviderIHttpAuthProvider?nullOptionalGlobal auth provider; per-package takes precedence

UpdatePackageInfo:

FieldTypeDefaultRequiredDescription
VersionstringYesTarget version
DownloadUrlstringYesAPK download URL
Sha256stringYesSHA256 hash for integrity check
FileSizelong0OptionalFile size in bytes
FileNamestring?nullOptionalDownloaded file name
IsForcedboolfalseOptionalForce update
VersionNamestring?nullOptionalDisplay version name
Descriptionstring?nullOptionalVersion description
PublishTimeDateTimeOffset?nullOptionalPublish time
AuthSchemeAuthScheme?nullOptionalPer-package auth scheme
AuthTokenstring?nullOptionalBearer or API Key token
AuthSecretKeystring?nullOptionalHMAC-SHA256 secret key
BasicUsernamestring?nullOptionalHTTP Basic username
BasicPasswordstring?nullOptionalHTTP Basic password

UpdateState Enum:

ValueOrdinalDescription
None0Initial state
Checking1Checking for updates
UpdateAvailable2Update found
Downloading3Downloading
Verifying4Verifying integrity
ReadyToInstall5Ready to install
Installing6Installing
Completed7Update completed
Failed8Update failed
Canceled9Canceled

AuthScheme Enum:

ValueDescription
HmacHMAC-SHA256 signature authentication
BearerBearer Token authentication
ApiKeyAPI Key authentication
BasicHTTP Basic authentication

3.2 Instance Methods

GeneralUpdateBootstrap (Static Factory):

MethodParametersUse CaseNotes
CreateDefault(AndroidUpdateOptions, ...)options — update options; optional: contextProvider, activityProvider, httpClient, versionComparer, eventDispatcher, logger, httpOptionsCreate default Android bootstrap instanceNull params use built-in defaults

IAndroidBootstrap:

MethodParametersUse CaseNotes
ValidateAsync(UpdatePackageInfo, string, CancellationToken)packageInfo, currentVersion, ctCheck for updatesReturns UpdateCheckResult with UpdateFound and TargetVersion
DownloadAndVerifyAsync(UpdatePackageInfo, CancellationToken)packageInfo, ctDownload APK and verify SHA256FilePath contains APK path on success
LaunchInstallerAsync(UpdatePackageInfo, string, CancellationToken)packageInfo, apkFilePath, ctLaunch Android Package InstallerRequires correct FileProvider authority
GetSnapshot()NoneGet current state snapshotReturns UpdateStateSnapshot(State, FailureReason, Message)
Dispose()NoneRelease downloader resources

3.3 Events

EventArgsTriggeredUsage
AddListenerValidateValidateEventArgsPackageInfo, CurrentVersionWhen update is found after version comparisonUI display of new version info
AddListenerDownloadProgressChangedDownloadProgressChangedEventArgsProgressPercentage, DownloadSpeedBytesPerSecond, DownloadedBytes, RemainingBytes, TotalBytes, PackageInfo, StatusDescriptionOn download progress updateProgress bar display
AddListenerUpdateCompletedUpdateCompletedEventArgsResult (UpdateOperationResult)On download or install completionUI state transition
AddListenerUpdateFailedUpdateFailedEventArgsResult (UpdateOperationResult)On any stage failureError display with reason and exception

4. Advanced Examples

4.1 Extensibility Overview

All services can be replaced via CreateDefault optional parameters:

InterfaceDefault ImplementationDescription
IVersionComparerSystemVersionComparerVersion comparison strategy
IUpdateDownloaderHttpResumableApkDownloaderAPK download
IHashValidatorSha256HashValidatorSHA256 hash verification
IApkInstallerAndroidApkInstallerAPK install trigger
IFileStoragePhysicalFileStorageFile storage operations
IUpdateEventDispatcherImmediateEventDispatcherEvent dispatcher (direct invoke)
IUpdateLoggerNoOpUpdateLoggerLogger
IAndroidContextProviderDefaultAndroidContextProviderAndroid Context provider
IAndroidActivityProviderNullAndroidActivityProviderAndroid Activity provider
ISslValidationPolicyVia HttpDownloadOptionsSSL validation policy
IHttpAuthProviderVia HttpDownloadOptions or per-packageHTTP auth provider

4.2 Scenario Examples

Scenario 1: Custom Version Comparer

using GeneralUpdate.Avalonia.Android.Abstractions;

public sealed class CustomDateVersionComparer : IVersionComparer
{
public bool TryCompare(string currentVersion, string targetVersion,
out int compareResult, out string? errorMessage)
{
try
{
var current = ParseVersion(currentVersion);
var target = ParseVersion(targetVersion);
compareResult = target.CompareTo(current);
errorMessage = null;
return true;
}
catch (Exception ex)
{
compareResult = 0;
errorMessage = ex.Message;
return false;
}
}

private static DateTime ParseVersion(string version) =>
DateTime.ParseExact(version, "yyyy.MM.dd.fff", null);
}

// Usage
var bootstrap = GeneralUpdateBootstrap.CreateDefault(
options,
versionComparer: new CustomDateVersionComparer());

Scenario 2: Custom Event Dispatcher (Avalonia UI Thread)

using GeneralUpdate.Avalonia.Android.Abstractions;

public sealed class AvaloniaEventDispatcher : IUpdateEventDispatcher
{
public void Dispatch(Action callback)
{
if (Avalonia.Threading.Dispatcher.UIThread.CheckAccess())
callback();
else
Avalonia.Threading.Dispatcher.UIThread.Post(callback);
}
}

var bootstrap = GeneralUpdateBootstrap.CreateDefault(
options,
eventDispatcher: new AvaloniaEventDispatcher());

Scenario 3: Custom Downloader

using GeneralUpdate.Avalonia.Android.Abstractions;
using GeneralUpdate.Avalonia.Android.Models;

public sealed class EnterpriseDownloader : IUpdateDownloader
{
private readonly HttpClient _httpClient;
private readonly string _enterpriseToken;

public EnterpriseDownloader(HttpClient httpClient, string enterpriseToken)
{
_httpClient = httpClient;
_enterpriseToken = enterpriseToken;
}

public async Task<DownloadResult> DownloadAsync(
UpdatePackageInfo packageInfo,
Action<DownloadProgressInfo>? progressCallback,
CancellationToken cancellationToken = default)
{
var request = new HttpRequestMessage(HttpMethod.Get, packageInfo.DownloadUrl);
request.Headers.Add("X-Enterprise-Token", _enterpriseToken);
// Custom download and progress reporting...
}
}

5. Basic Usage Examples

5.1 Quick Start (Minimal Demo)

using GeneralUpdate.Avalonia.Android;
using GeneralUpdate.Avalonia.Android.Models;

var options = new AndroidUpdateOptions
{
FileProviderAuthority = "com.myapp.fileprovider"
};

var bootstrap = GeneralUpdateBootstrap.CreateDefault(options);

var updatePackage = new UpdatePackageInfo
{
Version = "2.0.0",
DownloadUrl = "https://update.example.com/app-v2.0.0.apk",
Sha256 = "expected-sha256-hash",
FileSize = 50_000_000,
FileName = "app_update_2.0.0.apk"
};

var checkResult = await bootstrap.ValidateAsync(
updatePackage, currentVersion: "1.0.0");

if (!checkResult.UpdateFound)
{
Console.WriteLine("Already up to date.");
return;
}

var downloadResult = await bootstrap.DownloadAndVerifyAsync(updatePackage);
if (!downloadResult.Success)
{
Console.WriteLine($"Download failed: {downloadResult.Message}");
return;
}

var installResult = await bootstrap.LaunchInstallerAsync(
updatePackage, downloadResult.FilePath!);

if (installResult.Success)
Console.WriteLine("Installer launched.");

5.2 Parameter Combination Example

using GeneralUpdate.Avalonia.Android;
using GeneralUpdate.Avalonia.Android.Abstractions;
using GeneralUpdate.Avalonia.Android.Models;
using GeneralUpdate.Avalonia.Android.Services;

var options = new AndroidUpdateOptions
{
FileProviderAuthority = "com.myapp.fileprovider",
DownloadBufferSize = 128 * 1024
};

var httpOptions = new HttpDownloadOptions
{
RequestTimeout = TimeSpan.FromSeconds(60),
DownloadTimeout = TimeSpan.FromMinutes(30),
MaxRetryAttempts = 5,
SslValidationPolicy = new AllowAllSslValidationPolicy()
};

var bootstrap = GeneralUpdateBootstrap.CreateDefault(
options, httpOptions: httpOptions);

bootstrap.AddListenerDownloadProgressChanged += (_, e) =>
{
Console.WriteLine($"Download: {e.ProgressPercentage:F1}% " +
$"({e.DownloadedBytes}/{e.TotalBytes})");
};

var package = new UpdatePackageInfo
{
Version = "2.0.0",
DownloadUrl = "https://cdn.example.com/app-v2.0.0.apk",
Sha256 = "expected-sha256-hash",
FileSize = 50_000_000,
FileName = "app-v2.0.0.apk"
};

var checkResult = await bootstrap.ValidateAsync(package, "1.0.0");
if (!checkResult.UpdateFound) return;

var downloadResult = await bootstrap.DownloadAndVerifyAsync(package);
if (!downloadResult.Success) return;

await bootstrap.LaunchInstallerAsync(package, downloadResult.FilePath!);

5.3 Production Example

using GeneralUpdate.Avalonia.Android;
using GeneralUpdate.Avalonia.Android.Enums;
using GeneralUpdate.Avalonia.Android.Models;

public sealed class AppUpdateService
{
private IAndroidBootstrap? _bootstrap;
private bool _disposed;

public event EventHandler<double>? ProgressChanged;
public event EventHandler<string>? StatusChanged;

public void Initialize(string fileProviderAuthority, string serverToken)
{
var httpOptions = new HttpDownloadOptions
{
MaxRetryAttempts = 3,
AuthProvider = new Services.BearerTokenAuthProvider(serverToken)
};

_bootstrap = GeneralUpdateBootstrap.CreateDefault(
new AndroidUpdateOptions { FileProviderAuthority = fileProviderAuthority },
httpOptions: httpOptions);

WireEvents();
}

public async Task<bool> UpdateAsync(
UpdatePackageInfo package, string currentVersion,
CancellationToken ct = default)
{
if (_bootstrap == null)
throw new InvalidOperationException("Not initialized.");

StatusChanged?.Invoke(this, "Checking for updates...");
var checkResult = await _bootstrap.ValidateAsync(package, currentVersion, ct);
if (!checkResult.UpdateFound)
{
StatusChanged?.Invoke(this, "Already up to date.");
return true;
}

StatusChanged?.Invoke(this, "Downloading...");
var downloadResult = await _bootstrap.DownloadAndVerifyAsync(package, ct);
if (!downloadResult.Success)
{
StatusChanged?.Invoke(this, $"Download failed: {downloadResult.Message}");
return false;
}

if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
var ctx = Android.App.Application.Context;
var pm = ctx.PackageManager;
if (pm != null && !pm.CanRequestPackageInstalls())
{
StatusChanged?.Invoke(this, "Please allow install from unknown apps.");
var intent = new Android.Content.Intent(
Android.Provider.Settings.ActionManageUnknownAppSources)
.SetData(Android.Net.Uri.Parse("package:" + ctx.PackageName));
intent.AddFlags(Android.Content.ActivityFlags.NewTask);
ctx.StartActivity(intent);
return false;
}
}

StatusChanged?.Invoke(this, "Installing...");
var installResult = await _bootstrap.LaunchInstallerAsync(
package, downloadResult.FilePath!, ct);

if (installResult.Success)
{
StatusChanged?.Invoke(this, "Installer launched.");
return true;
}

StatusChanged?.Invoke(this, $"Install failed: {installResult.Message}");
return false;
}

private void WireEvents()
{
if (_bootstrap == null) return;
_bootstrap.AddListenerDownloadProgressChanged += (_, e) =>
ProgressChanged?.Invoke(this, e.ProgressPercentage);
_bootstrap.AddListenerUpdateFailed += (_, e) =>
StatusChanged?.Invoke(this, $"Failed: {e.Result.Message}");
}

public void Dispose()
{
if (_disposed) return;
_bootstrap?.Dispose();
_disposed = true;
}
}

6. Global Configuration

Authentication

SchemeAuthSchemeRequired Fields
HMAC-SHA256HmacAuthSecretKey
Bearer TokenBearerAuthToken
API KeyApiKeyAuthToken
HTTP BasicBasicBasicUsername + BasicPassword

SSL Policy

PolicyClassUse Case
System DefaultnullProduction
Allow All (dev only)AllowAllSslValidationPolicyDevelopment with self-signed certs
CustomImplement ISslValidationPolicyPrivate CA or certificate pinning

Resume Download

Downloader uses temporary files (.part) and metadata files (.json):

CacheDir/update/
├── app-v2.0.0.apk.part # Partial download temp file
├── app-v2.0.0.apk.json # Resume metadata
└── app-v2.0.0.apk # Final file

AndroidManifest

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

Resources/xml/file_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="update/" />
</paths>

Platform Notes

ItemDetails
Min API26 (Android 8.0)
Install PermissionAndroid 8.0+ requires CanRequestPackageInstalls() check
FileProviderRequired to pass APK URI to system installer