Android: пользовательский ListView и проблема с многопоточностью - PullRequest
0 голосов
/ 04 мая 2011

Я работаю над небольшим проектом на Android и имею серьезную проблему с внедрением многопоточности в мое решение. Ниже приведен класс, который представляет собой действие на вкладке основного интерфейса, в котором отображается настраиваемый список с изображениями и данными, загруженными из API YouTube.

Класс работает нормально, но он полностью блокирует пользовательский интерфейс, когда сначала данные, а затем изображения загружаются из Интернета. Я знаю, что мне нужно реализовать некоторые потоки, и я пробовал разные вещи, но я не совсем уверен, какие части кода мне нужно запускать как отдельные потоки. Также есть вероятность, что с моей структурой кода что-то не так.

В идеале я хотел бы, чтобы пользовательский интерфейс отображался пользователю сразу после запуска приложения с диалоговым окном хода выполнения, когда текстовые данные загружаются с YouTube. Затем пользователь должен получить контроль над пользовательским интерфейсом, когда изображения загружаются в другой поток в фоновом режиме.

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

}

Большое спасибо за любые указатели.

Ответы [ 2 ]

2 голосов
/ 04 мая 2011

Проверьте AsyncTask . Это позволит вам справиться с вашими долгосрочными процессами при отображении пользовательского интерфейса.

Также вы можете найти хороший / официальный учебник по Android Threading здесь.

1 голос
/ 05 мая 2011

В итоге я использовал стандартный поток 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);
}
}
...