ListView with getView() Over-Ridden Slow Due To Constant GC? - android

I have a ListView in my app, and I've over-ridden the getView() method so I can change the row's ImageView src depending on the row's text.
The problem is, I've noticed the ListView scrolling is lagging, and when I check DDMS, it seems the Garbage Collector is being called everytime the ListView is being scrolled, thus slowing the scrolling.
I've also noticed the Garbage Collector being called in a different part of my app, when reading lines from a BufferedReader, which makes opening a 2,000 line file take ~47 seconds, where as a file exporer I have installed on my phone opens that same file in about 2 seconds.
So my question is, what could be causing the constant Garbage Collection every 200ms or so, and how do I prevent it? It's really slowing my app down and I fear it will put some users off if I don't solve it.
Thanks,
Alex.
ListView getView():
class IconicAdapter extends ArrayAdapter<String> {
IconicAdapter(){
super(FileBrowser.this, R.layout.filebrowser_listview_row, R.id.listtext, directoryEntries);
}
#Override
public View getView(int position, View convertView, ViewGroup parent){
View row = super.getView(position, convertView, parent);
TextView text = (TextView) row.findViewById(R.id.listtext);
ImageView icon = (ImageView) row.findViewById(R.id.listicon);
entryFullFileName = directoryEntries.get(position).toString();
if(entryFullFileName.contains(".") && !entryFullFileName.matches("^\\.+$")){
String[] parts = entryFullFileName.split("\\.");
lastIndex = parts.length - 1;
fileType = parts[lastIndex];
}else{
fileType = "";
}
if(fileIsDir.get(position) == true){
icon.setImageResource(R.drawable.folderlightblue);
}else if(fileType.equals("html")){
icon.setImageResource(R.drawable.filehtml);
}else if(fileType.equals("css")){
icon.setImageResource(R.drawable.filecss);
}else if(fileType.equals("js")){
icon.setImageResource(R.drawable.filejs);
}else if(fileIsDir.get(position) == false){
icon.setImageResource(R.drawable.fileplain);
}
return(row);
}
}
Code To Open File
I removed the code the other day that logged how many seconds it took to open the file, but it took 47 seconds and definitely took too long, and again while the while loop is doing it's thing, there's constant calls to the Garbage Collector, which I'm guessing in the cause of the slow file reading - and yes, this function is called in a thread with progressDialog showing while the file is being read
private String getLocalFileContents(String fileUri){
try{
String contents = "";
BufferedReader in = new BufferedReader(new FileReader(fileUri));
String line;
while((line = in.readLine()) != null){
contents += line + "\n";
}
in.close();
return contents;
}catch(Exception e){
toast.setText("Failed to open sdcard file, try again.");
}
return null;
}
UPDATE:
The file reading problem is solved, turns out the String concatenation made the Garbage Collector get called after each loop, dramatically slowing the file reading down. As suggested by an answer I used StringBuilder instead and it now opens in a second - hooray!
2ND UPDATE:
I know what the cause of the constant GC calls when scrolling my ListView is, it's the ListView attribute android:cacheColorHint="#android:color/transparent" - but I don't know a work-around!

In general, garbage collection is happening because you're creating too many objects unnecessarily. It'd be easier to help with your code, but I'll give it a shot anyway.
In the case of your list, you're probably recreating your view in every call to getView. You should instead re-use convertView when appropriate. See my answer to this other SO question for an idea of how to structure your getView method.
Your file reading problem is a bit harder to guess at, but 47s seems ridiculously long for 2,000 lines. Are you also creating objects in that loop?
Update:
So apparently your problem isn't really with your View objects themselves, but it's all the work you do every time you get a View. You're doing quite a bit of work every time: a RegEx match, string splitting (and associated string object creation), etc. You should at minimum cache the results of this so that you don't have to redo the work for each item every time it comes back into view.

One optimization would be to stop splitting the entire string to get the filetype. You could use something like
String fileType = "";
int lastDot = entryFullFileName.lastIndexOf(".");
if(lastDot!=-1) {
fileType = entryFullFileName.substring()
}
That certainly shouldn't take 47s though.

Look at EfficientAdapter here is link
And explained more about efficient adapter and getView method in other thread have a look at it, here is link
Hope this help!!!

