Contattaci

Sviluppo applicazioni multipiattaforma: Xamarin

  • Data: 27 Marzo 2019
  • Autore: Alessio Benedetti
  • Categorie

  • Giuneco Tech

    Il nostro settore richiede uno studio continuo e una forte attenzione all’innovazione. Incentiviamo quindi dei programmi formativi annuali per tutti i componenti del team, con ore dedicate (durante l’orario di lavoro) e serate formative sia online che in presenza. Sponsorizziamo eventi, sia come partner che semplicemente come partecipanti, e scriviamo articoli su quello che abbiamo imparato per essere, a nostra volta, dei divulgatori.

    Vai alla sezione Team
  • Perché Sviluppare in Xamarin?

    Vista la grande diffusione delle applicazioni mobile, può essere utile avere uno strumento che fornisca un’alternativa allo sviluppo in codice nativo per i diversi ecosistemi mobile. Xamarin offre questa possibilità, utilizzando un unico linguaggio (C#), una libreria di classi e un run-time, utilizzabili su tutte e 3 le piattaforme per dispositivi mobile (iOS, Android, Windows Phone).

    Xamarin ha, quindi, il vantaggio di poter scrivere una singola volta le funzionalità comuni a tutte le piattaforme, garantendo quindi il riuso del codice e dando la possibilità agli sviluppatori di implementare le logiche di business, funzionalità di rete e funzionalità comuni una sola volta per tutti i dispositivi.

    In aggiunta a questo, Xamarin assicura anche un’interoperabilità con le librerie native (Objective-C, Java, C/C++), fornendo così la possibilità di sfruttare i vantaggi delle librerie iOS e Android (ad esempio per la realizzazione delle interfacce).

    Xamarin mobile: codifica e compilazione

    Gli elementi principali che costituiscono Xamarin sono:

    • Linguaggio C# (Lambda expression, LINQ, Task Parallel Library)
    • Mono .NET framework (.NET framework multipiattaforma)
    • Compilatore: può produrre un’app nativa (iOS) oppure un’app .NET integrata con run-time (Andorid)
    • IDE di sviluppo Visual Studio per la creazione, compilazione e distribuzione

    La compilazione, in particolare, genera l’applicazione nativa in modi diversi a seconda della piattaforma.

    In iOS il codice C# viene compilato in linguaggio assembly ARM con compilazione ahead-of-time (AOT), cioè viene compilata staticamente prima di eseguire il codice run-time. Il framework .NET viene incluso nell’assembly con delle limitazioni, poiché Apple non consente la generazione del codice a run-time:

    • Generics supportati in maniera limitata
    • Nessuna generazione codice dinamico (Reflection, Remoting)
    • Funzionalità run-time JIT non incluse
    • System.Configuration non incluso

    In Android, invece, la compilazione del codice C# genera un codice intermedio che viene incluso nel pacchetto MonoVM + JIT, che verrà poi compilato in codice nativo dal run-time Android (ART) durante l’istallazione dell’applicazione. La compilazione del codice intermedio genera tipi proxy Java, quindi alcune parti del codice C# vengono generati in maniera limitata (supporto parziale per i Generics).

    In Windows il codice C# viene compilato in linguaggio intermedio eseguibile dal run-time predefinito. Le piattaforme windows contengono anche un’opzione .NET Native che si comporta in maniera analoga al compilatore AOT.

    Architettura

    L’obbiettivo principale dello sviluppo multipiattaforma è creare un’architettura che massimizzi il codice condiviso. A questo scopo, utilizzare i principi della programmazione ad oggetti aiuta a creare un’applicazione ben strutturata:

    • Incapsulamento: assicurarsi che le classi espongano solo le funzionalità richieste, nascondendo le implementazioni
    • Separazione delle responsabilità: far sì che ogni componente abbia uno scopo preciso e ben definito, sia a livello di classe che di architettura
    • Polimorfismo: programmare interfacce o classi astratte con diverse implementazioni in modo da avere una porzione di codice base con funzionalità specifiche per piattaforma

    Il risultato sarà un’applicazione strutturata a livelli, partendo dai livelli più bassi adibiti alla gestione dei dati, i livelli di gestione dei servizi e logiche di business, fino ad arrivare al livello di interfaccia utente

    Progetto Core e Progetti per Piattaforma

    Un progetto Xamarin è normalmente suddiviso in 2 parti, il progetto core e i progetti specifici per piattaforma.

    Il progetto core è la parte di progetto che conterrà il codice condiviso, quindi opportunamente incapsulato. Dovendo contenere le parti condivise, questo progetto può avere al suo interno i seguenti livelli:

    • Dati: contenente il codice per l’archiviazione fisica dei dati (Sql o file XML)
    • Accesso ai dati: contente il codice di accesso, modifica, creazione ed eliminazione dei dati (CRUD)
    • Accesso ai servizi: contenente l’eventuale codice per gestire servizi remoti (Web services, download immagini, ecc.)
    • Di business: contenente la definizione delle classi che espongono le funzionalità dell’applicazione

    I progetti specifici per piattaforma dovranno implementare i seguenti livelli, facendo riferimento ognuno al proprio assembly (Xamarin.iOS, Xamarin.Android, Windows):

    • Livello di applicazione: contenente il codice che converte le classi modello definite nel livello business in quelle specifiche per l’interfaccia della piattaforma
    • Livello di interfaccia: contenente la definizione delle interfacce, schermate e controlli utente

    Figura1: Architettura dell’applicazione

    Gestire le varianti tra piattaforme

    Non sempre è possibile generalizzare le funzionalità che si vogliono implementare. Ci sono differenze tra piattaforme, ed anche tra modelli della stessa piattaforma (per esempio nei dispositivi Android), che impediscono la generalizzazione. Ad esempio, consideriamo le dimensioni degli schermo: alcune piattaforme hanno un meccanismo standard per gestirle (iOS, Windows), mentre altre hanno un’ampia gamma di dimensioni, che richiedono una gestione più complessa (Android). Un altro esempio è la gestione delle fotocamere, presenti fronte/retro solo su alcuni dispositivi, e spesso in grado anche di fare video. Per poter utilizzare queste funzionalità specifiche del dispositivo esistono 2 approcci: l’astrazione della piattaforma oppure l’utilizzo di implementazioni divergenti.

    Per astrazione della piattaforma si intende la definizione di interfacce o classi base nel codice condiviso, per poi creare l’implementazione specifica nel progetto della piattaforma. Questo approccio ha il vantaggio di poter utilizzare nell’implementazione il codice specifico della piattaforma, ma ha lo svantaggio di dover passare, come parametro o proprietà, l’interfaccia e la sua implementazione ai livelli del codice condiviso.

    Nell’approccio con implementazioni divergenti, invece, vengono utilizzate funzionalità specifiche di una piattaforma, tramite la compilazione condizionale oppure tramite l’ereditarietà delle interfacce. La compilazione condizionale è utile soprattutto nelle situazioni in cui il codice condiviso dovrà comunque comportarsi in maniera diversa a seconda della piattaforma su cui verrà eseguito. Di seguito un esempio che mostra come impostare il percorso di salvataggio di un file per database SQLite.

    Public static string DatabaseFilePath {
            get {
        var filename = “TodoDatabase.db3”;
    #if SILVERLIGHT
        // Windows Phone 8
        var path = filename;
    #else
    
    #if __ANDROID__
        string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); ;
    #else
    #if __IOS__
            // we need to put in /Library/ on iOS5.1 to meet Apple’s iCloud terms
            // (they don’t want non-user-generated data in Documents)
            string documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); // Documents folder
            string libraryPath = Path.Combine (documentsPath, “..”, “Library”);
    #else
            // UWP
            string libraryPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
    #endif
    #endif
            var path = Path.Combine (libraryPath, filename);
    #endif
            return path;
    }
    

    Il risultato sarà una classe che può essere usata su tutte le piattaforme, inserendo il file di database in un percorso diverso per ogni piattaforma.

    Prestazioni

    Le scarse prestazioni di un’applicazione possono inficiare irrimediabilmente l’esperienza dell’utente, tuttavia per migliorare le prestazioni non basta implementare codice efficiente. In Xamarin esistono delle strategie  che è bene conoscere per migliorare le prestazioni della propria applicazione.

    Garbage Collector

    I linguaggi come C# utilizzano la Garbage Collection per recuperare la memoria che non è più in uso. Xamarin utilizza 2 Garbage Collector:

    1. Sgen: Garbage Collector generazionale, predefinito nella piattaforma Xamarin
    2. Boehm: Garbage Collector non generazionale conservativo. È il Garbage Collector predefinito per le applicazioni Xamarin.iOS

    Il Garbage Collector impatta sulle prestazioni poiché, quando viene avviato, interrompe l’esecuzione del thread principale per recuperare la memoria. Durante questo periodo quindi potrebbe verificarsi una breve pausa dell’applicazione o addirittura un blocco dell’interfaccia. È quindi importante ridurre la pressione del Garbage Collector sull’applicazione riducendo la frequenza con cui viene effettuata l’operazione di recupero della memoria e la durata dell’operazione di Garbage Collection. Per fare questo basterà attenersi a delle regole precise:

    • Evitare operazioni di Garbage Collection in cicli ridotti usando pool di oggetti. Questa indicazione è pertinente in particolare per i giochi, che devono creare in anticipo la maggior parte degli oggetti usati
    • Rilasciare in modo esplicito risorse quali flussi, connessioni di rete, grandi blocchi di memoria e file quando non sono più necessari
    • Annullare la registrazione dei gestori degli eventi quando non sono più necessari

    Ridurre le dimensioni dell’app

    Per ridurre le dimensioni del file eseguibile è necessario agire sul processo di compilazione. Come visto in precedenza, a seconda della piattaforma, il codice compilato conterrà anche le classi definite nel framework .NET. Oltre a questo, se l’applicazione fa uso dei Generics, saranno presenti anche le versioni compilate di tutti i Generics nativi.

    Per favorire la riduzione delle dimensioni delle applicazioni, gli strumenti di compilazione della piattaforma Xamarin includono un linker. In fase di compilazione, il linker esegue un’analisi statica dell’applicazione per determinare i tipi e i membri effettivamente usati dall’applicazione stessa e una volta finito rimuove tutti i tipi e tutti i metodi inutilizzati dall’applicazione, riducendo così le dimensioni dell’eseguibile finale.

    Profiler Xamarin

    La profilatura è una tecnica che consente di determinare i punti del codice in cui l’ottimizzazione può influire maggiormente nella riduzione dei problemi di prestazioni. Il profiler tiene traccia dell’utilizzo di memoria dell’applicazione e quindi registra il tempo di esecuzione dei metodi nell’applicazione stessa. Il profiler Xamarin consente l’individualicazionizione dei problemi relativi alle prestazioni in un’applicazione, quantificando e valutando i problemi individuati. Questo strumento può essere usato per profilare le applicazioni Xamarin.iOS e Xamarin.Android da Visual lilStudio.

    Altre Ottimizzazioni

    Esistono inoltre altri accorgimenti per migliorare le prestazioni:

    • Gestione ritardata della creazione di oggetti
    • Implementazione asincrona di operazioni prolungate, in modo da gestire in thread separati tali operazioni
    • Ottimizzazione delle risorse/immagini utilizzate
    • Riduzione dei tempi di attivazione
    • Riduzione della comunicazione con servizi Web