Una guida eccessivamente approfondita alle librerie Android sottoutilizzate

Pubblicato: 2022-03-11

Qualsiasi sviluppatore esperto ti dirà che il codice migliore non è il codice che ha scritto. È il codice che hanno preso dal lavoro di qualcun altro.

Sì, noi sviluppatori siamo innovativi risolutori di problemi, ma molti dei problemi che incontriamo sono già stati risolti e i rimedi sono inseriti in librerie a disposizione di chiunque. Perché reinventare la ruota quando le ruote libere sono ovunque?

Android non fa eccezione. La fonte definitiva per il riutilizzo del codice è lo stesso Android SDK, che viene fornito con ottimi costrutti e servizi che faranno molto del tuo lavoro per te.

Ma dove l'SDK è a corto, la comunità Android ha creato alcune librerie top di gamma che possono farti risparmiare tonnellate di lavoro di codifica, sostituendolo con implementazioni altamente ottimizzate, controllate e testate. Non sto parlando delle librerie ovvie: Android Support Library, Android Design Support Library, Gson. Mi riferisco a strumenti che potresti non conoscere. E anche se lo fai, probabilmente non li stai ancora usando.

Una delle principali differenze tra uno sviluppatore standard e uno sviluppatore master è l'uso corretto di librerie di terze parti. Uno sviluppatore esperto eseguirà all'incirca la stessa attività tre volte più velocemente di un principiante e di solito con un codice più breve. Gran parte di questo deriva dal sapere quali librerie di terze parti utilizzare e come incorporarle correttamente all'interno del tuo progetto.

Ho sviluppato, guidato e guidato team Android per anni e ho studiato e utilizzato dozzine di strumenti e librerie esterne. (Sono stato anche conosciuto per leggere il loro codice di implementazione e discutere i loro interni con lo sviluppatore.) Molti sono stati molto efficaci nell'aiutarmi a portare a termine il lavoro, ma la verità è che la maggior parte non lo era.

Ecco perché ho messo insieme questa guida. Affidati alla mia esperienza, così come a quella di altri sviluppatori di dispositivi mobili, per assicurarti di utilizzare le migliori librerie. Ne ho scelti sette. Sospetto che presto diventeranno anche alcuni dei tuoi preferiti.

Selezione della libreria Android giusta

Quando scelgo una libreria, cerco quattro caratteristiche chiave:

  • Fornisce una soluzione coerente e di alta qualità per un problema reale e non banale.
  • Utilizza un'API il più semplice possibile.
  • Non forza alcun cambiamento nella mia architettura generale.
  • Ha una vasta base di utenti e, preferibilmente, una comunità di sviluppatori attiva.

Le prime tre caratteristiche sono rompicapo. Se non sono presenti, vado avanti o inizio la codifica manuale.

Librerie Android

Le librerie che tratterò di seguito superano tutti e quattro i test. Risolvono anche alcuni degli aspetti più impegnativi dello sviluppo mobile.

  • Due librerie per l'inserimento delle dipendenze, associazione layout-to-Java, oggetti fittizi.
  • Modello di messaggistica pub/sub in-app.
  • Livello di comunicazione HTTP sicuro, efficiente e autoripristinante.
  • Manipolazione delle immagini: download, memorizzazione nella cache, ridimensionamento e caricamento nella RAM.
  • Streaming video in tempo reale.
  • Rilevamento perdite di memoria.

ButterKnife: l'ultimo strumento di iniezione delle dipendenze

Questa è la libreria di iniezione delle dipendenze definitiva per Android. Semplice, robusto, super veloce (nessuna riflessione!) E in grado di eliminare gran parte del codice standard della tua app.

Alcuni sosterrebbero che ButterKnife è ciò che il layout di Android per la mappatura Java avrebbe dovuto essere in primo luogo.

Iniezione di dipendenza da Butterknife Android

È scomparsa la necessità di associare direttamente ciascuna delle tue viste tramite una chiamata a findViewById() ; invece, c'è una vista annotata che ti dà accesso diretto al codice. ButterKnife elimina anche la necessità di eventi dell'interfaccia utente standard come onClick , onTouch e così via e li sostituisce con codice iniettato automaticamente.

Ma basta chiacchiere, vediamo il codice.

