Blog Stats

  • Blogs - 41
  • Posts - 4243
  • Articles - 185
  • Comments - 23657
  • Trackbacks - 950

Bloggers (posts, last update)

Powered By:
Powered by Subtext

Latest Posts

Xamarin.Forms, ListView ed effetti indesiderati

Ancora su Xamarin.Forms e la ListView, in binding a una collection. Su un progetto Android che sto seguendo mi è capitato di avere un data template XAML abbastanza articolato, ma in realtà con pochissimi elementi da visualizzare eppure con un problema di rendering.

Nel mio caso, i vari elementi della lista dovevano espandersi e richiudersi al touch. Il problema è che, una volta chiusi, rimanevano disegnati sul display alcuni elementi della visualizzazione "aperta". Sparivano solamente ad un successivo tocco sul display e anche forzare un refresh della UI non portava beneficio, con un pessimo effetto visivo per l'utente. Il codice non aveva difetti, ma con data template articolati Xamarin.Forms fa fatica.

Per varie ragioni non era possibile usare i controlli nativi, possibilità dell'ultima release di Xamarin.Forms, ma la soluzione è stata semplicemente quella di ricorrera a un custom renderer (che, per vie traverse, fa la stessa cosa). Per esempio, con questa classe si implementa un custom renderer che implementa alcune proprietà come la caching strategy:

using Xamarin.Forms.Platform.Android; 
using Xamarin.Forms;
using
 MyApp.Droid.Controls;

[assemblyExportRenderer(typeof(Xamarin.Forms.ListView), typeof(CustomListViewRenderer))]
namespace
 MyApp.Droid.Controls
{    
public
 class CustomListViewRenderer : ListViewRenderer
     {
         protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
         {
             base.OnElementChanged(e);
             Control.ScrollingCacheEnabled = true;
             Control.CacheColorHint = Android.Graphics.Color.Transparent;
             Control.Divider = null;
         }
     }
}

Nota bene che in questo caso sto usando il renderer direttamente sulla ListView di Xamarin.Forms, ciò implica che ogni qual volta utilizzerò la ListView, questa verrà sostituita dal renderer. Se non voglio questo, mi basta creare una classe che eredita da ListView.

Nel codice sopra si vede come ScrollingCacheEnabled consenta di impostare la caching strategy, CacheColorHint stabilisce come ciascun elemento della lista sia disegnato su un elemento che abbia sfondo del colore specificato e il separatore tra elementi (null in questo caso, cioè nessun separatore).

In questo modo, ricorrendo alla ListView nativa, abbiamo evitato problemi di disegno non automaticamente aggiornato.

Alessandro

posted @ 17/01/2017 01:00 by Alessandro Del Sole

Xamarin.Forms, ListView e ObjectDisposedException

Con particolare riguardo ai progetti Android, se in Xamarin.Forms avete una ListView in cui c'è un DataTemplate che visualizza immagini in binding, molto probabilmente otterrete una ObjectDisposedException durante lo scorrimento della lista, con conseguente crash dell'applicazione.

Si tratta di un problema noto, peraltro non ancora risolto. Si verifica perché Xamarin istanzia uno stream per ciascuna immagine, ne fa il dispose, ma poi non lo riapre al ritorno su un'immagine precedentemente visualizzata.

Dovreste risolvere, almeno nel mio caso ha funzionato, impostato la caching stategy della ListView per memorizzare in cache locale i contenuti. In questo modo:
    <ListView ItemsSource="{Binding}" CachingStrategy="RecycleElement" >
L'enumerazione CachingStrategy ha due valori, RecycleElement e RetainElement. Se non specificata, la seconda è il default. Con RecycleElement, gli elementi della ListView vengono messi in cache locale e questo evita il ricorso agli stream di cui sopra evitando anche l'eccezione e favorendo uno scroll normale.

Alessandro

posted @ 16/01/2017 01:00 by Alessandro Del Sole

Video: code generation in C# con Roslyn e VS Code su Linux, Mac e Windows

Ne avevo fatto un post qualche tempo fa, ma se vi piace guardare video, ho pubblicato su Channel9 una registrazione (in inglese) relativa alla tematica che dà l'oggetto al post.


In realtà si tratta di un primo video, in quanto lunedì prossimo ne uscirà un secondo che mostra come ottenere lo stesso risultato utilizzando Visual Studio for Mac e la sua strumentazione.

Alessandro

