venerdì 9 maggio 2014

Ottenere una lista di tutte le applicazioni installate

Con questo articolo apro una Rubrica dedicata interamente alla programmazione su Android con la solita premessa che io non sono un programmatore. Cominciamo!

La prima cosa da chiederci è: come devo strutturare il tutto? Di quali elementi grafici ho bisogno? La risposta è semplice. Essendo un elenco delle apps c'è bisogno di una semplice ListView con un custom adapter. Procediamo, dunque, alla creazione dei files XML.



1. Rechiamoci in /res/layout ed aprimo il file activity_main. Inseriamo quanto segue.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
   <RelativeLayout 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical">

    <ProgressBar
        android:id="@+id/barraProgresso"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"/>  
        
    <TextView
        android:id="@+id/caricando"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_below="@+id/barraProgresso"
        android:fontFamily="sans-serif-light"
        android:textSize="15sp"
        android:text="Caricamento in corso"
        android:textColor="@color/bianco"/>
            
   <ListView
        android:id="@+id/lista"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:divider="@null"
        android:dividerHeight="0dp"
        android:fastScrollEnabled="true"
        android:fastScrollAlwaysVisible="true"/>  

Abbiamo inserito una ListView, una TextView (per mostrare un messaggio di caricamento) ed una ProgressBar, per rendere più carino il tutto. Ora passiamo alla creazione del custom adapter.

2. Rechiamoci in /res/layout/ e creiamo un nuovo file XML che chiameremo adattatore_apps (siete liberi di cambiarlo, ovviamente). Al suo interno inseriamo quanto segue.
 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?xml version="1.0" encoding="utf-8"?>

 <LinearLayout 
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent">

 <LinearLayout
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:layout_marginTop="5.0dip"
     android:layout_marginBottom="5.0dip"
     android:layout_marginRight="15.0dip"
     android:layout_marginLeft="15.0dip"
     android:background="@drawable/card_layout"> 
     
 <ImageView
     android:id="@+id/icona_app"
     android:layout_width="55dp"
     android:layout_height="55dp"
     android:padding="3dp"
     android:scaleType="centerCrop"/>

 <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center_vertical"
    android:orientation="vertical"
    android:paddingLeft="5dp">
 
 <TextView
     android:id="@+id/nome_applicazione"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:gravity="center_vertical"
     android:textStyle="bold" 
     android:textColor="@color/bianco"
     android:singleLine="true"/>
 
 <TextView
     android:id="@+id/nome_package"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:gravity="center_vertical"
     android:fontFamily="sans-serif-light"
     android:textColor="@color/bianco"
     android:singleLine="true"/>
    
</LinearLayout>
</LinearLayout>
</LinearLayout>

Questo layout XML sarà il responsabile della rappresentazione grafica di ogni riga della nostra lista. Il layout si compone di due TextView (una per il nome dell'applicazione e l'altra per il package) e di una ImageView per rappresentare l'icona dell'app. 

3. Andate in /res/values/colors.xml e aggiungete


1
<color name="bianco">#FFFFFF</color>

E' cosa buona e giusta mettere tutti i colori che utilizzate nell'applicazione in questo file in modo da essere facilmente accessibili semplicemente facendo @color/nomeColore. Ora inizia il divertimento col codice Java!

4. Rechiamoci in /src/MainActivity.java e cerchiamo di capire cosa dobbiamo fare di preciso. Inanzitutto, visto che andremo a recuperare un elenco di applicazioni e considerato che su un dispositivo ce ne sono installate, in media, almeno una 60ina è un operazione lunga che non può essere eseguita sul Thread principale poichè causerebbe un ANR. E' necessario, quindi, creare un Async Task ma prima di fare questo dobbiamo ottenere il riferimento della ListView, della TextView e della ProgressBar XML nel codice Java e quindi utilizziamo il metodo findViewById per cercare nel layout impostato tramite setContentView la View con id corrispettivo a quello passato come parametro. Fatto questo abbiamo bisogno di un'ArrayList e del Package Manager. Abbiamo, tuttavia, bisogno di un Oggetto al quale passare le varie informazioni delle applicazioni e per questo creiamo la classe ApplicazioniInfo ed inseriamo quanto segue.
 
1
2
3
4
5
6
7
8
public class ApplicazioniInfo {

 CharSequence labelActivity, nomeActivity, nomeApplicazione;
 String nomePackage, nomeClasse;
 ComponentName componente;
 Drawable icona;

}

