2014-05-01 4 views
0

J'utilise AsyncTask pour ouvrir une URL, accéder au serveur, récupérer le contenu et l'afficher dans une vue de liste dans l'activité principale. Le contenu extrait est constitué d'un titre du journal et d'une URL vers le site Web, qui sera affiché sur un WebView dans une deuxième activité, si un bouton "Lire" est cliqué. J'ai tout de suite codé le programme et cela fonctionne, mais quand j'ai regardé en arrière, j'ai trouvé quelque chose qui semble déraisonnable, donc je veux surtout clarifier le fonctionnement du code. Voici le code pour l'activité principale:android AsyncTask et interaction de thread UI

package com.example.newsapp; 

public class MainActivity extends Activity { 

    static final private String LOG_TAG = "main"; 
    private ArrayList<Content> aList; 

    private class Content{ 

     Content() {}; 
     public String title; 
     public String url; 
    } 

    private class MyAdapter extends ArrayAdapter<Content>{ 

     int resource; 


     public MyAdapter(Context _context, int _resource, List<Content> titles) { 
      super(_context, _resource, titles); 
      resource = _resource; 
     // this.context = _context; 
     } 

     @Override 
     public View getView(int position, View convertView, ViewGroup parent) { 
      LinearLayout newView; 

      final Content content = getItem(position); 

      // Inflate a new view if necessary. 
      if (convertView == null) { 
       newView = new LinearLayout(getContext()); 
       String inflater = Context.LAYOUT_INFLATER_SERVICE; 
       LayoutInflater vi = (LayoutInflater) getContext().getSystemService(inflater); 
       vi.inflate(resource, newView, true); 
      } else { 
       newView = (LinearLayout) convertView; 
      } 

      // Fills in the view. 
      TextView tv = (TextView) newView.findViewById(R.id.listText); 
      ImageButton b = (ImageButton) newView.findViewById(R.id.listButton); 
      b.setBackgroundResource(0); 
      tv.setText(content.title); 
      Typeface type = Typeface.createFromAsset(getAssets(),"LiberationSerif-BoldItalic.ttf"); 
      tv.setTypeface(type); 

      // Sets a listener for the button, and a tag for the button as well. 
      b.setTag(Integer.toString(position)); 
      b.setOnClickListener(new View.OnClickListener() { 

       @Override 
       public void onClick(View v) { 
        // Reacts to a button press. 
        Intent intent = new Intent(MainActivity.this, WebPage.class); 
        Bundle bundle = new Bundle(); 
        bundle.putString("URL", content.url); 
        intent.putExtras(bundle); 
        startActivity(intent); 
       } 
      }); 
      return newView; 
     }  
    } 

    class MyAsyncTask extends AsyncTask<String, String, String> { 
     private ProgressDialog progressDialog = new ProgressDialog(MainActivity.this); 
     InputStream inputStream = null; 
     String result = ""; 
     Content content; 

     protected void onPreExecute() { 
      super.onPreExecute(); 
      progressDialog.setMessage("Downloading the news..."); 
      progressDialog.show(); 
      progressDialog.setOnCancelListener(new OnCancelListener() { 
       public void onCancel(DialogInterface arg0) { 
        MyAsyncTask.this.cancel(true); 
       } 
      }); 
     } 