posted @ 12/01/2017 18:01 by Alessandro Del Sole

Visual Studio Code: installazione offline di estensioni

Come sapete, Visual Studio Code supporta l'installazione di estensioni che ora si trovano nel Visual Studio Marketplace, nell'apposita sezione.

L'installazione avviene direttamente dall'Extensions panel:



Il fatto è che bisogna essere necessariamente connessi ma si potrebbe avere necessità di installare estensioni al di fuori della rete oppure nonostante un proxy, oppure semplicemente per avere in locale le estensioni desiderate (l'update può avvenire successivamente).

In Visual Studio Code, le estensioni sono costituite da file .vsix che però non vanno confuse con l'omonimo formato supportato dal fratello maggiore Visual Studio. Dal Marketplace non è possibile scaricare i .vsix, quindi la documentazione stessa ci viene in aiuto e ci suggerisce come fare.

In pratica, bisogna costruire l'URL dell'estensione in questo modo:

https://nomepublisher.gallery.vsassets.io/_apis/public/gallery/publisher/nomepublisher/extension/nomeestensione/versione/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage

I placeholder evidenziati vanno sostituiti con l'id del publisher name, col nome dell'estensione e col numero di versione.
Nota bene: l'id del publisher è diverso dal nome che vediamo sulla pagina dell'estensione. Per esempio, supponiamo di voler scaricare l'estensione dei tool per Docker prodotta da Microsoft. L'URL è questo:

https://marketplace.visualstudio.com/items?itemName=PeterJausovec.vscode-docker

Nella pagina troverete che è prodotta da Microsoft, ma l'id del publisher name è quello evidenziato in marrone nell'URL, mentre in verde è evidenziato il nome del prodotto. Non c'è nulla di strano in questo, il publisher Id è, di fatto, personale.

Il numero di versione è visibile nella pagina Web. Perciò, ad oggi, l'URL per scaricare l'estensione offline diventa:

https://PeterJausovec.gallery.vsassets.io/_apis/public/gallery/publisher/PeterJausovec/extension/vscode-docker/0.0.11/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage

Qualunque URL scaricherà un file chiamato Microsoft.VisualStudio.Services.zip. Dovrete cambiare l'estensione, da .zip a .vsix e non avviare il file direttamente. Per installare un .vsix scaricato in questo modo, ricorriamo all'apposito comando nell'Extension panel:



A questo punto l'estensione verrà installata correttamente.

Alessandro

posted @ 12/01/2017 17:56 by Alessandro Del Sole

Visual Studio Code dietro proxy

Se dovesse capitarvi di utilizzare Visual Studio Code dietro proxy, si può intervenire sulle impostazioni in questo modo:

  1. File, Preferences, User Settings
  2. Si aprirà il file chiamato settings.json
  3. Localizziamo il gruppo di setting chiamato HTTP
  4. Nell'area destra dell'editor, quella in cui possiamo ridefinire le impostazioni, scriviamo il seguente markup:

{
    // The proxy setting to use. If not set will be taken from the http_proxy and https_proxy environment variables
    "http.proxy": "http://nomeutente:password@127.0.0.1:8080",

    // Whether the proxy server certificate should be verified against the list of supplied CAs.
    "http.proxyStrictSSL": false,

    // The value to send as the 'Proxy-Authorization' header for every network request.
    "http.proxyAuthorization": null
}

Dove:

  • nomeutente:password andrà sostituito con le credenziali dell'utente che ha accesso alla rete
  • 127.0.0.1:8080 andrà sostituito con l'IP del proxy e dal numero di porta

Salvate il file, chiudete e riavviate Visual Studio Code. In questo modo dovreste risolvere.


Alessandro

posted @ 11/01/2017 15:18 by Alessandro Del Sole

Xamarin.Forms: Roadmap per il 2017

E' stata pubblicata la roadmap per il 2017 relativa a Xamarin.Forms, disponibile a questo indirizzo. Forms sta indubbiamente crescendo molto, ci vuole il suo tempo, ma cresce e finalmente cominciano ad arrivare funzionalità e caratteristiche importanti e interessanti.

Non vedo l'ora di avere a disposizione il Picker col supporto al data-binding, cosa che più di tutti mi ha fatto "soffrire" lavorandoci

Le prossime versioni sono previste per febbraio e maggio, quindi non manca molto.

Alessandro

posted @ 05/01/2017 20:20 by Alessandro Del Sole

Roslyn e .NET Core: code generation col compilatore C# su Linux, Mac e Windows (con Visual Studio Code)

.NET Core è il runtime modulare, open source e cross-platform che consente di sviluppare applicazioni .NET con C# che girino su Linux, Mac e Windows.

Il fatto che si possa scrivere codice C# (e non solo eseguirlo) su altri sistemi operativi presuppone che il compilatore stesso sia ormai cross-platform. Roslyn, ovvero .NET Compiler Platform, tra le sue mille peculiarità porta i compilatori open source C# e VB con le loro rich code analysis APIs su Linux e Mac, oltre che già su Windows. In realtà, non tutte le librerie di Roslyn sono state ancora rese cross-platform, ma il compilatore e ciò che serve a fare code generation ed emit di assembly, sicuramente si.

L'obiettivo è quindi quello di creare un'applicazione .NET Core e chiamare il compilatore C# per fare code generation e analisi su Linux, Mac e Windows. Come IDE utilizzerò, ovviamente, Visual Studio Code. In un prossimo post vedremo come fare la stessa cosa con Visual Studio for Mac. Su Visual Studio 2017 è troppo facile :-)

Prerequisiti

1. Ubuntu 16.04, o fisico o in virtual machine. Io ho una VM su Hyper-V. Opzionale. Vanno bene anche altre "distro", ovviamente farete attenzione al punto 3.
2. Un Mac, opzionale
3. Un PC con Windows
4. .NET Core SDK con la command line interface. E' necessaria una delle versioni che supporti le solution MSBuild e non più project.json. Userò la v 1.0 preview 4. Da questa pagina potete selezionare l'installazione che fa al caso vostro, che ripeterete su tutti i sistemi su cui intendete provare.
5. Visual Studio Code e un minimo di dimestichezza con esso. Vi rimando agli articoli presenti su VB T&T.

Una raccomandazione: seguite le istruzioni almeno per i prerequisiti per Mac e Linux, poi per il download della versione affidatevi al link di cui sopra.

Windows

Su Windows è tutto più familiare. Con un command prompt all'interno di una cartella, digito la seguente sequenza per creare una directory e all'interno un'applicazione Console C# per .NET Core (dove crossroslyn sarà il nome esemplificativo del progetto):

> md crossroslyn
> cd crossroslyn
> dotnet new


Viene creato un progetto chiamato crossroslyn.csproj (in generale, il progetto prende il nome della cartella). NON chiudo il command prompt, e apro la cartella in Visual Studio Code e, all'interno del file .csproj, vado ad aggiungere i due seguenti riferimenti a pacchetti NuGet che mi servono:
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.0.0-rc2" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
Il primo è il pacchetto NuGet relativo al compilatore C# e alle API di code generation e code analysis. Il secondo servirà poi per fare reflection. Nel frattempo, Code vi chiederà il permesso di creare file che gli servono per gestire la cartella e vi suggerirà di scaricare le estensioni appropriate, se non presenti (es. C# e debugger .NET). Allo stato attuale, Visual Studio Code non va molto d'accordo con le preview di .NET Core e quindi il comando di restore dei pacchetti non va. Ergo, chiudiamo Code, torniamo al command prompt e digitiamo:

> dotnet restore

Questo eseguirà il corretto ripristino dei pacchetti. Riapriamo Code sulla cartella (non è casuale farvi chiudere e riaprire l'IDE). Su GitHub si parla del fatto che Code supporterà presto le solution MSBuild e questi salti non saranno più necessari.

Ora consideriamo il seguente, lungo listato relativo al file Program.cs. In sintesi, parto da una stringa che contiene una rappresentazione di una classe statica con un metodo che calcola l'area di un cerchio, dato un raggio hard-coded. Il codice viene trasformato in SyntaxTree dal metodo SyntaxFactory.ParseSyntaxTree affinché il nostro testo diventi codice sorgente comprensibile al compilatore. Viene chiamato un metodo PrintDiagnostics che si occupa di analizzare eventuali errori/warning all'interno del codice. Dopo il listato mi soffermerò su questo metodo e sulla parte di code generation.

using System;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis;
using System.Linq;
using System.Runtime.Loader;
using System.Reflection;
using System.IO;

class Program
{
    public static void Main(string[] args)
    {
        const string code = @"using System; 
using System.IO; 

namespace MathFunctions 

 public static class MathHelper
 { 
    public static void CalculateCircleArea() 
    { 
        double radius = 10;
        double result = radius * radius * System.Math.PI;
        Console.WriteLine(result.ToString()); 
    } 
  } 
}"
;

        // Ottiene un SyntaxTree
        var tree = SyntaxFactory.ParseSyntaxTree(code);
        Console.WriteLine(tree);
        PrintDiagnostics(tree);

        var compilation = CSharpCompilation.Create("mylib.dll").
            WithOptions(
                new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)).
            AddReferences(MetadataReference.CreateFromFile(typeof(object).
            GetTypeInfo().Assembly.Location)).
            AddSyntaxTrees(tree);

        var fileName = "mylib.dll";
        var path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
        compilation.Emit(path);

        // Non ancora totalmente supportato su Mono
        var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
        asm.GetType("MathHelper").GetMethod("CalculateCircleArea").Invoke(nullnew object[] { "" });
        Console.ReadLine();
    }

    private static void PrintDiagnostics(SyntaxTree tree)
    {
        var diagnostics = tree.GetDiagnostics();

        if (diagnostics.Any())
        {
            foreach (var diag in diagnostics)
            {
                Console.WriteLine($"{diag.GetMessage()} {diag.Location.GetLineSpan()}");
            }
        }
    }
}
PrintDiagnostics ottiene eventuali oggetti diagnostici dal SyntaxTree, come errori e warning. Se presenti, per ciascuno mostra il messaggio completo e la relativa posizione riga/colonna all'interno del nostro codice. Sono informazioni che ci da il compilatore. Notevole, no?

