Overclock.net › Forums › Software, Programming and Coding › Coding and Programming › Need someone with decent knowledge of Android/Java/Threading
New Posts  All Forums:Forum Nav:

Need someone with decent knowledge of Android/Java/Threading

post #1 of 3
Thread Starter 
I'm working on a small Android project and got stuck trying to implement threading into my solution. Below is an example of a class, which does what it's supposed to do, but completely blocks the UI while it's doing it. This class is a ListView activity inside a Tab in a TabView which is the main view of the application. There are three tabs and each of them contains something similar.

Briefly what is going on in the code:
Quote:
There's a static array of strings (YouTube user names) stored in xml file. There are also three Vector collections for storing attributes of the users, used to create a custom ListView items. These atributes are loaded from the internet from two different YouTube API feeds. There's a method that gets user's profile picture URL and total number of uploads and another method that gets the number of today's uploads. When the ListView items are being created the URLs are used to download images and then use them in the list.
Now all this is happening in a single thread, so when the application starts user is not able to do or see anything until all the data is loaded. Also during refreshing the list (for example on change of screen orientation) the UI gets blocked.

I've tried running various parts of the code in separate threads, but I can't quite figure out the solution. Sorry for a bit lengthy piece of code, but I guess someone who understands threading better should have no problem in figuring it out. I've tried commenting the code as much as possible.

Ideally I'd like to have the UI shown to the user immediately after the application is launched with a progress dialog on top of it, while the textual data is being loaded from youtube. Then the user should get control of the UI, while images are being loaded in another thread in the background.

Code:
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;
}
}
}
Here's the whole class in pastebin for better readibility:
http://pastebin.com/Y28ezg7a

Thanks for any pointers.
buka
(17 items)
 
  
Reply
buka
(17 items)
 
  
Reply
post #2 of 3
Android's AsyncTask is made for this sort of operation
It goes to eleven
(13 items)
 
  
CPUMotherboardGraphicsRAM
E6300 DS3 EVGA 8600GTS 2GB XMS2 DDR2-800 
Hard DriveOSMonitorKeyboard
1.294 TB Arch Linux/XP Samsung 226bw Eclipse II 
PowerCaseMouse
Corsair 520HX Lian-Li v1000B Plus G7 
  hide details  
Reply
It goes to eleven
(13 items)
 
  
CPUMotherboardGraphicsRAM
E6300 DS3 EVGA 8600GTS 2GB XMS2 DDR2-800 
Hard DriveOSMonitorKeyboard
1.294 TB Arch Linux/XP Samsung 226bw Eclipse II 
PowerCaseMouse
Corsair 520HX Lian-Li v1000B Plus G7 
  hide details  
Reply
post #3 of 3
Thread Starter 
Yeah, I've tried using it, but with no success for some reason. Thanks anyway, +rep.

I ended up using standard java Thread to load the data from API in the background and created a separate class for loading images in separate threads as well. In case you're wondering it now looks like this, and seem to work fine.

Loading the data:
Code:
  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();
}
}
};
Loading images:
Code:
            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 class:
Code:
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);
    }
}
buka
(17 items)
 
  
Reply
buka
(17 items)
 
  
Reply
New Posts  All Forums:Forum Nav:
  Return Home
  Back to Forum: Coding and Programming
Overclock.net › Forums › Software, Programming and Coding › Coding and Programming › Need someone with decent knowledge of Android/Java/Threading