Yes, android:cacheColorHint="#android:color/transparent" is causing excessive calling of the garbage collector on some OS versions (not sure if the newest ones this is fixed).
Well, just try to not use it. For example, I spoke with my designers, explained them the problem about the cause of the lags, and they agreed to not use the transparent background.

Related

Casting an EditText as a TextView

This is a question on performance, not on capability.
When I get a View from findViewById(...), and it's an EditText, but I don't need anything specific to EditText, should I cast the View to TextView or to EditText? TextView is a closer subclass to View, but EditText is what the View actually is.
IMHO, yes you can cast the edittext as textview. But I dont think there is any must-to-follow rule. You can perform operations either by casting to an edittext or a textview.
As you dont need the functionalities of edittext you can surely cast to textview
I can see no problem doing that. If you dont have any EditText specific task then go ahead and cast it to TextView.
P.S.:- I personally do that always.. :D
You should cast it to EditText because otherwise it will through exception and your application will be crashed. And BTW if you are doing such things in your code I don't think you are following good practices. So don't do that.
Here is what I think, for me clarity is more important than any kind of optimization you can think of making in your application. If you have an EditText then leave it as such, otherwise you are only adding confusion to the code.
At the end the object already exists, and it is an EditText, so you will be accessing an EditText, no matter what are you casting it. You are only casting it in order to use it's methods in code, but the object instance doesn't change. So regarding to the inheritance, for example, if the EditText has overriden a method of the TextView, if you cast the object to a TextView and use it, you will still be using the EditText method.
Anyway, the virtual machine should cache the results of method resolution, so it wil be no impact in further use.
Don't worry about premature optimizations. Is this actually a bottleneck in your code? It shouldn't be. Something like this is totally negligible. It shouldn't have any noticeable effect on performance because it's still the same underlying object, just the type is being handled differently.
edit: Also, you may not need the EditText features now, but in the future you may. Just leave it as an EditText to save yourself a maintenance headache in the future.
I've found that there IS benefit in casting an EditText to a TextView in java code.
Doing so can reduce the amount of imports in your code, which can (drastically) reduce the size of your compiled APK.
EditTexts and Buttons can more often than not be referenced as TextViews in code (Buttons could also just be a View). Almost any kind of ViewGroup can be referenced as just ViewGroup. AbsListView instead of ListView (API 11+). AbsSpinner instead of Spinner. Just to name a few easy ones. Doing so has reduced some of my apk sizes by 20-30%.
you can miss the cast. findViewById returns a View object. If you don't need anything specific, work with it as View object
EDIT:
since we are talking about performance, here is a quick test. The results looks a bit random to me, but it looks like there is no significant difference between EditText and TextView. Only the Reflection method is slower:
try {
String text = "";
String result = "";
//Reflection
Long time = SystemClock.elapsedRealtimeNanos();
View v = ctx.findViewById(R.id.hello_text);
Field f = v.getClass().getSuperclass().getDeclaredField("mText");
f.setAccessible(true);
text = f.get(v).toString();
time = SystemClock.elapsedRealtimeNanos() - time;
result = "via Reflection ("+time.toString()+" ms)";
//TextView
time = SystemClock.elapsedRealtimeNanos();
TextView t = (TextView)ctx.findViewById(R.id.hello_text);
text = t.getText().toString();
time = SystemClock.elapsedRealtimeNanos() - time;
result = result + "\nvia TextView ("+time.toString()+" ms)";
//EditText
time = SystemClock.elapsedRealtimeNanos();
EditText e = (EditText)ctx.findViewById(R.id.hello_text);
text = e.getText().toString();
time = SystemClock.elapsedRealtimeNanos() - time;
result = result + "\nvia EditText ("+time.toString()+" ms)";
Toast.makeText(ctx, result, Toast.LENGTH_SHORT).show();
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (NoSuchFieldException e) {
}
}

Setting an imageview src through an activity