L'oggetto CSharpCompilation viene usato per creare un assembly con una serie di opzioni, come il fatto che si tratti di una dll, aggiungendo un riferimento al namespace che definisce object (System, quindi) e a cui viene aggiunto il SyntaxTree generato. Il metodo Emit di CSharpCompilation genera fisicamente la .dll. Per caricarla, usiamo l'oggetto AssemblyLoadContext, classe singleton, e il suo metodo LoadFromAssembly. Infine ricorriamo a classiche tecniche di reflection come GetType, GetMethod e Invoke per eseguire il codice recuperato dall'assembly.

Se ora avviamo l'applicazione, nella finestra di output vedremo dapprima il testo del codice tramutato in SyntaxTree, da ultimo il calcolo matematico richiesto, ottenuto invocando un oggetto generato a partire dalla compilazione del nostro codice/testo C#, sfruttando Roslyn:




Con un breakpoint potrete anche facilmente esaminare il contenuto delle variabili locali, tra cui quella che contiene il SyntaxTree e tutte le relative informazioni. Ora proviamo a vedere che succede se dal codice/testo tolgo un ; e una }:



Due cose interessanti: la prima è che il compilatore riporta i suoi messaggi diagnostici e la posizione in cui l'issue si verifica, come potete vedere dalla figura sopra. La seconda è che, nonostante nel codice ci siano degli errori, il compilatore ha comunque generato un syntax tree. Non verrà di fatto compilato, ma questo ci da l'idea di come il compilatore rappresenti con estrema fedeltà il sorgente/testo.