Ora che abbiamo la nostra classe ApplicazioniInfo torniamo nella MainActivity e inseriamo

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.apps_manager);
  
  lista = (ListView)findViewById(R.id.lista);
  progressBar = (ProgressBar)findViewById(R.id.barraProgresso);
  textView = (TextView)findViewById(R.id.caricando);
  
  packageManager = getPackageManager();
  new CaricaApplicazioni().execute();
 }

 class CaricaApplicazioni extends AsyncTask<Void, Void, Void> {

 @Override
 protected Void doInBackground(Void... arg0) {
  // TODO Auto-generated method stub
  arrayApps = new ArrayList<ApplicazioniInfo>();
  packageInfoLista = packageManager.getInstalledPackages(0);
  
  for(int i=0; i<packageInfoLista.size(); i++) {
   packageInfo = packageInfoLista.get(i);
   applicazioniInfo = new ApplicazioniInfo();
   applicazioniInfo.nomePackage = packageInfo.packageName;
   applicazioniInfo.nomeApplicazione = packageInfo.applicationInfo.loadLabel(packageManager);
   applicazioniInfo.icona = packageInfo.applicationInfo.loadIcon(packageManager);
   arrayApps.add(applicazioniInfo);
  }
  
        Collections.sort(arrayApps, new Comparator<ApplicazioniInfo>() {
            @SuppressLint("DefaultLocale")
   @Override
            public int compare(ApplicazioniInfo activityInfo, ApplicazioniInfo activityInfo2) {
                return activityInfo.nomeApplicazione.toString().toUpperCase().toString().trim().compareTo(activityInfo2.nomeApplicazione.toString().toUpperCase().toString().trim());
            }
        });
  
  return null;
 }
 
 protected void onPreExecute() {
  super.onPreExecute();
  textView.setVisibility(View.VISIBLE);
  progressBar.setVisibility(View.VISIBLE);
 }
 
 protected void onPostExecute(Void result) {
  super.onPostExecute(result);
  adattatore = new AdattatoreApps(AppsManager.this, 0, arrayApps);
  lista.setAdapter(adattatore);
  textView.setVisibility(View.GONE);
  progressBar.setVisibility(View.GONE);
 }
 
}

Che cosa abbiamo fatto? Nel metodo doInBackground abbiamo inializzato l'ArrayList e ottenuto le applicazioni tramite il getInstalledPackages. Successivamente, tramite un ciclo for, per ogni applicazione abbiamo ottenuto il suo PackageName, il nome vero e proprio e l'icona e man mano aggiungiamo il tutto all'ArrayList che, successivamente, nel metodo onPostExecute, passiamo all'Adattatore che esamineremo ora.

5. E' arrivato il momento di creare l'Adattatore. Andiamo in /src/ e creiamo una classe di nome AdattatoreApps. Inseriamo quanto segue.

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public AdattatoreApps(Context context, int textViewResourceId, ArrayList<ApplicazioniInfo> applicazioni) {
  super(context, textViewResourceId, applicazioni);
  // TODO Auto-generated constructor stub
  this.context = context;
  this.listaApps = applicazioni;  
 }
 
 @Override
 public View getView(int posizione, View view, ViewGroup parent) {
  holder = new Holder();
  if(view == null) {
            inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   view = inflater.inflate(R.layout.adattatore_apps, null);
   holder.nomeApplicazione = (TextView)view.findViewById(R.id.nome_applicazione);
   holder.nomePackage = (TextView)view.findViewById(R.id.nome_package);
   holder.iconaApp = (ImageView)view.findViewById(R.id.icona_app);
   view.setTag(holder);
  }
  else {
            holder = (Holder)view.getTag();
  }
  appInfos = listaApps.get(posizione);
  holder.nomeApplicazione.setText(appInfos.nomeApplicazione);
  holder.nomePackage.setText(appInfos.nomePackage);
  holder.iconaApp.setImageDrawable(appInfos.icona);  
  return view;
 }

}

class Holder {
 
 TextView nomeApplicazione, nomePackage;
 ImageView iconaApp;
 
}

Esaminiamo in primo luogo il costruttore della classe. Esso richiede il Context, l'ID della risorsa e l'ArrayList con le applicazioni. Passiamo al metodo getView. Esso è il cuore dell'adapter e lo abbiamo strutturato in modo che la lista possa essere ottimizzata al massimo e non avere gli odiosi lags durante lo scroll. Come abbiamo fatto? Semplice, se la view è nulla allora in quel caso facciamo i vari findViewById che essendo esosi in termini di risorse dovendoli fare sempre produrebbero i classici e già noti lag. Ma perchè? Una ListView visualizza sul display al massimo una 20ina di elementi quando si fa lo scroll, se la lista non è ottimizzata, le nuove celle che compaiono vengono create ogni volta! Per ovviare a questo problema abbiamo usato l'Holder che serve semplicemente per memorizzare i riferimenti alle View che ci permette, dunque, di ovviare ad utilizzare sempre il metodo findViewById. 

6. Quasi mi dimenticavo! Anche l'occhio vuole la sua parte e per questo, se ben avete notato, nel layout dell'adapter avrete visto un riferimento ad una certa card_layout. Questa drawable rappresenta una card in stile Google Now, che ultimamente vanno molto di moda e rendono moderna una applicazione. Quindi, recatevi in /drawable/ e create un file XML chiamato card_layout e inserite

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<layer-list
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:dither="true" android:shape="rectangle">
            <corners android:radius="2.0dip" />
            <solid android:color="#ff9c9c9c" />
        </shape>
    </item>
    <item android:bottom="2.0dip">
        <shape android:dither="true" android:shape="rectangle">
            <corners android:radius="2.0dip" />
            <solid android:color="@color/card" />
            <padding android:left="8.0dip" android:top="15.0dip" android:right="8.0dip" android:bottom="15.0dip" />
        </shape>
    </item>
</layer-list>

Finito! Quando andrete ad installare l'applicazione sul vostro dispositivo vi ritroverete davanti ad un risultato del genere.




Chiaramente questa guida è molto elementare. Lascio al lettore la possibilità di migliorare ed espandere le funzionalità della piccola applicazione creata. 

1 commento:

  1. Ottima guida simo, complimenti! Chiara e semplice, ovvero perfetta!

    RispondiElimina