2011-05-03 4 views
0

Je travaille sur un petit projet sur Android et ont un sérieux problème avec l'application de certaines multi-threading dans ma solution. Voici une classe qui est une activité à l'intérieur de l'onglet de l'interface principale, qui affiche une liste personnalisée avec des images et des données téléchargées depuis l'API YouTube.Android: Custom ListView et problème Threading

La classe fonctionne très bien, mais il bloque complètement l'interface utilisateur lorsque, d'abord les données, puis les images sont chargées à partir d'Internet. Je sais que j'ai besoin d'implémenter des threads et j'ai essayé plusieurs choses, mais je ne sais pas exactement quelles parties du code je dois lancer en tant que threads séparés. Il y a aussi une possibilité qu'il y ait quelque chose de fondamentalement faux dans ma structure de code.

Idéalement, je voudrais avoir l'interface utilisateur affiché à l'utilisateur immédiatement après l'application est lancée avec une boîte de dialogue de progression au-dessus de celui-ci, alors que les données textuelles est en cours de chargement à partir de YouTube. Ensuite, l'utilisateur devrait avoir le contrôle de l'interface utilisateur, tandis que les images sont chargées dans un autre thread en arrière-plan.

public class VodsActivity extends ListActivity { 

private LayoutInflater mInflater; 
private Vector<RowData> data; 
RowData rd; 

//private Handler mHandler; 
private ProgressDialog dialog; 


//Generic names of custom ListView elements 
private static String[] title; 
private Vector<String> detail; 
private Vector<String> status;  
private Vector<String> imgurl; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.custom_list); 
    mInflater = (LayoutInflater) getSystemService(Activity.LAYOUT_INFLATER_SERVICE); 

    title = getResources().getStringArray(R.array.yt_channels); 
    detail = new Vector<String>(); 
    status = new Vector<String>(); 
    imgurl = new Vector<String>(); 

    //mHandler = new Handler(); 

    //dialog = ProgressDialog.show(VodsActivity.this, "","Loading. Please wait...", true);   

    loadData(); 
    displayData(); 

    //dialog.dismiss(); 

} 

private void loadData() {   
    String[] values = {"error", "error", "http://www.ephotobay.com/thumb/message-error.jpg" }; 

    for (int i = 0; i < title.length; i++) { 
     values = getData(title[i]); 
     values[1] = getTodaysUploads(title[i]); 
     detail.add(i, values[0]); 
     status.add(i, values[1]); 
     imgurl.add(i, values[2]); 
    } 
} 