     @Override 
     protected String doInBackground(String... params) { 

      String url_select = params[0]; 

      ArrayList<NameValuePair> param = new ArrayList<NameValuePair>(); 

      try { 
       // Set up HTTP post 
       // HttpClient is more then less deprecated. Need to change to URLConnection 
       HttpClient httpClient = new DefaultHttpClient(); 

       HttpPost httpPost = new HttpPost(url_select); 
       httpPost.setEntity(new UrlEncodedFormEntity(param)); 
       HttpResponse httpResponse = httpClient.execute(httpPost); 
       HttpEntity httpEntity = httpResponse.getEntity(); 

       // Read content & Log 
       inputStream = httpEntity.getContent(); 
       } catch (UnsupportedEncodingException e1) { 
        Log.e("UnsupportedEncodingException", e1.toString()); 
        e1.printStackTrace(); 
       } catch (ClientProtocolException e2) { 
        Log.e("ClientProtocolException", e2.toString()); 
        e2.printStackTrace(); 
       } catch (IllegalStateException e3) { 
        Log.e("IllegalStateException", e3.toString()); 
        e3.printStackTrace(); 
       } catch (IOException e4) { 
        Log.e("IOException", e4.toString()); 
        e4.printStackTrace(); 
       } 
      // Convert response to string using String Builder 
      try { 
       BufferedReader bReader = new BufferedReader(new InputStreamReader(inputStream, "iso-8859-1"), 8); 
       StringBuilder sBuilder = new StringBuilder(); 
       String line = null; 
       while ((line = bReader.readLine()) != null) { 
        sBuilder.append(line + "\n"); 
       } 
       inputStream.close(); 
       result = sBuilder.toString(); 
      } catch (Exception e) { 
       Log.e("StringBuilding & BufferedReader", "Error converting result " + e.toString()); 
      } 
      return result; 
     } // protected Void doInBackground(String... params) 


     protected void onPostExecute(String result) { 
      //parse JSON data 
      try { 
       super.onPostExecute(result); 
       Log.i(LOG_TAG, result); 
       JSONObject object = new JSONObject(result); 
       JSONArray jArray = object.getJSONArray("sites"); 
       for(int i=0; i < jArray.length(); i++) { 
        JSONObject jObject = jArray.getJSONObject(i); 
        content = new Content(); 
        if (jObject.has("title") && jObject.has("url")){ 
         content.title = jObject.getString("title"); 
         content.url = jObject.getString("url"); 
         aList.add(content); 
         aa.notifyDataSetChanged(); 
        } 
       } // End Loop 
       progressDialog.dismiss(); 
      } catch (JSONException e) { 
      // progressDialog.dismiss(); 
       Log.e("JSONException", "Error: " + e.toString()); 
      } 

     } // protected void onPostExecute(String result) 
    } 

    private MyAdapter aa; 
    private MyAsyncTask loadTask; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     loadTask = new MyAsyncTask(); 
     loadTask.execute("http://luca-ucsc.appspot.com/jsonnews/default/news_sources.json"); 
     aList = new ArrayList<Content>(); 
     aa = new MyAdapter(this, R.layout.list_element, aList); 
     ListView myListView = (ListView) findViewById(R.id.listView1); 
     myListView.setAdapter(aa); 
     aa.notifyDataSetChanged(); 
    } 

    public void refresh(View v){ 
     if (loadTask.getStatus() == AsyncTask.Status.FINISHED){ 
      aList.clear(); 
      aa.notifyDataSetChanged(); 
      new MyAsyncTask().execute("http://luca-ucsc.appspot.com/jsonnews/default/news_sources.json"); 
     } 
    } 


    @Override 
    public boolean onCreateOptionsMenu(Menu menu) { 
     // Inflate the menu; this adds items to the action bar if it is present. 
     getMenuInflater().inflate(R.menu.activity_main, menu); 
     return true; 
    } 

} 

Vous pouvez donc voir que seulement après loadTask.execute() dans onCreate(), dois-je créer l'objet pour alist et aa, mais je suis déjà les utiliser dans onPostExecute() dans la classe AsyncTaks , donc je ne suis pas très clair ce qui se passe ici, parce que onPostExecute() et l'interface utilisateur sont sur le même fil, donc le code dans onPostExecute() devrait être exécuté en premier.

Je pensais que je devrais mettre

aList = new ArrayList<Content>(); 
aa = new MyAdapter(this, R.layout.list_element, aList); 