I keep trying to set an imageview through my main activity and it keeps returning a null pointer exception in LogCat. My code is fairly self explanatory.
I'm pulling data from a JSON url and pulling data out of objects.
for (int i=0; i<forecastday_arr.length(); i++) {
HashMap<String, String> map = new HashMap<String, String>();
JSONObject e = forecastday_arr.getJSONObject(i);
JSONObject date_obj = e.getJSONObject("date");
String curDate = date_obj.getString("weekday");
String conditions = e.getString("conditions");
String icon_to_use = e.getString("icon");
map.put("weekday", curDate);
map.put("conditions", conditions);
map.put("icon", icon_to_use);
if (icon_to_use=="rain") {
ImageView imgView = (ImageView) findViewById(R.id.imageViewDayOne);
imgView.setImageBitmap(BitmapFactory.decodeResource(this.getResources(), R.drawable.rain));
}
mylist.add(map);
}
From my code I'm checking to see if the value in "icon" is equal to rain, which it is (today), and if so, return the rain icon that's stored in my drawable folder. Even without the if statement I get the same null pointer exception. I'm quite new to Android so debugging isn't the easiest thing right now.
I presume I'm setting the image correctly. I've attached a picture of my LogCat below.
Sorry to be so vague but I thought it would be really simple to set an image. I'm sorry if I've missed anything as well - if I have I will quickly correct.
Pastebin of my MainActivity.java: http://pastebin.com/uNfYDGAw and a pastebin of my activity_main.xml: http://pastebin.com/M1xy1buB
So it appears your NullPointerException is coming from this line:
imgView.setImageBitmap(BitmapFactory.decodeResource(this.getResources(), R.drawable.rain));
I suspect that either this.getResources() or more likely BitmapFactory.decodeResource is returning null.
Luckily for you, there is an easier way to set an image on an ImageView. You can use setImageResource to set the drawable resource at runtime, like this:
imgView.setImageResource(R.drawable.rain);
--EDIT--
You also need to make sure you call setContentView with the layout that contains the ImageView you are attempting to use in onCreate of you MainActivity, before trying to reference any Views using findViewById.
setContentView(R.layout.layoutContainingImageViewDayOne);
Lastly, if you are attempting to do any network requests, they should be done in a background thread (off the UI thread) or the application with get an "Application Not Responding" crash. AsyncTasks are useful for this (however there are other methods). See http://developer.android.com/training/basics/network-ops/connecting.html for more info.
--EDIT 2--
So now that you posted all of the code, I see there are some major flaws here. Your R.layout.activity_main should actually be renamed to R.layout.list_row since it contains your row elements. Right now you are trying to use the same layout (R.layout.activity_main) for your Activity and its ListAdapter, which is impossible. R.layout.activity_main needs to have a ListView in it, that you attach your ListAdapter to. You are also trying to set your ImageView in your Activity code for your list rows, which the ListAdapter will already do for you.
So I'm going to suggest you take a look at some examples and refactor your app, Vogella has some great tutorials - http://www.vogella.com/articles/AndroidListView/article.html.
First use equals method to compare the Strings. Instead of ==
if (icon_to_use != null && icon_to_use.equals("rain")) {
....
}
Cause of NullPointerException
It might be because of you did not call setContentView to the layout or did not set right layout. means imageViewDayOne is not define in that layout.

Android: File Reading - OutOfMemory Issue

I am creating an app that involves reading in data from a file. The file is relatively large (1.8 MB) and is being read from an async thread in onCreate. The very first time the app is started up, it loads just fine. However, if you click the back button and then load it again, it runs out of memory and crashes (throwing the OutOfMemory error).
How do I get it to use the lowest amount of memory possible and/or free that memory when it is done?
File Reading Code (executed in the doInBackground() method of the async class):
public ArrayList<String> createDataList() {
dataList = new ArrayList<String>();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(getAssets().open(
"text.txt")));
String data;
while ((data = br.readLine()) != null) {
dataList.add(data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close(); // stop reading
} catch (IOException ex) {
ex.printStackTrace();
}
}
return dataList;
}
EDIT*
Async Class:
private class loadData extends AsyncTask<Void, Void, ArrayList<String>> {
#Override
protected ArrayList<String> doInBackground(Void... arg0) {
dataList = createDataList();
return dataList;
}
protected void onPostExecute(ArrayList<String> result) {
super.onPostExecute(result);
// display first element in ArrayList in TextView as a test
}
}
I have tried splitting up the file based on how I want to organize the data and store the data from each text file into a separate ArrayList but I had memory problems with that as well. I have also stored all of the data into one "master" ArrayList and then invoked a method on that "master" to add the data to the appropriate ArrayList (removing/clearing the data from the "master" as soon as it copied).
Any ideas on how to streamline and reduce memory impact?
EDIT**
Logcat:
That is from when you click the back button and then load the activity again. The following is just one of the messages produced (in verbose):
You can try adding android:largeHeap="true" in your manifest but it is not supported in Android API-8. To my understanding, you are reading and storing the data onto heap memory, which is usually quite limited and its size depends on the device your running your app on.
You might also want to investigate here: android - out of memory
First, make sure you don't have two copies of the data in memory. You can null out the reference to the old ArrayList before starting to create the new one, though you have to do that carefully -- calling ArrayList.clear() first would be more thorough.
Second, figure out how much memory that ArrayList is eating up by using a tool like hprof or Eclipse MAT. If it's using a ton of space, you may want to consider a more compact data representation.
So... from the code snippet, it looks like you're just reading a bunch of text strings in from a file, using a byte-to-char conversion. If the source material is plain UTF-8 (i.e. essentially ASCII), you've got a 2x expansion to UTF-16, plus allocation of the char[] object to hold it, plus the size of the String object that wraps that, plus the overhead of the entry in the ArrayList. Depending on how long an average string in the file is, this can be a significant multiple on your 1.8MB.
One way to avoid this would be to read the file into memory as byte[], scan it to figure out where each string starts, and just keep an array of integers with the start offset of each string. When you need string N, decode it from the byte[] into a String and return it. This reduces your overhead significantly. You could reduce it further by not loading the file and just reading individual strings out as needed (using a RandomAccessFile), but this may slow things down.
Seems to be you might have a bit of trouble with the immutability of Strings .
Why don't you try changing your code so you use StringBuilder for instance? Of course, you'll have to change more than one thing but it would be similar enough to your code and wouldn't fill your memory up as fast.