/*** This function gets total number of uploads and thumbnail url for the user from a single feed ***/ 
private String[] getData (String username) { 
    String[] result = new String[3]; 
    String ytFeedUrl = "http://gdata.youtube.com/feeds/api/users/" + username + "?v=2"; 
    InputStream inStream = null; 

    try {   
     inStream = OpenHttpConnection(ytFeedUrl); 

     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 
     DocumentBuilder db = dbf.newDocumentBuilder(); 
     Document dom = db.parse(inStream); 
     Element docEle = dom.getDocumentElement(); 

     inStream.close(); 

     NodeList nl = docEle.getElementsByTagName("entry"); 
     if (nl != null && nl.getLength() > 0) { 
      for (int i = 0; i < nl.getLength(); i++) { 
       Element entry = (Element) nl.item(i); 
       Element thumbnail = (Element) entry.getElementsByTagName("media:thumbnail").item(0); 
       String thumbUrl = thumbnail.getAttribute("url"); 
       Element feedLink = (Element) entry.getElementsByTagName("gd:feedLink").item(5); 
       String uploads = feedLink.getAttribute("countHint"); 

       result[0] = uploads + " videos"; 
       result[1] = ""; //not used here     
       result[2] = thumbUrl;       
      } 
     } 

    } catch (MalformedURLException e) { 
     e.printStackTrace(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } catch (ParserConfigurationException e) { 
     e.printStackTrace(); 
    } catch (SAXException e) { 
     e.printStackTrace(); 
    } 
    finally { 
     // 
    } 
    return result; 
} 

/*** This function gets a number of today's uploads of the user ***/ 
private String getTodaysUploads (String username) { 
    String result = null; 
    String ytFeedUrl = "http://gdata.youtube.com/feeds/api/videos?author=" + username + "&time=today&v=2"; 
    InputStream inStream = null; 

    try {   
     inStream = OpenHttpConnection(ytFeedUrl); 

     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 
     DocumentBuilder db = dbf.newDocumentBuilder(); 
     Document dom = db.parse(inStream); 
     Element docEle = dom.getDocumentElement(); 

     inStream.close(); 

     NodeList nl = docEle.getElementsByTagName("feed"); 
     if (nl != null && nl.getLength() > 0) { 
      for (int i = 0; i < nl.getLength(); i++) { 
       Element entry = (Element) nl.item(i); 
       Element title = (Element)entry.getElementsByTagName("openSearch:totalResults").item(0);      

       result = title.getFirstChild().getNodeValue(); 
       result += " new today"; 
      } 
     } 

    } catch (MalformedURLException e) { 
     e.printStackTrace(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } catch (ParserConfigurationException e) { 
     e.printStackTrace(); 
    } catch (SAXException e) { 
     e.printStackTrace(); 
    } 
    finally { 
     // 
    } 
    return result; 
} 

private void displayData() { 
    //Use vector instead of ArrayList for safe threading 
    data = new Vector<RowData>(); 

    for (int i = 0; i < title.length; i++) { //Loop needs to be changed based on results 
     try { 
      rd = new RowData(i, title[i], detail.get(i), status.get(i)); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
     data.add(rd); 
    }   

    CustomAdapter adapter = new CustomAdapter (this, R.layout.custom_list_item, R.id.title, data); 
    setListAdapter(adapter); 
    getListView().setTextFilterEnabled(true); 
} 

private InputStream OpenHttpConnection(String strUrl) throws IOException { 
    InputStream inStream = null; 
    URL url = new URL(strUrl); 
    URLConnection conn = url.openConnection(); 

    try { 
     HttpURLConnection httpConn = (HttpURLConnection) conn; 
     httpConn.setRequestMethod("GET"); 
     httpConn.connect(); 

     if (httpConn.getResponseCode() == HttpURLConnection.HTTP_OK) { 
      inStream = httpConn.getInputStream(); 
     } 
    } catch (Exception ex) { 
     ex.printStackTrace(); 
    } 
    return inStream; 
} 

//This is temporary 
public void onListItemClick(ListView parent, View v, int position, long id) { 
    CustomAdapter adapter = (CustomAdapter) parent.getAdapter(); 
    RowData row = adapter.getItem(position);     
    Builder builder = new AlertDialog.Builder(this); 
    builder.setTitle(row.mTitle); 
    builder.setMessage(row.mDetail + " -> " + position); 
    builder.setPositiveButton("ok", null); 
    builder.show(); 
} 

//Private class RowData - holds details of CustomAdapter item 
private class RowData { 
    protected int mId; 
    protected String mTitle; 
    protected String mDetail; 
    protected String mStatus; 

    RowData (int id, String title, String detail, String status) { 
     mId = id; 
     mTitle = title; 
     mDetail = detail; 
     mStatus = status; 
    } 

    @Override 
    public String toString() { 
     return mId + " " + mTitle + " " + mDetail + " " + mStatus; 
    } 
} 

//Custom Adapter for the custom list, overrides onView() method 
private class CustomAdapter extends ArrayAdapter<RowData> { 

    public CustomAdapter(Context context, int resource, int textViewResourceId, List<RowData> objects) { 
     super (context, resource, textViewResourceId, objects); 
    } 

    @Override 
    public View getView(int position, View convertView, ViewGroup parent) { 
     ViewHolder holder = null; 
     TextView title = null; 
     TextView detail = null; 
     TextView status = null; 
     ImageView image = null; 
     RowData rowData = getItem(position); 

     //Reuse existing row views 
     if(convertView == null) { 
      convertView = mInflater.inflate(R.layout.custom_list_item, null); 
      holder = new ViewHolder(convertView); 
      convertView.setTag(holder); 
     } 

     holder = (ViewHolder) convertView.getTag(); 

     title = holder.getTitle(); 
     title.setText (rowData.mTitle); 
     detail = holder.getDetail(); 
     detail.setText(rowData.mDetail); 
     status = holder.getStatus(); 
     status.setText(rowData.mStatus); 

     //add if statements here for colors 

     image = holder.getImage(); 

     /**** This loads the pictures ****/ 
     BitmapFactory.Options bmOptions; 
     bmOptions = new BitmapFactory.Options(); 
     bmOptions.inSampleSize = 1; 
     String imageUrl = imgurl.get(rowData.mId); 
     Bitmap bm = LoadImage(imageUrl, bmOptions); 
     image.setImageBitmap(bm); 

     return convertView; 
    } 

    //Load image from the URL 
    private Bitmap LoadImage(String url, BitmapFactory.Options options) { 
     Bitmap bitmap = null; 
     InputStream inStream = null; 
     try { 
      inStream = OpenHttpConnection(url); 
      bitmap = BitmapFactory.decodeStream(inStream, null, options); 
      inStream.close(); 
     } catch (IOException ioex) { 
      ioex.printStackTrace(); 
     } 
     return bitmap; 
    }      
} 

/*** Wrapper for row data ***/ 
private class ViewHolder { 
    private View mRow; 
    private TextView title = null; 
    private TextView detail = null; 
    private TextView status = null; 
    private ImageView image = null; 

    public ViewHolder (View row) { 
     mRow = row; 
    } 

    public TextView getTitle() { 
     if (title == null) { 
      title = (TextView) mRow.findViewById(R.id.title); 
     } 
     return title; 
    } 

    public TextView getDetail() { 
     if (detail == null) { 
      detail = (TextView) mRow.findViewById(R.id.detail); 
     } 
     return detail; 
    } 

    public TextView getStatus() { 
     if (status == null) { 
      status = (TextView) mRow.findViewById(R.id.status); 
     } 
     return status; 
    } 

    public ImageView getImage() { 
     if (image == null) { 
      image = (ImageView) mRow.findViewById(R.id.thumbnail); 
     } 
     return image; 
    } 
} 

}

Merci beaucoup pour tous les pointeurs.

+0

Tout ce qui prend une quantité non déterministe du temps doit être sur un fil non-ui. – Falmarri

Répondre

1

J'ai fini à l'aide de vis standard java pour charger les données de l'API en arrière-plan et créé une classe distincte pour le chargement des images dans des threads séparés, ainsi . Au cas où vous vous demanderiez cela ressemble maintenant à ceci, et semble fonctionner correctement.

Chargement des données:

public void onCreate(...) { 
    //... 

    mHandler = new Handler(); 
    dialog = ProgressDialog.show(VodsActivity.this, "","Loading. Please wait...", true); 
    getData.start();   
} 

private Thread getData = new Thread() { 
    public void run() { 
     try { 
      loadData();    
      mHandler.post(showData); 
     } catch (Exception ex) { 
      ex.printStackTrace(); 
     } 
    } 
}; 

private Runnable showData = new Runnable() { 
    public void run() { 
     try { 
      displayData(); 
      dialog.dismiss(); 
     } catch (Exception ex) { 
      ex.printStackTrace(); 
     } 
    } 
}; 

Chargement des images (en CustomAdapter):

 String imageUrl = imgurl.get(rowData.mId); 
     final ImageView image = holder.getImage(); 

     //Reuse downloaded images or download new in separate thread      
     image.setTag(imageUrl); 
     Drawable cachedImage = imageLoader.loadDrawable(imageUrl, new ImageCallback() { 
      public void imageLoaded(Drawable imageDrawable, String imageUrl) { 
       ImageView imageViewByTag = (ImageView) image.findViewWithTag(imageUrl); 
       if (imageViewByTag != null) { 
        imageViewByTag.setImageDrawable(imageDrawable); 
       } 
      } 
     }); 
     image.setImageDrawable(cachedImage); 

classe ImageLoader:

public class ImageLoader { 
private HashMap<String, SoftReference<Drawable>> imageCache; 
private static final String TAG = "ImageLoader"; 

public ImageLoader() { 
    imageCache = new HashMap<String, SoftReference<Drawable>>(); 
} 

//Loads image from the cache if it exists or launches new thread to download it 
public Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) { 
    Log.d(TAG, "loadDrawable(" + imageUrl + ")"); 
    if (imageCache.containsKey(imageUrl)) { 
     SoftReference<Drawable> softReference = imageCache.get(imageUrl); 
     Drawable drawable = softReference.get(); 
     if (drawable != null) { 
      return drawable; 
     } 
    } 
    final Handler handler = new Handler() { 
     @Override 
     public void handleMessage(Message message) { 
      imageCallback.imageLoaded((Drawable) message.obj, imageUrl); 
     } 
    }; 
    new Thread() { 
     @Override 
     public void run() { 
      Drawable drawable = loadImageFromUrl(imageUrl); 
      imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); 
      Message message = handler.obtainMessage(0, drawable); 
      handler.sendMessage(message); 
     } 
    }.start(); 
    return null; 
} 

//Downloads image from the url 
public static Drawable loadImageFromUrl(String url) { 
    Log.d(TAG, "loadImageFromUrl(" + url + ")"); 
    InputStream inputStream; 
    try { 
     inputStream = new URL(url).openStream(); 
    } catch (IOException e) { 
     throw new RuntimeException(e); 
    } 
    return Drawable.createFromStream(inputStream, "src"); 
} 

public interface ImageCallback { 
    public void imageLoaded(Drawable imageDrawable, String imageUrl); 
} 
} 
+0

Si vous trouvez que ce projet se développe, je vous recommande fortement la bibliothèque Volley. Il gère des choses comme la mise en cache et réessayer des téléchargements échoués, etc. – wblaschko

2

Vérifiez la AsyncTask. Cela vous permettra de mettre en contexte vos processus de longue durée tout en affichant l'interface utilisateur.

Vous pouvez également trouver bonne/tutoriel officiel sur le filetage Android here.

+0

Merci. J'ai lu les bases. J'ai juste un problème en les appliquant dans mon code. Le fait est que même si je cours certaines parties du code dans des threads séparés, l'interface utilisateur ne s'affiche toujours pas tant que toutes les données ne sont pas téléchargées et que la boîte de dialogue de progression ne s'affiche pas du tout. J'ai réussi à le faire fonctionner dans une classe plus simple affichant les données téléchargées dans un TextView, mais je ne peux pas obtenir la même chose avec un ListView. – Marian

+0

Vous pouvez essayer 'Service' si la tâche asynchrone ne fonctionne pas pour vous. http://developer.android.com/reference/android/app/Service.html – Haphazard