Visualizza campo vincolante:

 class MyButterKnifeActivity extends Activity { @BindView(R.id.name) TextView name; @BindView(R.id.address) TextView address; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // MUST BE CALLED BEFORE ACCESSING UI FIELDS name.setText(“etc etc”); } }

Associazione delle risorse:

 class ExampleActivity extends Activity { @BindString(R.string.username) String username; @BindDrawable(R.drawable.graphic) Drawable graphic; @BindColor(R.color.bg_color) int bgColor; @BindDimen(R.dimen.lower_padding) Float lowerPadding; // and no need for getResources().getString()/getDrawable/getColor() }

Associazione evento dell'interfaccia utente:

 @OnClick(R.id.my_button) public void clickHandler(View view) { // onClick logic goes here }

AndroidAnnotations: portare l'iniezione delle dipendenze al livello successivo

Un secondo vicino a ButterKnife quando si tratta di iniezione delle dipendenze, AndroidAnnotations utilizza un approccio leggermente diverso: classi generate automaticamente, che, una volta apprese, sono estremamente semplici. Ancora più utile è che ti consente l'iniezione di dipendenze "basata sul nome". Ad esempio, @ViewById ListView myUserList; indica alla libreria di assegnare a questo campo un layoutListView con lo stesso nome.

Anche AndroidAnnotations è velocissimo, ma lo raggiunge in un modo leggermente diverso rispetto a ButterKnife. Invece dell'iniezione di dipendenza dell'associazione di runtime, AndroidAnnotations crea una duplicazione del tempo di compilazione di tutte le attività interessate e inserisce la logica di connessione in esse, consentendoti così di ottenere le stesse prestazioni che otterresti con la logica codificata a mano.

Ma le capacità di iniezione di AndroidAnnotations vanno anche oltre. Puoi inserire sia lo stato che il layout in un'attività.

Implementazione di AndroidAnnotations:

 @NoTitle @Fullscreen @EActivity(R.layout.my_layout) public class MyActivity extends Activity { @ViewById ListView customerList; // auto-binded to R.id.customerList @App MyApplication app; // auto-binded to app object @AminationRes Animation fadeoutAnimation; @UiThread void updateUI() { // main thread action } }

L'ultima annotazione richiede un po' più di spiegazione: un'attività comune per un'app Android multi-thread è il passaggio dai thread in background (o di lavoro) al thread in avanti (o principale o dell'interfaccia utente), che è l'unico che consente l'accesso ai componenti dell'interfaccia utente . Questo compito, sebbene non complesso, è spesso richiesto e comporta una codifica disordinata:

 new Handler(Looper.getMainLooper()).post(new Runnable() { logic goes here } ); // NO ANNOTATIONS

In AndroidAnnotations, tutto ciò che devi fare è annotare la tua funzione con @UiThread e ora è garantito che venga sempre eseguita:

 @UiThread void updateUI() {..} // WITH ANNOTATIONS

Tieni presente che questa annotazione si applica alle classi di componenti Android standard (attività, servizi e così via). Ma cosa succede quando voglio anche annotare le mie classi?

Qui, AndroidAnnotations propone un nuovo concetto, quello di EBean . Tutto quello che devi fare è contrassegnare la tua classe come tale usando @EBean , e sei a posto:

 @EBean public class MyNonComponentClass { @SystemService NotificationManager notifManager; @Bean MyOtherClass dependency; @UiThread void updateUI() { // main thread work goes here } }

EventBus: comunicazione tra componenti semplificata

La libreria EventBus trasforma un problema che perseguita gli sviluppatori Android da anni in una passeggiata nel parco. La comunicazione tra componenti non è mai stata così semplice: utilizza un semplice modello pub/sub per comunicare tra due parti qualsiasi del tuo sistema.

Animazione EventBus

L'uso del bus degli eventi risulterà in un codice più robusto, poiché ti costringe a disaccoppiare i tuoi componenti l'uno dall'altro.

Il tuo servizio di polling in background non ha più bisogno di essere a conoscenza dei tuoi frammenti per alimentarli con eventi di modifica.

L'utilizzo di EventBus è semplice.

un. Crea classi di eventi. Lavorare con i POJO qui è il migliore:

 class NewUserEvent { String fullname; String address; String role; // add getters and setters }

B. Crea metodi di gestione degli eventi nella classe, qualsiasi classe a cui desideri iscriverti per questi eventi:

 class MySubscriber { @Subscribe public void newUserHandler(NewUserEvent event) { // handle NewUserEvent } @Subscribe public void newUserHandler(AnotherEvent event) { // handle AnotherEvent } }

Ma ehi, qualsiasi sviluppatore Android con poca esperienza si fermerebbe e chiederebbe a questo punto: qual è il modello di threading di questi gestori? E posso forzare un gestore a uscire dal thread principale se, ad esempio, implica l'accesso al componente dell'interfaccia utente? Buona domanda…

Per impostazione predefinita, tutti i metodi del gestore vengono eseguiti su un thread di lavoro prelevato da un pool di thread allocato e gestito dallo stesso EventBus. Se hai bisogno di un metodo di gestione da eseguire sul thread principale, espandi le annotazioni di sottoscrizione come segue:

 @Subscribe(threadMode = ThreadMode.MAIN) public void runOnMainThreadHandler(AnotherEvent event) { … }

Avvertimento: non abusare di questa funzione! Le operazioni di lunga durata non dovrebbero mai essere eseguite sul thread principale e, anche con operazioni rapide, fai attenzione. Travolgere il thread principale è il modo più sicuro per rendere la tua app lenta, nervosa e sostanzialmente meno divertente per i tuoi utenti.

C. Gestisci il ciclo di vita della registrazione EventBus della tua classe di abbonato, ovvero quando si connette e quando si disconnette dal bus? Un flusso di registrazione ragionevole per un'attività sarebbe:

 class MySubscriberActivity extends Activity { @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); // START RECEIVING EVENTS HERE } @Override public void onStop() { EventBus.getDefault().unregister(this); // NO MORE EVENTS super.onStop(); } }

Quanto sopra è, ovviamente, solo un esempio. Puoi eseguire la (dis)registrazione ovunque tu scelga.

D. E infine, attiva effettivamente un evento:

 EventBus.getDefault().post(new MyEvent(“I'm here”));

C'è molto altro da sapere sull'utilizzo di EventBus: eventi multicast (il comportamento predefinito), utilizzo di eventi persistenti, thread di consegna, priorità e altro. Ma quanto sopra è sufficiente per iniziare con questa tecnologia semplice, ma potente.

OkHttp: HttpClient di Android su steroidi

Questo è il modo in cui avrebbe dovuto essere scritto HttpClient di Android. Molto semplice, molto intelligente. La libreria OkHttp si occupa internamente del ciclo di tentativi, della compressione automatica del payload, del supporto HTTP/2, del pool di connessioni e della memorizzazione nella cache delle risposte in modo da evitare accessi alla rete non necessari.

Animazione della libreria OkHttp

L'utilizzo di OkHttp è un gioco da ragazzi.

Http POST:

 OkHttpClient client = new OkHttpClient(); MediaType JSON = MediaType.parse("application/json; charset=utf-8"); RequestBody body = RequestBody.create(JSON, json_str); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); return response.body().string();

HTTP OTTIENI:

 OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(urls[0]) .build(); Response responses = client.newCall(request).execute(); String jsonData = responses.body().string();

OkHttp supporta anche funzioni utili come la rete asincrona, la query di route di reindirizzamento delle richieste, la query della cache locale e altro ancora. Sentiti libero di usarli dove necessario. La maggior parte degli sviluppatori utilizza OkHttp come sostituto più intelligente del client HTTP predefinito di Android, HttpURLConnection. In effetti, l'intero progetto è iniziato come fork privato per HttpURLConnection.

Adoro la sua robustezza: si aggiunge immediatamente al tuo livello di rete.

Con poche righe di codice, OkHttp fa sì che la tua app si comporti come se avessi passato notti a eseguire il debug e ottimizzare il livello di rete.

Picasso: C'è una buona ragione per cui anche Google lo usa!

Picasso è il modo più semplice e robusto per gestire il download, la memorizzazione nella cache, il ridimensionamento e il ritaglio delle immagini.

Questa dichiarazione:

 Picasso.with(context).load(url).resize(50,50).centerCrop().into(imageView)

Farò questo per te:

  • Connetti a un URL remoto.
  • Scarica un'immagine.
  • Archivialo in una cache LRU locale che gestirà anche per te.
  • Ridimensionare l'immagine originale prima di caricarla in memoria.
  • Esegui tutto quanto sopra su un pool di thread gestito da Picasso.
  • Usa l'immagine ridimensionata per popolare il tuo imageView.
  • Prima di qualsiasi esecuzione futura, controlla la cache locale per assicurarti che sia davvero necessario un round trip di rete.

Costruire l'insieme di attività di cui sopra richiederebbe molte ore di lavoro, anche per uno sviluppatore esperto. E questo presuppone che tu abbia ricordato tutto. E se avessi dimenticato, per esempio, la parte di ridimensionamento?

Animazione della libreria Android Picasso

Bene, su un dispositivo Android medio, un'app non riceve più di 50-60 Megabyte di RAM e il fattore pixel per byte per la maggior parte dei dispositivi Android è 4. Ciò significa tentare di caricare un'immagine da 13 megapixel dalla scheda SD richiederebbe 52 Megabyte di RAM. In altre parole, la tua app si arresterebbe immediatamente.

Questo è solo un esempio della forza di Picasso. Una delle prime cose che faccio durante l'ottimizzazione/il debug di un progetto legacy ad alta intensità di media è passare tutto il caricamento delle immagini su Picasso. Saresti sorpreso dell'impatto che questo semplice passaggio ha sulla qualità dell'app.

Una delle testimonianze più forti della potenza di questa libreria: molti dei campioni di codice Android di Google degli ultimi due anni utilizzano Picasso per il caricamento delle immagini.

ActiveAndroid: ORM senza spese generali di prestazione

ORM, abbreviazione di mappatura relazionale a oggetti, divenne popolare ai tempi di J2EE. Ti consente di archiviare i tuoi POJO e recuperarli da un database senza doverli convertire in campi separati.

ORM Android attivo

È utile? Molto, in quanto ti consente di scrivere gran parte della tua app senza codificare alcuna istruzione SQL.

È anche molto efficiente. In passato, le piattaforme ORM si basavano massicciamente sulla riflessione ed erano famose per essere lente. Le piattaforme moderne, incluso ActiveAndroid, sono molto più veloci e, per la maggior parte dei requisiti pratici, non subiranno un sovraccarico delle prestazioni rispetto alla codifica SQL grezza.

Nessuna istruzione SQL codificata a mano, nessun sovraccarico di prestazioni!

Utilizzo:

un. Inizializza nell'oggetto application estendendo una classe Application personalizzata:

 public class MyApplication extends extends com.activeandroid.app.Application { … }

B. Crea POJO, derivato per una classe modello, con classi per ciascuno dei record che prevedi di archiviare nel database. Ciascuno di questi POJO può risiedere nella propria tabella. Le annotazioni devono essere utilizzate per specificare il nome dei campi DB per ciascun membro archiviato:

 @Table(name = "Categories") public class UserDetails extends Model { @Column(name = "Name") public String name; @Column(name = "Address") public String address; @Column(name = "Age") public int age; }

Se desideri impostare un indice per un membro, utilizza la seguente annotazione:

 @Column(name = "ID", index = true) public String userID;

C. Per evitare che la libreria esegua l'iterazione per tutto il tempo di avvio più classico, che è il comportamento predefinito, si consiglia vivamente di specificare tutte le classi del modello nella seguente sezione manifest:

 <meta-data android:name="AA_MODELS" android:value=“com.myapp.MyModelA, com.myapp.MyModelB" />

Nota: le classi modello non visualizzate in questo elenco non verranno riconosciute da ActiveAndroid.

D. Scrivi al database:

 UserDetails usr = new UserDetails(); usr.save(); // RUNS ON A BACKGROUND THREAD

Se sono necessarie più scritture, un modo più efficiente sarebbe quello di raggrupparle in un'unica transazione:

 ActiveAndroid.beginTransaction(); try { for (UserDetails u: userList) item.save(); ActiveAndroid.setTransactionSuccessful(); } finally { ActiveAndroid.endTransaction(); }

e. Leggi POJO dal database:

 new Select() .from(UserDetails.class) .where("name = ?", usr.getName()) .orderBy("Age") .executeSingle();

ORM era uno strumento indispensabile durante i miei giorni come sviluppatore lato server. Ha avuto un ingresso un po' tardivo nel dominio Android. Ma, finalmente, eccolo qui: la programmazione di database è così semplice. Divertirsi.

LibStreaming: streaming video indolore

Lo streaming video in tempo reale era un problema importante a causa delle API non documentate, delle differenze di versione tra SDK, dell'utilizzo della riflessione e altro ancora.

Animazione della libreria libStreaming

Fortunatamente, libStreaming ha cambiato tutto questo incapsulando la maggior parte delle complessità di streaming ed esponendo un'API semplice e intuitiva che ti consente di scrivere un'app di streaming di base nel giro di poche ore.

In poche parole, snellisce lo streaming video.

Per usarlo per H.264 e AAC, devi fare quanto segue:

un. Inizializza un oggetto sessione nel metodo onCreate della tua attività principale. Gli oggetti di sessione rappresentano lo streaming multimediale a un peer:

 protected void onCreate(Bundle savedInstanceState) { mSession = SessionBuilder.getInstance() .setCallback(this) .setSurfaceView(mSurfaceView) .setPreviewOrientation(90) .setContext(getApplicationContext()) .setAudioEncoder(SessionBuilder.AUDIO_NONE) .setAudioQuality(new AudioQuality(16000, 32000)) .setVideoEncoder(SessionBuilder.VIDEO_H264) .setVideoQuality(new VideoQuality(320,240,20,500000)) .build(); mSurfaceView.getHolder().addCallback(this); }

B. Inizia effettivamente la sessione:

 mSession.setDestination(destination_server_url); mSession.start();

C. Interrompi la sessione al termine:

 mSession.stop();

Ora, per favore, non fraintendere. Lo streaming in tempo reale è disordinato per natura e libStreaming non elimina questa complessità. Tuttavia, fa davvero un buon lavoro nascondendolo da te per la maggior parte del tempo. In alcuni casi, dovrai affrontare la complessità, ad esempio quando selezioni la politica di segnalazione peer, scegli la codifica della telecamera (in genere vorresti utilizzare MediaCodec/surface-to-buffer) o gestisci la pacchettizzazione.

Tuttavia, scoprirai che i bravi ragazzi dietro libStreaming hanno fatto il possibile per unire queste complessità senza problemi in un'API semplice da usare.

LibStreaming supporta la maggior parte dei codificatori utilizzati dalle app Android, inclusi H.264, H.263, AAC e AMR.

Ho ottenuto ottimi risultati con questa libreria. Molte delle app di streaming più popolari lo utilizzano come parte della sua infrastruttura. Se mai ne dovessi incontrare la necessità, sono certo che renderà la tua esperienza di streaming multimediale molto più fluida.

LeakCanary: rileva le perdite di memoria in una riga di codice

Cominciamo con la motivazione alla base di questa libreria: memory leaks . Le app Android sono inclini a loro, soprattutto se non stai attento con la tua codifica. In effetti, creare perdite di memoria è molto semplice. Tutto quello che devi fare è memorizzare un riferimento di attività al di fuori del suo contesto. In effetti, anche la memorizzazione di un riferimento a un singolo oggetto vista al di fuori del contesto della sua attività creerà una perdita.

Come mai? Perché una vista, di fatto tutte le viste, memorizza internamente un riferimento di contesto alla sua attività che la contiene. Finché viene mantenuto un riferimento alla vista, la sua attività che la contiene, insieme a ciò che contiene, inclusi i drawable, la gerarchia della vista e le risorse, non può essere recuperata dal Garbage Collector.

Mantenere un riferimento a un'attività che perde non è sempre ovvio come parametro statico. Ogni volta che crei una classe interna o generi un thread all'interno di un'attività, verrà creato un riferimento a tale attività e l'attività potrebbe non essere rivendicata fino al completamento della classe interna o del thread.

Le perdite ovviamente non riguardano solo Android, ma essendo un sistema mobile con risorse di memoria limitate, l'impatto è più immediato.

La perdita di un riferimento a una singola attività ad alta intensità di risorse a volte è sufficiente per arrestare in modo anomalo l'app con un'eccezione " Memoria insufficiente".

Libreria LeakCanary

Come puoi proteggerti da loro? Inizia con pratiche di codifica rigorose , ovviamente. Ma non tutti noi siamo sviluppatori Android esperti e anche gli sviluppatori esperti a volte dimenticano le regole.

Le revisioni periodiche del codice con un'enfasi sulle perdite di memoria possono essere utili, ma richiedono tempo. Inoltre, alcune perdite sono davvero subdole e difficili da rilevare con la semplice revisione del codice.

L'uso dello strumento di memoria del DDMS è un ottimo modo per sapere, nel tempo, se la tua app perde. Dovresti assolutamente usarlo. Tuttavia, non ti dirà cosa sta causando la perdita.

Ecco che arriva leakCanary in soccorso. È il miglior rilevatore di perdite di memoria in circolazione e fornisce un rilevamento automatico delle perdite, come in una o due righe di codice, per tutte le tue attività.

Per usarlo, inizializza semplicemente leakCanary con l'oggetto della tua app onCreate() :

 public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); // more initialisations } }

E hai finito. LeakCanary controllerà le perdite di memoria e invierà una notifica se ne rileva una.

LeakCanary ottiene questa magia iniettando automaticamente un oggetto chiamato ActivityRefWatcher in tutte le tue attività e monitorando il conteggio dei riferimenti dopo onDestroy() è stato chiamato. Un conteggio di riferimento > 0 su un'attività distrutta può significare solo una perdita.

Importante: il rilevamento delle perdite funziona solo per le applicazioni in modalità debug. Non testare mai le perdite (beh, non con LeakCanary) in un APK in modalità di rilascio.

Ma cosa succede se voglio testare altre parti del mio sistema per rilevare eventuali perdite? Qui, LeakCanary offre un oggetto chiamato refWatcher, che è in effetti il ​​valore di ritorno della chiamata di inizializzazione:

 refWatcher = LeakCanary.install(this);

Può essere utilizzato per guardare i valori che saranno presto recuperati. Più precisamente, valori che credo verranno presto recuperati. Per farlo, chiama:

 refWatcher.watch(my_soon_to_be_reclaimed_obj);

La libreria ti avviserà se questo oggetto non è stato rilasciato poco tempo dopo la chiamata di controllo.

Non sono riuscito a trovare da nessuna parte il valore di questo “breve tempo”, ma probabilmente non è così importante. Con leakCanary, le cose funzionano e basta. Inestimabile.

Sommario

Gli sviluppatori esperti riducono giorni e settimane alle loro fasi di codifica e debugging utilizzando queste librerie, quindi non c'è motivo per cui tu non possa fare lo stesso.

Per riassumere, ecco cosa può fare per te la mia selezione di librerie Android:

  • ButterKnife : il codice iniettato automaticamente ti aiuterà a eliminare gran parte del codice standard della tua app. È l'ultima iniezione di codice per Android. Devo dire di più?

  • AndroidAnnotations : usa classi generate automaticamente dalla velocità incredibile e iniezione di codice basata sul nome per risparmiare tempo senza penalizzare le prestazioni rispetto alla logica codificata a mano.

  • EventBus : disaccoppia i componenti per un codice più robusto, la comunicazione tra componenti non è mai stata così semplice.

  • OkHttp : un sostituto intelligente di HttpURLConnection, con supporto per la rete asincrona, query di route di reindirizzamento delle richieste, query della cache locale e altro ancora.

  • Picasso – La manipolazione delle immagini semplificata che è così buona che ora è utilizzata da Google. È un grande risparmio di tempo nei progetti pesanti dei media e in alcuni progetti legacy.

  • ActiveAndroid – ORM semplificato senza sovraccarico di prestazioni.

  • LibStreaming – Streaming video in tempo reale, utilizzato dalle principali app di streaming.

Queste sono le uniche librerie Android che valgono il tuo tempo? Certamente no. Ma ti prometto questo: usarne uno qualsiasi nel tuo prossimo progetto ti renderà uno sviluppatore molto migliore. Se vuoi vederli in azione, dai un'occhiata al mio GitHub.

Se stai già utilizzando alcune o tutte, o se stai utilizzando librerie alternative, ti esorto a condividere le tue esperienze nei commenti qui sotto.

Correlati: Android 7.0 per sviluppatori: nuove funzionalità, aggiornamenti delle prestazioni e altre cose di cui non ti interessa