Multiple TextViews update very slowly

I have a Service that sends an Intent to my Activity every 0.1 seconds. I use it to update a custom implementation of a Chronometer. Here everything goes right. The problem comes when I want to update 14 TextView I have in a TableView inside a Fragment in my Activity. Here the app is very slow.
The method in my Activity where it receives the Intent from the Service:
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
long milis = intent.getLongExtra("milis",0);
if( mFragment != null)
mFragment.Update(milis);
}
};
The code inside the Fragment where I update the TextViews:
public void actualizarTiempoJuego(long milis){
// Se recuperan los tiempos acumulados y se aumenta la cantidad pasada como parĂ¡metro
for(int i=0;i<7;++i) {
long mCurrentMilis1 = mVectorMilis1.get(i);
long mCurrentMilis2 = mVectorMilis2.get(i);
TextView1 t1 = mListaTitularLayoutLocal.get(i);
TextView1 t2 = mListaTitularLayoutVisitante.get(i);
t1.setText(String.value(milis + mCurrentMilis1));
t2.setText(String.value(milis + mCurrentMilis2));
}
}
Am I doing anything wrong, or is it just that I'm trying to do something very complex in terms of efficiency?
#Sherif brings up a good point about hidden alpha values that bog down your application a lot.
Depending on your platform you may also want to check
<application android:hardwareAccelerated="true"... />
Another thing you can look into that may help performance is not firing off all those Intents. Once you start firing intents you are getting the system involved and depending on how they are getting resolved it may take some extra time.
For this issue I like to use Handlers. They are more light weight than intent.
You may also want to look at AsyncTask. This is basically like a thread, but also gives hooks that run on the UI Thread so you can perform both perform a background operation and update the UI without have to post runnables.
EDIT: Lastly, you can always run your layouts through the layoutopt tool. I was personally told by Romain Guy himself that if your drawing too slow, than you need to draw less. Just check out a screenshot (from a less than ideal view tree, but well within the max) from the profiling tool. You can see how much of the resources view drawing takes up. It's very important to keep this as lean as possible if you want your app to be responsive.
EDIT: It is no longer called layoutopt, it's called lint. Check your ~/android-sdk/tools/
I have once faced a situation where a fragment was really slow.
I am just predicting that your fragment has some kind of alpha and it is drawn on a 'heavy' activity.
The conclusion is that each time you are setting the text of a textview your whole view hierarchy is being invalidated.
It seems that fragments have this flaw. Anyway, use some layout instead of the fragment and check if it remains 'slow'.
ADDITION: A wrap_content textview will cause much more delay after a setText than a fill_parent textview.
You're likely running into slowdowns due to layout management with TableLayout and TextView. Every time you update text in one of those, a large amount of view measuring has to take place in order to put the characters in the right place on the screen. You should really just profile the app yourself using Traceview to find out. More information at: http://developer.android.com/tools/debugging/debugging-tracing.html
I've had the exact same issue you're seeing with the same type of layout (Fragment > TableLayout > Multiple TextViews). One way to test if your TableLayout/TextView setup is to blame is simply replace all that with a single TextView. That will probably run pretty well. Then put your 14 views into a FrameLayout or RelativeLayout. Even if they all overlap, you should still get decent performance, because it's the complexity of the TableLayout view measurements that's really causing slowdown.
As someone said you can use HardwareAccelerated but this is not a great solution, you will waste ram and cpu if you can't solve it in a different way. A solution probably more safety is to reduce the number of TextView. Try to reduce 14 to 7 and it will go twice faster. Usually is hard to do it but if you put the objects in a strategy position a pair of TextView one above other can be together if you make a TextView with two lines. And don't forget that findViewById is so expensive, if you will use a view object often find it one time and hold its reference.
Benchmarks are always useful for determining where slowness actually comes from, but I feel pretty confident suggesting that sending an Intent is probably much slower than updating 14 TextViews. Sending 10 Intents per second is a sign that you're Doing It Wrong (TM). This is just isn't what they're for.
Am I doing anything wrong, or is it just that I'm trying to do something very complex in terms of efficiency?
Updating 14 TextViews per second isn't inherently complex; you should be able to easily achieve this with a more appropriate application design. ASyncTask or Handler come to mind as possible tools, but it's hard to know what's best without knowing more about exactly what you're trying to do.
You can try to declare vars outside the loop :
public void actualizarTiempoJuego(long milis){
// Se recuperan los tiempos acumulados y se
// aumenta la cantidad pasada como parĂ¡metro
long mCurrentMilis1;
long mCurrentMilis2;
TextView1 t1;
TextView1 t2;
for(int i=0;i<7;++i) {
mCurrentMilis1 = mVectorMilis1.get(i);
mCurrentMilis2 = mVectorMilis2.get(i);
t1 = mListaTitularLayoutLocal.get(i);
t2 = mListaTitularLayoutVisitante.get(i);
t1.setText(String.value(milis + mCurrentMilis1));
t2.setText(String.value(milis + mCurrentMilis2));
}
}
And to setText() with mixed type, you can try setText("" + milis + mCurrentMilis2);