Nota bene, anzi benissimo: quella vista è la più essenziale e semplice dimostrazione di code generation che si possa fare. Con Roslyn possiamo ottenere SyntaxNode di un certo tipo, elaborarli, riscriverli, iniettare refactoring, ecc. ecc. Ok? Ok!

Mac OS

Il bello di .NET Core è che si comporta analogamente su tutti i sistemi. Su Mac OS, creata una cartella, la apriamo nel Terminal. Digitiamo semplicemente:

> dotnet new

Apriamo Visual Studio Code e ripetiamo gli stessi passaggi visti per Windows per l'aggiunta dei due pacchetti NuGet, quindi torniamo nel terminal e digitiamo:

> dotnet restore

Apriamo la cartella con VS Code. E' probabile che, oltre a suggerirvi le estensioni richieste tra cui il debugger Mono, la prima volta Code debba scaricarsi le librerie di Mono. Ad ambiente pronto, riscriviamo esattamente lo stesso codice visto prima e avviamo il debug esattamente allo stesso modo:




In questo caso si vede sia il syntax tree che i diagnostics. Signore e signori, avete appena usato il compilatore C# su Mac!

Linux/Ubuntu

Provate a indovinare? Esatto... stessi passaggi. Tramite il File Manager di Ubuntu creiamo una cartella, la apriamo col Terminal e digitiamo:

> dotnet new

Apriamo la cartella con Visual Studio Code, aggiungiamo i due pacchetti NuGet, torniamo al Terminal e digitiamo:

> dotnet restore

Stesso codice di cui sopra, né più né meno. Eseguiamo, et voilà:



In questo caso vediamo il risultato del calcolo ottenuto chiamando il codice compilato nella .dll. Signore e signori, avete appena invocato il compilatore C# su Ubuntu!

Conclusioni

Occhio che come dicevo all'inizio alcune librerie di Roslyn non sono state ancora portate su Mac e Linux, ma la strada è buona. Potete comunque invocare il compilatore, generare e compilare codice, eseguirlo e caricare assembly. Oltre alle operazioni di code analysis e refactoring da implementare. 

Alessandro

posted @ 29/12/2016 00:26 by Alessandro Del Sole

Creare un'applicazione ASP.NET Core su Ubuntu con Visual Studio Code

Ubuntu è un popolare sistema operativo open source, noto per avere come genitore l'ancor più noto Linux. Uno dei più grandi investimenti fatti da Microsoft negli ultimi anni è .NET Core, che consente di usare C# per scrivere applicazioni cross-platform e che quindi funzionino anche su Mac e Linux, oltre che su Windows. Tra le distribuzioni più importanti, oltre a Linux di Red Hat, ci sono appunto Ubuntu, Debian e Fedora.

.NET Core consiste certamente in un runtime, ma anche in una Command Line Interface per l'esecuzione di strumenti a riga di comando. Non dispone quindi di un IDE, ma il problema è presto risolto grazie a Visual Studio Code, anche lui cross-platform e assolutamente supportato su Ubuntu e le altre distribuzioni.

L'obiettivo di questo post è spiegare i passaggi introduttivi per sviluppare un'applicazione ASP.NET Core su Ubuntu con Visual Studio Code, con gli opportuni flash per approfondimenti.

Preparazione del sistema

La versione più indicata per lavorare con .NET Core e VS Code su Ubuntu è la 16.04, sebbene ci sia supporto per la 16.10 e la 14.04. La 16.04 è una long time support ed è quella attualmente più stabile. Se avete Ubuntu installato su un pc o su una partizione, siete a posto. Diversamente è possibile creare una virtual machine scaricando la ISO del sistema operativo da questo indirizzo, poi, se siete su Windows, potete utilizzare Hyper-V Manager per creare la nuova VM. L'installazione del sistema è piuttosto semplice e assolutamente guidata.
Al termine, la prima cosa da fare è installare .NET Core, di cui ci sono varie preview versions. Il mio consiglio è di installare la 1.0.3, che ripristina le solution e i file .csproj invece di project.json. Per farlo, serve un command prompt che in Ubuntu si chiama Terminal. Possiamo facilmente trovarlo utilizzando l'icona Search your computer in alto a sinistra e digitando "terminal".

Quando compare il command prompt, digitate in sequenza questi comandi:

> sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main" > /etc/apt/sources.list.d/dotnetdev.list'

> sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893

> sudo apt-get update

> sudo apt-get install dotnet-dev-1.0.0-preview3-004056

Sudo consente di installare applicazioni anche da parte di utenti che non abbiano privilegi amministrativi. Nell'ultima riga, chiaramente potremo modificare la versione di .NET Core che ci interessa. Qui viene installato il runtime ma anche l'SDK, che include la command line interface.

Ultima cosa da fare è installare Visual Studio Code. Ci sono varie versioni per ambienti Linux, su Ubuntu dovete scegliere quella Ubuntu/Debian con estensione .deb. Download, doppio click, installazione. Semplice.

Creazione dell'applicazione

Con .NET Core, l'applicazione viene creata dalla riga di comando all'interno di una cartella. Se già conoscete Ubuntu, potete creare una cartella da riga di comando nel Terminal. Se non avete dimestichezza, una buona idea può essere ricorrere allo strumento Files, spostarvi all'interno di una sottocartella di vostra scelta, tasto destro -> New Folder:



Chiamo la cartella SampleApp, poi tasto destro sul suo nome e Open in Terminal. Digito due semplici righe:

> dotnet new -t web

> dotnet restore


La prima crea un'applicazione .NET Core di tipo (-t) Web. Se ometto il tipo, viene creata un'app Console. La seconda ripristina i pacchetti NuGet necessari. Ecco il risultato:



Infine, possiamo aprire il progetto appena creato con Visual Studio Code digitando, direttamente da command prompt:

> code .

Visual Studio Code si accorgerà che il progetto è in C#, quindi, se non già disponibile, vi suggerirà di scaricare e installare la relativa estensione. Notate, inoltre, che Visual Studio Code scaricherà i runtime di Mono e OmniSharp e il debugger di .NET Core.
Nella barra Explorer vedrete la classica rappresentazione di un progetto ASP.NET MVC:



Se ora andate nella scheda di debug cliccando sull'icona col simbolo della formica-bug, dopo esservi accertati che in alto sia selezionata la configurazione .NET Core Launch (web), premete F5. Su Ubuntu il browser di default è Firefox, quindi vedrete l'applicazione Web in esecuzione in questo browser (se non ne avete altri, ben inteso). Quello che voglio evidenziare è come però abbiate a disposizione la consueta, potente strumentazione di debug che Visual Studio Code mette a disposizione:



La cosa quindi estremamente interessante è che avete un'applicazione Web scritta in C#, basata su ASP.NET Core e il pattern MVC su un sistema Ubuntu, cosa impensabile fino a un paio d'anni fa.

Ulteriori implementazioni

Chiaramente questa è solo l'introduzione alla creazione del progetto. Per proseguire, sicuramente la documentazione. Poi potrebbe interessarvi questo mio articolo per MSDN Magazine che illustra come implementare l'accesso ai dati basato su Entity Framework Core. Infine, la possibilità di usare Yeoman per generare controller MVC e Web API per ASP.NET Core.

E indovinate? funziona anche su Mac

Alessandro

posted @ 27/12/2016 01:00 by Alessandro Del Sole

Xamarin.Forms: considerazioni sulle performance e come migliorarle

Per lavoro mi sto occupando di un'app scritta con Xamarin.Forms in cui le performance non sono un optional e quindi ho voluto raccogliere una serie di considerazioni e analisi fatte, con alcune piccole premesse. Di seguito alle premesse, farò le considerazioni del caso. Non inserisco codice né esempi, perché voglio che leggiate e non vi distraiate . Troverete tutto approfondito nella documentazione, linkata successivamente.

Premessa 1: E' davvero sempre colpa di Xamarin.Forms se ho problemi di performance?

Risposta breve: no. Ricordiamoci che Xamarin genera app native, ovviamente si porta dietro le proprie librerie e le sue modalità di compilazione e questo può causare degli scarti in eccesso rispetto a tool, linguaggi e framework nativi. Sono anche vere due cose: il sistema operativo fa differenza (vedi Premessa 2) e molta importanza è rivestita da come noi scriviamo codice e da come noi progettiamo la UI. Prendiamoci le nostre responsabilità, insomma, e non diamo tutta la colpa a Xamarin.

Premessa 2: Sistemi operativi diversi, comportamenti diversi

Non sto qui a giudicare quale OS sia il migliore. Ognuno avrà i propri benchmark e le proprie idee. Certo è che iOS, Android e Windows 10 Mobile sono diversi tra loro e hanno modalità diverse di gestire sistema e hardware. La stessa operazione su tre sistemi diversi avrà performance e tempi di esecuzione diversi. Teniamolo in debita considerazione e proviamo, ove possibile, a replicare la stessa operazione su tutti e tre.

Premessa 3: Lo sviluppatore (ancora) conta

Lo sviluppatore è un creativo e quindi può trovare modi diversi di fare la stessa operazione. A volte modi diversi hanno impatti diversi. Questo vale anche per la user interface, con tanti piccoli accorgimenti. Altre volte si scrive codice ridondante, altre volte semplicemente non consideriamo il fatto che esiste documentazione al riguardo. Per esempio, questa pagina dedicata all'ottimizzazione delle performance di Xamarin.Forms.

Premessa 4: Questa non è la Bibbia!

