2011-05-03 246 views
0

我正在研究Android上的一个小项目,并且在我的解决方案中实现一些多线程时遇到了严重的问题。下面是一个类,它是主界面选项卡中的一个活动,它显示一个自定义列表,其中包含从YouTube API下载的图片和数据。Android:自定义列表视图和线程问题

该类可以正常工作,但是当首先从Internet上加载数据,然后再加载图像时,它会完全阻止用户界面。我知道我需要实现一些线程,并且我尝试了各种各样的东西,但我不太确定代码中哪些部分必须作为单独的线程启动。我的代码结构也有一些根本性的错误。

理想情况下,我希望在应用程序启动后立即向用户显示用户界面,并在其上方显示进度对话框,同时从YouTube加载文本数据。然后,用户应该可以控制UI,而图像则在后台的另一个线程中加载。

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; 
    } 
} 

}

非常感谢任何指针。

+0

凡是需要非确定性的时间量需要在非ui线程上。 – Falmarri 2011-05-03 21:06:35

回答

1

我结束了使用标准Java线程加载在后台从API的数据,并创造了在单独的线程加载图像一个单独的类,以及。如果你想知道它现在看起来像这样,似乎工作正常。

加载数据:

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(); 
     } 
    } 
}; 

加载图像(在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); 

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

如果你发现这个项目在增长,我强烈推荐Volley库。它管理诸如缓存和重试失败下载等事情。 – wblaschko 2014-02-21 18:36:24

2

查看AsyncTask。这将让您在展示用户界面的同时背景您长时间运行的流程。

此外,你可以找到在Android好/官方教程线程here.

+0

谢谢。我读过基础知识。我只是有一个问题在我的代码中应用它们。问题是,即使我在单独的线程中运行代码的某些部分,UI仍然不会显示,直到下载所有数据并且进度对话框根本不显示。我设法让它在一个更简单的类中工作,在TextView中显示下载的数据,但我无法实现与ListView相同的功能。 – Marian 2011-05-03 22:53:28

+0

如果AsyncTask不适合你,你可以试试'Service'。 http://developer.android.com/reference/android/app/Service.html – Haphazard 2011-05-03 23:04:00