For loop not terminating - Android

I don't know why but my for loop won't 'stop' when its reached the truth of the termination statement.
for(int i = 1; i < 11; i++){
edittext.setText("");
EasyGame();
//if(i==10){
//Game.this.finish();
//}
}
EasyGame() is an arithmetic method, just adds two numbers together. I tried using the if statement shown above, but it still wouldn't do anything, and if it did it would call finish() after the first question!
If someone would be kind to help me I would be grateful.
EDIT:
public void EasyGame(){
Random rand = new Random();
final int a = (int) rand.nextInt(20)+1;
final int b = (int) rand.nextInt(20)+1;
String aString = Integer.toString(a);
String bString = Integer.toString(b);
String display = aString + " + " + bString + " =";
questionLabel.setText(display);
c = a + b;
}
that for loop is inside a switch/case, which deals with onClick() for buttons
Very difficult to say without seeing more code, but what you have posted alone is inherently flawed because you're trying to continuously update a UI element within a loop. If that loop is running on the UI thread, then the system isn't going to be able to redraw any UI elements such as your edittext until the loop (and whatever containing callback method) exits.
Therefore, when you say "I tried using the if statement shown above, but it still wouldn't do anything, and if it did it would call finish() after the first question!" I make the assumption that you're believing that the loop is only iterating once because you only ever see edittext display whatever is passed in the last ever .setText() call.
I dont see anything wrong with that code. The only possible problem is that your counter is being decremented somewhere (for example inside EasyGame();), or the problem is somewhere else
In the lack of provided code, I assume that your indefinite loop is happening inside EasyGame() method
The loop probably only runs once, and then it gets halted by a never ending loop in EasyGame().
Note: Don't use initial capitalized letters for methods, it's confusing.
I agree with Trevor Page, and i'll try and clarify :
your code, if it runs on a callback on the UI thread, could be calling itself by generating a callback when you are clearing the first textView or modifying the second.
Also, what do you mean by 'it won't stop' ? I don't quite see what you are trying to do here, since you erase 10 times the content of a textView and replace 10 times the content of another one.

Categories

Resources