Questo post contiene una serie di consigli e di considerazioni, ciò non vuol dire che a) sia la Bibbia (per quello c'è la documentazione) e che b) tutto quanto descritto debba sempre trovare applicazione. E' chiaro che posso avere esigenza di soddisfare un requisito del cliente e che, per farlo, debba accettare dei compromessi.

Oggetti, memoria e Garbage Collection

Se la nostra app istanzia tantissimi oggetti, c'è un impatto sulla memoria. Bisogna capire se è un comportamento atteso e in modo fluido oppure no, nel qual caso possiamo sfruttare un nuovo tool chiamato Xamarin Profiler che, tra le varie cose, analizza l'utilizzo della memoria e degli oggetti istanziati. Nel caso di Android, il debugger di Mono ci dice anche quando ci sono Garbage Collection scatenate dal sistema. Frequenti GC sono sintomo di problemi. GC scatenate dal codice tramite System.GC.Collect() causano ovvi performance overhead, quindi sono da evitare il più possibile (o al massimo ricorrere alle c.d. minor GC). In sintesi, l'intervento del Garbage Collector ha un peso: se è frequente, ha un peso maggiore e se lo invoco da codice, magari ripetutamente, ha un peso.

Multi-threading

I device moderni non hanno alcun problema col multi-threading, anzi. E' buona pratica demandare ad un thread secondario (ad esempio con Task.Run) l'esecuzione di operazioni intensive per la CPU, in modo che il thread della UI rimanga libero. Al riguardo, prima considerazione: async/await è preferibile, vedi paragrafo successivo. Seconda considerazione: l'operazione che demando al thread secondario, è davvero intensiva per la CPU? Terza e più importante considerazione: mentre eseguire un thread secondario non ha un particolare impatto, lo avrà invece se la cosa è ciclica. Mi spiego meglio: se ad esempio genero tanti elementi a runtime tramite un loop e per ognuno faccio fare delle cose in un thread secondario che poi viene chiuso per tornare al thread principale, ho un certo impatto negativo, poiché il sistema deve switchare di continuo tra thread. La domanda a cui devo rispondere è quindi: sto davvero eseguendo operazioni CPU-intensive o posso fare in altro modo?

Il pattern Async/Await

Credo che ormai lo conosciamo tutti. Il suo utilizzo nelle app è cruciale. Mi permette di eseguire operazioni potenzialmente long-running in modalità asincrona, senza bloccare la UI e senza dover ricorrere a thread secondari. Quindi, riagganciandomi ai thread: l'operazione che demando al thread secondario è realmente CPU-intensive o solamente long-running?
Altra questione: metodi sincroni che eseguono operazioni long-running all'interno del thread della UI, la bloccano. Se dichiaro un metodo asincrono ma al suo interno non c'è nessun await o nessun'altra operazione asincrona invocata, verrà considerato sincrono e quindi non ho risolto nulla . Attenzione quindi agli operatori.

L'interfaccia utente: i giusti Layout

La progettazione dell'interfaccia ha un impatto importantissimo sulle performance. Spesso dimentichiamo che ogni controllo o panel che scriviamo nello XAML corrisponde non solo all'istanza di una classe in memoria, ma anche ad un elemento visivo che deve essere "renderizzato" sul display e che quindi ha un costo per la CPU. Per esempio, la documentazione ci riferisce che il RelativeLayout, rispetto ad altri, causa problemi di performance e quindi è meglio non usarlo. Ovviamente se esiste c'è un perché, quindi l'utilizzo dovrebbe essere limitato a tutte quelle situazioni in cui lo stesso risultato non si possa ottenere con altri panel. Se però in una pagina ho tanti RelativeLayout e magari ho un data template che può contenerne n, posso avere un problema di performance nel disegno. Stesso discorso vale, ad esempio, per panel nidificati tra loro quando non serve. Se per esempio devo affiancare in verticale una serie di controlli, posso farlo attraverso un unico StackLayout. Non è detto che mi servano vari StackLayout all'interno di quello principale solo per raggruppare controlli. Ogni panel, infatti, dev'essere istanziato e renderizzato: ha un costo. Se non posso farne a meno, ok. Se però posso farne a meno, è bene che faccia attenzione.

L'interfaccia utente: popolamento a runtime

Spesso abbiamo necessità di generare elementi dell'interfaccia a runtime, magari tramite un foreach. Tutti questi elementi vanno a finire poi in un panel (Grid, StackLayout, ecc.) che li conterrà. Se questo contenitore viene dichiarato nello XAML e popolato a runtime, il sistema continuerà a modificarne lo stato ad ogni nuovo elemento aggiunto e quindi ci sarà un peggioramento delle performance (ovviamente se gli elementi sono tanti). Se questo contenitore viene invece generato da codice, popolato e solo al termine del popolamento assegnato all'interfaccia, il sistema lo disegnerà una sola volta. Chiaro, dovrà disegnare anche tutti gli elementi che contiene, ma il suo stato non viene modificato di continuo.

L'interfaccia utente: le immagini

Se un'app fa uso di immagini, la cosa migliore è che ne preveda diverse risoluzioni a seconda del tipo di device su cui girerà. Ovviamente, almeno per quanto riguarda le immagini statiche tipo loghi e simili, eviteremo immagini la cui dimensione è di vari megabytes. Sempre a livello di immagini statiche, Xamarin lavora meglio se le dichiariamo direttamente nello XAML.

Tap gesture ed eventi

E' abbastanza normale aggiungere delle cosiddette tap gesture a controlli che, by design, non supportino eventi tipo Clicked, Tapped o simili. E' il tipico caso delle Label. Se ho una pagina che, alla sua inizializzazione, aggiunge delle tap gesture e che viene visualizzata varie volte nel ciclo di vita dell'app, è bene che nel suo OnDisappearing ne faccia anche il remove. Stesso dicorso vale per i gestori di evento. Se mi metto in ascolto di un evento (operatore += o lambda) è bene che rimuova l'ascolto quando non serve più (operatore -=).

Performance percepite: OnAppearing

Non sempre, ma in alcuni casi può essere utile fare l'override del metodo OnAppearing che ha ogni pagina per fare delle elaborazioni. Questo metodo viene chiamato appena prima che la pagina sia visibile, dopo l'invocazione del costruttore, mentre quest'ultimo viene chiamato quando la pagina dev'essere inizializzata.

Compilazione XAML

Esiste la possibilità di compilare lo XAML direttamente in Intermediate Language, in modo da ridurre le dimensioni dell'assembly finale e da rimuovere parte del tempo necessario al caricamento e all'istanza di elementi. Inoltre, questa opzione verifica la validità dello XAML a compile time, al contrario di quanto avviene per default (ossia a runtime). Non è abilitata di default, bisogna farlo da codice a livello di classe o di assembly.

Elenchi, virtualizzazione e caching

Parliamo di elenchi di dati e di ListView, tipicamente in data-binding a una collection e quindi con un data template. Se quest'ultimo è composto da un numero essenziale di elementi e senza troppi orpelli, è molto meglio. Inoltre la ListView consente di abilitare un'opzione di caching degli elenchi, in modo da velocizzarne il ri-caricamento.

Uso di controlli nativi

Dalla versione 2.2, Xamarin.Forms consente l'utilizzo di controlli nativi invece dei suoi. Sicuramente più performante.

Conclusioni

Come anticipato, queste sono considerazioni fatte sulla pratica e soprattutto con i giusti spunti della documentazione ufficiale. Tutto va sempre poi collocato nel giusto contesto. Se avete altri consigli o se avete obbiezioni, lasciate pure un commento.

Alessandro

posted @ 19/12/2016 01:00 by Alessandro Del Sole

WPC 2016: Grazie!

Complice un periodo abbastanza intenso, arrivo con qualche giorno di ritardo a scrivere due righe riassuntive sulla mia esperienza alla tre-giorni di WPC 2016, la conferenza italiana più importante dell'anno organizzata da Overnet Education in collaborazione con Microsoft Italia e tenutasi in quel di Assago nei giorni 29, 30 novembre e 1 dicembre.

Come tutti gli anni è stata una belissima conferenza, con tante sessioni interessanti ma anche un'occasione per ritrovare vecchi amici con cui si condivide la passione per le tecnologie Microsoft.

Quest'anno, il mio 8° da speaker, ho tenuto una sessione introduttiva a Visual Studio Code, strumento di sviluppo cross-platform per scrivere codice su Linux, Mac e Windows. Devo dire che mi ha fatto molto piacere vedere un buon numero di persone interessate a scenari che vadano oltre Windows e, quindi, oltre la quotidianità per la maggior parte dei presenti. Mi sono divertito, come tutti gli anni, e spero che la sessione sia piaciuta.

Se avete partecipato, non dimenticate di scaricare le slide delle varie sessioni (la mia era la WPC034).

Un doveroso ringraziamento a Overnet e Microsoft per l'organizzazione e uno in particolare a Barbara Palumbo che, come ogni anno, mette cuore e impegno per la perfetta riuscita dell'evento.

Alessandro

posted @ 09/12/2016 17:47 by Alessandro Del Sole