dans onPostExecute(), ce qui est plus logique pour moi, mais l'application se bloque de cette façon. Aussi je pense que la suppression aa.notifyDataSetChanged(); dans onPostExecute() ne devrait pas être un problème parce que c'est aussi dans la méthode onCreate(), mais cela fait en fait l'affichage de la liste pour être vide, sans aucun contenu. En fait, l'insertion de l'un des codes après loadTask.execute() dans le bloc if de la méthode onPostExecute() provoque un problème ou bloque l'application. Ce serait génial si quelqu'un peut donner un aperçu ou un indice. Merci d'avoir lu.

+0

Mais vous devez mettre à jour votre 'UI' dans la méthode' onPostExecute() '. – Piyush

+0

@PiYusHGuPtA Je pense aa.notifyDataSetChanged() met à jour l'interface utilisateur –

Répondre

4

onPostExecute est appelée sur le thread d'interface utilisateur une fois la tâche d'arrière-plan terminée. Vous ne pouvez pas garantir la synchronisation de cet appel par rapport aux autres appels sur le thread de l'interface utilisateur.

Puisque vous implémentez déjà getView vous-même, je vous recommande d'étendre BaseAdapter au lieu de ArrayAdapter et de mettre en œuvre les autres méthodes requises. Ce n'est pas difficile et vous pouvez utiliser n'importe quelle structure de données pour sauvegarder l'adaptateur. En supposant que vous utilisez un List<Content> pour sauvegarder l'adaptateur, vous pouvez écrire une méthode pour échanger la liste en place comme ceci:

public void swapList(List<Content> newList) { 
    this.list = newList; 
    notifyDataSetChanged(); 
} 

Dans votre AsyncTask, vous avez le contrôle complet de l'Params, Progrès, et les résultats types paramétrés. Ils ne doivent pas tous être String. Vous pouvez le faire à la place:

private class myAsyncTask extends AsyncTask<String, Void, List<Content>> { 
    /* ... */ 
} 

Le String pour Params est l'URL (comme vous le faites maintenant). Void pour Progress car vous ne publiez pas de progression quand même. List<Content> pour Résultat parce que c'est la chose que vous voulez vraiment finir après avoir fait votre travail.

Vous devriez faire TOUT votre travail dans doInBackground. Il n'y a aucune raison de désérialiser une chaîne dans un JSONArray et de déranger avec cela dans onPostExecute, particulièrement depuis que cela se produit sur le thread principal. Réécrire doInBackground retourner un List<Content>, et tout ce dont vous avez besoin onPostExecute est ceci:

public void onPostExecute(List<Content> result) { 
    adapter.swapList(result); 
} 

Maintenant, vous pouvez créer l'adaptateur une fois (en onCreate()) et juste échanger la liste chaque fois qu'il est approprié.

+0

onPostExecute envoie un message à la boucle d'exécution du thread principal. Cela sera exécuté par le thread principal quand il est prêt pour cela. Très probablement après la fin de l'onCreate. – Arno

+0

Merci beaucoup pour votre explication. Oui, je pense que séparer le travail d'arrière-plan et la publication est certainement une façon beaucoup mieux et plus propre. Mais je ne comprends toujours pas pourquoi je ne peux pas créer mon objet arraylist et adaptateur dans la méthode onPostExecute, à l'ancienne. Et pouvez-vous expliquer pourquoi étendre BaseAdapter est meilleur ici? Je n'ai pas trouvé le champ de liste dans la classe de base, donc j'ai aussi besoin de surcharger la méthode notifyDataSetChanged()? Merci. –

+0

Vous n'avez pas besoin de surcharger 'notifyDataSetChanged()', il suffit de l'appeler chaque fois que vous modifiez le jeu de données sauvegardant l'adaptateur. J'ai seulement suggéré d'étendre 'BaseAdapter' pour vous donner un meilleur contrôle de son comportement puisque vous êtes déjà en train d'implémenter' getView() ', qui est vraiment le noyau d'un adaptateur de toute façon. Si vous préférez, vous pouvez à la place créer un nouvel adaptateur à chaque fois dans 'onPostExecute'. – Karakuri