Le combat contre l'AsyncTask

Publier le 20 juin 2021 16:00

Scène précédente

Pour comprendre comment le Pare-Fou en est arrivé là, je vous conseille la lecture de la scène précédente.

Le voyage du dev en droïde

Le naufrage

Le Pare-Fou est échoué sur une plage. Il crache un peu d'eau et se lève difficilement.

Oh... ma tête... Que s'est-il passé ? Je crois me souvenir d'un plat succulent, mais je ne me souviens plus de son goût. Ai-je eu le temps de le goûter ?

Des gargouillis se font ressentir.

Hum, ça se confirme donc... Et mon rhum a dû s'évaporer vu le bruit. Rien à se mettre sous la dent sur cette île, il faut vite que je m'extirpe de là.

Pour repartir, il faudrait que je me confronte à cette tempête AsyncTask. Cette classe a été dépréciée à partir de la version d'API 30 : la version 11 d'Android. Avec l'annonce d'Android 12 qui sera déployé à partir de fin août 2021, il faut trouver une alternative à cette classe qui nous fait défaut. Alors, mettons-nous au boulot !

Le capitaine reprend la barre

Le Pare-Fou prend un bâton et dessine sur le sable.

Plan dans le sable

Donc nous avons d'une part notre Activité et d'autre part une autre classe que l'on peut appeler "Thread" ou "Process", nous verrons.

Notre Activité a actuellement l'URL pour récupérer le flux RSS (fichier XML). Cette URL pourrait ensuite être envoyée à notre seconde classe qui se chargera de faire le traitement des données et nous retourner les résultats. Ce résultat sera pris en charge par l'Activité principale qui mettra ensuite à jour l'interface graphique avec le contenu récupéré.

Une seconde idée serait d'avoir le lien directement dans la seconde classe. Ce qui nous pose la seconde question : où placer la seconde classe ?

Cette classe pourrait être "à part" afin que l'on puisse la réutiliser avec d'autres URL sur d'autres activités. Elle prendrait également le XMLParser adéquat à l'URL passée en paramètre afin de convenir à tout type de format RSS. Or dans mon programme il n'y aura que mon fichier avec les scènes de mon blog, donc partons plutôt sur une classe "interne".

Le sauvetage

La tempête commence à approcher de l'île.

Bon, il est temps de mettre notre plan à exécution ! Il me faut une nouvelle Thread qui prendra en charge cette AsyncTask. Le doInBackground sera géré en premier et fera appel à notre classe interne qui nous donnera le résultat de notre requête d'URL. Le onPostExecute sera pris en charge par le runOnUiThread qui actualisera notre UI. Il y a deux manières de faire : soit avec une Thread Java simple, soit avec un executor et un handler. Mettons les deux solutions d'un coup pour contrer cette tempête !

public class MainActivity extends AppCompatActivity {
    private static final String URL = "https://leparefou.fr/feed/rss";
    private static final String TAG = "ACTIVITY_MAIN";
    private static final List<XMLParser.Item> itemsList = new ArrayList<>();
    private static ItemsAdapter itemArrayAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        itemArrayAdapter = new ItemsAdapter(this, itemsList);
        ListView listView = findViewById(R.id.list_item);
        listView.setAdapter(itemArrayAdapter);

        loadThreadTaskDownload();
    }
    private void loadThreadTaskDownload() {
        // Methode 1 : Thread simple
        new Thread(() -> {
            // doInBackground
            List<XMLParser.Item> items = DownLoadTaskThread.getResult(URL);
            runOnUiThread(() -> {
                // onPostExecute
                Log.d(TAG, items.toString());
                itemsList.addAll(items);
                itemArrayAdapter.notifyDataSetChanged();
            });
        }).start();
        
        // Methode 2 : Executor / Handler
//        ExecutorService executor = Executors.newSingleThreadExecutor();
//        Handler handler = new Handler(Looper.getMainLooper());
//        executor.execute(() -> {
//            List<XMLParser.Item> items = DownLoadTaskThread.getResult(URL);
//            handler.post(() -> runOnUiThread(() -> {
//                  // OnPostExecute
//                Log.d(TAG, items.toString());
//                itemsList.addAll(items);
//                itemArrayAdapter.notifyDataSetChanged();
//            }));
//        });
    }

    private static class DownLoadTaskThread {
        private static final String TAG = "DownLoadTaskThread";

        public static List<XMLParser.Item> getResult(String URL) {
            // Préparation de certains flux de lecture / écriture (IO)
            HttpURLConnection conn = null;
            List<XMLParser.Item> items = null;
            try {
                // 1) Obtenir une connexion à notre URL
                java.net.URL url = new URL(URL);
                conn = (HttpURLConnection) url.openConnection();

                // 2) Préparation de notre requête
                conn.setReadTimeout(10000);
                conn.setConnectTimeout(15000);
                conn.setRequestMethod("GET");
                conn.setDoInput(true);

                // 3) Lancer notre requête
                conn.connect();

                // 4) Récupération de la réponse
                try (InputStream response = conn.getInputStream()) { // try with resources - auto-closable
                    items = XMLParser.parse(response);
                }
                return items;
            } catch (MalformedURLException | ProtocolException e) {
                Log.e(TAG, "Une erreur lors de la requête est levée.");
                e.printStackTrace();
                return items;
            } catch (IOException e) {
                Log.e(TAG, "Une erreur de flux est levée.");
                e.printStackTrace();
                return items;
            } finally { // 5) Fermer les écoutilles !
                if (conn != null)
                    conn.disconnect();
            }
        }
    }
}

La tempête s'éloigne jusqu'à disparaître.

Pfiou... un problème de régler. Si j'avais décidé de prendre Kotlin comme langage au départ, j'aurai probablement opté pour les coroutines.

Un bateau approche, il semble familier au Pare-Fou.

Hé ! Mais c'est "L'an Droïde" ! Moussaillons ! Je suis ici ! Venez me chercher ! Lorsque je remonterai à bord, je nommerai en second capitaine celui que j'avais mis à la plonge. Cette personne est digne de confiance ! Après tout, il avait deviné que cette tempête était dépréciée.