I'm looking for a way to speed up the creation of a TableLayout with over 1000 rows. Is there a way to create a TableLayout entirely on a separate thread or a way to speed it up?
Here is my method that is creating the table:
private void setTable()
{
final Activity activity = this;
final Handler handler = new Handler();
new Thread(new Runnable()
{
#Override
public void run()
{
for (int x = 0; x < rooms.size(); x++)
{
final int inx = x;
handler.post(new Runnable()
{
#Override
public void run()
{
Methods.createRow(table, rooms.get(inx), null, activity);
TableRow row = (TableRow)table.getChildAt(inx);
row.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View arg0)
{
if (arg0.getTag() != null && arg0.getTag().getClass() == Integer.class)
select((Integer)arg0.getTag());
}
});
}
});
}
}
}).start();
}
I was hoping that using a Handler would at least allow the new Activity to appear before the table was created. The application seems to freeze up for a few seconds when creating tables with a lot of rows. setTable() is being run in my Activity's onStart() method.
Methods.createRow adds a row to the end of the TableView that is passed in.
Edit:
After deciding to try out a ListView, I got much better results with a lot less code.
private void setTable()
{
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, roomNames);
table.setAdapter(adapter);
table.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3)
{
select(arg2);
}
});
}
First things first.
Why your app freezes:
Handler works like a queue, it queues every post you made and than execute it serially in your main thread.
But the main problem is the amount of data you are trying to show at once, but it is easily solved with an Adapter, you probably can use some default Component for solve this, like ListView or GridView, you can make your custom rows to work around the columns maybe.
Just from guessing on the method name, it seems this line may be slow to run on the main/UI thread:
Methods.createRow(table, rooms.get(inx), null, activity);
I would suggest separating all the heavy database work and UI work with AsyncTask, so it might look something like this:
private OnClickListener rowClickListener = new OnClickListener()
{
#Override
public void onClick(View arg0)
{
if (arg0.getTag() != null && arg0.getTag().getClass() == Integer.class)
select((Integer)arg0.getTag());
}
};
private void setTable()
{
final Activity activity = this;
final Handler handler = new Handler();
new AsyncTask<Void, Void, List<TableRow>>() {
#Override
protected List<TableRow> doInBackground(Void... params) {
// Do all heavy work here
final List<TableRow> rows = new ArrayList<TableRow>(rooms.size());
for (int x = 0; x < rooms.size(); x++)
{
Methods.createRow(table, rooms.get(x), null, activity);
rows.add((TableRow)table.getChildAt(x));
}
return rows;
}
#Override
protected void onPreExecute() {
// maybe show progress indication
}
#Override
protected void onPostExecute(List<TableRow> result) {
// Do all UI related actions here (maybe hide progress indication)
for (final TableRow row : result) {
row.setOnClickListener(rowClickListener);
}
}
};
}
Since I can't tell what is in some methods, you'll just need to ensure you've tried to optimize as best as possible in Methods.createRow(...) and move all the UI related work to the onPostExecute(...) method.
You are stacking the post with all the actions at once. So it same as not using a thread at all as your main thread doing all the work. Try changing to postDelay, and for each room index give it 1 millisecond or more.
Related
I have a problem with showing progress on ProgressBar.
I have function that calls my server with Volley, when return result I have implemented a Callback that return data to my activity.
Something like:
public static void saveData(Data data, final VolleyCallbackJsonObject callback){
...
callback.onSuccessResponse(new JSONObject());
...
}
Supposing that the function is called on cycle like:
for(int i = 1; i<=5; i++){
final int copyIndex = i;
MyClass.saveData(new Data(i), new VolleyCallbackJsonObject() {
#Override
public void onSuccessResponse(JSONObject result) {
callFunctionForShowAndUpdateProgress(copyIndex);
}
})
}
}
I would like show a ProgressBar that show a bar with a progress from 1/5 to 5/5
so I have my Function in my activityClass:
public Class Test extends Activity{
private ProgressBar pb;
public void callFunctionForShowAndUpdateProgress(int index){
if(index == 1){
pb = (ProgressBar) findViewById(R.id.pbLoading);
pb.setMax(5);
pb.setIndeterminate(false);
pb.setVisibility(View.VISIBLE);
}
if(pb != null){
pb.setProgress(index);
}
if(index == 5){
pb.postDelayed(new Runnable() {
#Override
public void run() {
pb.setVisibility(View.GONE);
}
}, 3000);
}
}
}
My problem is that progress bar not set progress, but it show only at the end so with 5/5 and not show the progress step (1/5, 2/5, 3/5, 4/5) and then it will hide after 3 seconds how specified on postDelayed.
So How can I show all the step? what is wrong?
Try to postDelay the method 'callFunctionForShowAndUpdateProgress' in the method 'onSuccessResponse' instead the postDelay you are already making because the delay you are making happens only when i = 5, so technically the progress goes through 2, 3, and 4 but you don't see it.
try this inside 'onSuccessResponse':
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
callFunctionForShowAndUpdateProgress(copyIndex);
}
}, 3000);
By the way, When the method 'callFunctionForShowAndUpdateProgress' is called in the
'onSuccessResponse' the index initially will be 1 and that is fine, the compiler then will go to second if statement
if(pb != null){
pb.setProgress(index);
}
and will execute it because the condition is correct there, it will not cause a problem but leaving it like this is not a good practice, to solve this prevent the compiler from getting into this if statement by adding else.
I hope that helps
Change your cycle calling function as like below and then check,
int copyIndex = 1;
callFunctionForShowAndUpdateProgress(copyIndex);
for(int i = 1; i<=5; i++)
{
MyClass.saveData(new Data(i), new VolleyCallbackJsonObject()
{
#Override
public void onSuccessResponse(JSONObject result)
{
copyIndex++;
callFunctionForShowAndUpdateProgress(copyIndex);
}
})
}
I am trying to update my listview to make an "endless scroll". What happens is that the first 40 results load fine, when i get to the bottom of the scroll, next 40 results replace the first 40...
What I want is for second set of 40 results to add to the first 40 so I have an endless list and ability to scroll back to the beginning of the list.
I am posting my code below. Thank you!
public class SearchResults extends Activity implements BannerAdListener, OnScrollListener{
private LinearLayout bottomNav;
private ListView ringtoneList;
private int start = 0, num = 40, curPage = 1;
private Handler handler = new Handler();
private ProgressDialog progressDialog = null;
private ArrayList<Ringtone> ringtones;
private MoPubView moPubView;
private String searchString;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
if (extras == null) {
// no search string defined
finish();
} else {
searchString = extras.getString("search_string");
}
setContentView(R.layout.search_results);
ringtoneList = (ListView)findViewById(R.id.ringtone_list);
ringtoneList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> av, View view, int position, long id) {
Intent i = new Intent(SearchResults.this, RingtoneView.class);
i.putExtra("ringtone", ringtones.get(position));
startActivity(i);
}
});
performSearch();
moPubView = (MoPubView) findViewById(R.id.adview);
moPubView.setAdUnitId(Utils.MoPubBannerId);
moPubView.loadAd();
moPubView.setBannerAdListener(this);
ringtoneList.setOnScrollListener(this);
}
public void onScroll(AbsListView view, int firstVisible, final int visibleCount, int totalCount) {
Log.i("List", "firstVisible="+firstVisible+" visibleCount="+visibleCount+" totalCount="+totalCount);
boolean loadMore = firstVisible + visibleCount >= totalCount;
if(loadMore) {
Log.i("List", "Loading More Results");
curPage++;
start = num * (curPage-1);
new Thread() {
public void run() {
ringtones = Utils.search(start, num, searchString);
if (ringtones != null && ringtones.size() > 0) {
handler.post(new Runnable() {
public void run() {
ringtoneList.setAdapter(new RingtoneRowAdapter(SearchResults.this, ringtones));
}
});
} else {
handler.post(new Runnable() {
public void run() {
new AlertDialog.Builder(SearchResults.this)
.setTitle(getResources().getString(R.string.context_info)).setMessage(getResources().getString(R.string.context_noresult))
.setPositiveButton(getResources().getString(R.string.context_ok), null).show();
}
});
}
}
}
.start();
}
}
public void onScrollStateChanged(AbsListView v, int s) { }
private void performSearch() {
progressDialog = ProgressDialog.show(SearchResults.this, getResources().getString(R.string.loading_message), getResources().getString(R.string.loading_search), true);
new Thread() {
public void run() {
ringtones = Utils.search(start, num, searchString);
if (ringtones != null && ringtones.size() > 0) {
updateList();
} else {
handler.post(new Runnable() {
public void run() {
new AlertDialog.Builder(SearchResults.this)
.setTitle(getResources().getString(R.string.context_info)).setMessage(getResources().getString(R.string.context_noresult))
.setPositiveButton(getResources().getString(R.string.context_ok), null).show();
ringtoneList.setVisibility(View.INVISIBLE);
bottomNav.setVisibility(View.INVISIBLE);
}
});
}
progressDialog.dismiss();
}
}.start();
}
private void updateList() {
handler.post(new Runnable() {
public void run() {
//Log.d("search", "ringtones.size() " + ringtones.size());
ringtoneList.setVisibility(View.VISIBLE);
ringtoneList.setAdapter(new RingtoneRowAdapter(SearchResults.this, ringtones));
}
});
}
}
Please help! Thank you!
While I can't be 100% sure, I think your problem has to do with the fact that you're setting a new adapter that only has the section of ringtones that in loads. It probably has to do with this snippet:
ringtones = Utils.search(start, num, searchString);
if (ringtones != null && ringtones.size() > 0) {
handler.post(new Runnable() {
public void run() {
ringtoneList.setAdapter(new RingtoneRowAdapter(SearchResults.this, ringtones));
}
});
}
Instead of putting an entirely new array of ringtones, you should add on to the one you already have. Your ringtones variable is already an instance variable, so I'm sure if you changed this line:
ringtones = Utils.search(start, num, searchString);
to the following:
ringtones.addAll(Utils.search(start, num, searchString));
It might fix your problem.
Your code is a little bit messy, but your updateList method is creating a NEW RingtoneRowAdapter. You should ADD items to the list and call
mRingtoneRowAdapter.notifyDatasetChanged();
That will tell the adapter to get new views (if needed) along with a lot of internal stuff happening at Adapter's level. So it's kinda:
private void updateList() {
handler.post(new Runnable() {
public void run() {
if ( mAdapter == null ) {
mAdapter = new RingtoneRowAdapter(SearchResults.this, ringtones);
ringtoneList.setAdapter(mAdapter);
} else {
mAdapter.notifyDataSetChanged();
}
ringtoneList.setVisibility(View.VISIBLE);
}
});
}
And of course, as already suggested, don't init your ringtones array all the time. Just add data to it.
You don't need (most of the times) to hide the list, you can add in your layout, any View (including a full Layout!) with an id of:
android:id="#android:id/empty"
And if your ListView has an id of android:id="#id/android:list", and you're in a ListFragment or ListActivity, then when the list is empty, the "empty" layout will be shown (which can be a dummy view if you don't want to see it).
Just suggestions :)
UPDATE: For your null, I see that the logic is a little bit weird and since we don't know the requirements for your app (i.e. what to do if there are no results, what to do if the results are invalid, etc.) I'll assume you just want to make sure it works and deal with that later.
So with that in mind, I see two problems.
I assume your Utils.search method can return null, because you're checking for it. To me that feels strange, I'd rather return an empty array indicating that the search produced no results; that little remark aside, you are not checking (or we don't know because we haven't seen the source for your search method), if the searchString is null or not.
You haven't provided a stack trace, so we can't tell where the null is happening (either inside search or ?)
A quick solution would be to check for null before searching… I would personally do this INSIDE the search function.
Something like:
public ArrayList<Ringtone> search(final int start, final int num, final String searchString) {
if ( searchString == null ) {
//DECIDE WHAT YOU WANT TO DO, EITHER:
return null;
// OR YOU CAN RETURN AN EMPTY ARRAY
return new ArrayList<Ringtone>();
}
// You should check for these (change according to your rules)
if (start < 0 ) {
start = 0; // protect yourself from bad data.
}
if ( num < 0 ) {
num = 0;
}
/// THE REST OF YOUR search FUNCTION
return <your array>
}
Now another thing is that you may want the search to return incremental results (so you can ADD them to your array (instead of returning a new array every time). For that, as already suggested, use the addAll trick, but then, DON'T return null, return an new empty array, so there's no harm done if there's nothing else to add.
I can not decide for himself the task will be to ask
I want to measure the height of the ListView. Can not catch the moment of rendering ListView (rssListView.getHight(); = 0 )
public class RSSactivity extends Activity {
public static RssItem selectedRssItem = null;
String feedUrl = "";
ListView rssListView = null;
ArrayList<RssItem> rssItems = new ArrayList<RssItem>();
ArrayAdapter<RssItem> aa = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
rssListView = (ListView) findViewById(R.id.rssListView);
aa = new ArrayAdapter<RssItem>(this, R.layout.list_item, rssItems);
rssListView.setAdapter(aa);
feedUrl = rssURLTV.getText().toString();
refressRssList();
rssListView.getWidth(); // = 0 ,
}
private void refressRssList() {
ArrayList<RssItem> newItems = RssItem.getRssItems(feedUrl);
rssItems.clear();
rssItems.addAll(newItems);
aa.notifyDataSetChanged();
}
}
target to calculate the number of items intermeddle ListView screen
In onCreate:
rssListView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// If you need this to be called again, then run again addOnGlobalLayoutListener.
mainPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
rssListView.getWidth(); // It's available here.
}
});
why you need Listview's Height()? if u want to check empty Listview then you can do using setEmptyListview().
You can Measure the height of listview by using size method.
rssItems.size();
Hope it helps you.
When onCreate method is called the screen is not loaded so every layout component has height = 0 and width = 0. If you want to know the height or width do it before the screen is loaded. The way I use is make a thread that starts running on onCreate. The thread asks for layout object height, while it is 0 I know that the screen components are not loaded yet. When the height is greater than 0 I commit to UI thread the task I want to do.
Edit: Try to do get the height inside onStart method, but I'm not sure about the screen is loaded yet.
Some Code example:
public void onCreate(Bundle savedBundle){
super.onCreate(savedBundle);
setContentView(R.layout.main);
final ListView lv = (ListView) findViewById(R.id.my_list_view);
Thread thr = new Thread(new Runnable(){
public void run(){
while(lv.getHieght() <= 0){
try{
Thread.sleep(10);
} catch(Exception e) {
}
} //while ends
final int height = lv.getHight();
MyActivity.this.runOnUiThread(new Runnable(){
public void run(){
myMethod(height);
} //UI thread runnable-run method ends
}); //UI Runnable declaration ends
} //thread runnable-run method ends
}); //Thread Runnable declaration ends
thr.start();
} // onCreate ends.
public void myMethod(int height){
//opeate with valid height
}
I am trying update text views while this loop is processing. Here is my code that I am using for this activity.
public class StartDayActivity extends Activity {
Data data_;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.startdayscreen);
Thread seperationTimer = new Thread(){
public void run(){
int seperationTimer = 5000;
int weatherAffect = 0;
int randomEventAffect = 0;
int locationTotalAvailable = 10;
//int servingTime = 3000;
int totalAvailableCustomers = (weatherAffect + randomEventAffect + locationTotalAvailable);
int dayTimer = (totalAvailableCustomers * seperationTimer);
int seperationTimerDay = 0;
int lemonadeSold = 11;
int totalDrinksSold = 0;
int pitcherSize = 12;
int totalLemonsUsed= 0;
int totalIceUsed =0;
int totalSugarUsed = 0;
int totalCupsUsed = 0;
double drinkPrice = 0.80;
try{
while(seperationTimerDay < dayTimer){
sleep(seperationTimer);
seperationTimerDay = seperationTimerDay + seperationTimer;
lemonadeSold = lemonadeSold + 1;
totalDrinksSold += 1;
data_.cups_ -= 1;
totalIceUsed += 2;
if(lemonadeSold == pitcherSize){
lemonadeSold = 0;
data_.lemons_ -= 4;
data_.sugar_ -= 2;
totalSugarUsed +=2;
totalLemonsUsed +=4;
}
if(data_.lemons_ == 0 || data_.ice_ == 0 || data_.sugar_ ==0 || data_.cups_ == 0){
break;
}
updateView();
Log.d("TAG", "for each : " + data_.ice_);
//calcMarketing();
}
data_.day_gross_profit_ = (totalDrinksSold * drinkPrice);
data_.cash_ += (totalDrinksSold * drinkPrice);
Intent i = new Intent("com.game.lemonade.PLAYSCREEN");
startActivity(i);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
finally{
finish();
}
}
};
seperationTimer.start();
}
#Override
public void onStart(){
super.onStart();
data_ = getData();
}
#Override
public void onResume(){
super.onResume();
data_ = getData();
//refreshDisplay();
}
#Override
public void onPause(){
super.onPause();
saveData();
//refreshDisplay();
}
#Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
//refreshDisplay();
}
private Data getData() {
GameData player_data = new GameData(this);
player_data.open();
Data game_state = new Data(player_data.getGameString());
player_data.close();
return game_state;
}
private void saveData() {
GameData player_data = new GameData(this);
player_data.open();
player_data.setGameString(data_.SerializeGame());
player_data.close();
}
private void updateView(){
((TextView)findViewById(R.id.lemonsLeftText2)).setText(
(data_.lemons_) );
}
}
Is there a better way to try to do this? And how would I want to do that? I get an error when I do the updateView() to try to update textview.
Error message already contains answer to your question. You can not update UI from different thread - use runOnUIThread() to do this. And for battery sake - get reference to textView in onCreate() and reuse it.
You cant access the UI using threads directly.Either use an AsyncTask or Handler for your longer thread operations which effect the UI.
Check these;
http://developer.android.com/resources/articles/painless-threading.html
http://developer.android.com/reference/android/os/Handler.html
You are not allowed to change the appearance of your Views from any thread that is not the main thread.
#Serdar has some great suggestions for how to handle that.
Also you should avoid calling findViewById() each time you need to call setText(). findViewById() is a fairly expensive call in terms of system resources. The fewer calls like that you make the better your app will run.
I suggest you make yourself a TextView reference and make your findViewById() call inside onCreate() and set your reference to what it returns to you. Then in your updateView() method call something like yourTxt.setText("");
Although the other answers have covered the answer to your question, I'll point you at this section in the documentation.
Threads
In particular the last part.
Additionally, the Andoid UI toolkit is not thread-safe. So, you must not manipulate your UI from a worker thread—you must do all manipulation to your user interface from the UI thread. Thus, there are simply two rules to Android's single thread model:
Do not block the UI thread
Do not access the Android UI toolkit from outside the UI thread
Two very simple rules indeed but if you stick to them you'll avoid a lot of problems.
You should use Handler class:
Where you call:
updateView();
replace with:
myHandler.sendEmptyMessage(0);
In the handler:
private Handler myHandler=new Handler(){
#Override
public void handleMessage(Message msg) {
if(msg.what==0){
updateView();
}
};
};
I am having a problem with ProgressDialog UI being frozen when I start the action in the AsyncTask.
My problem is somewhat different than the bunch of other similar question because the my background task consists of two parts:
- first part (loadDB()) is related to the database access
- second part (buildTree()) is related to building the ListView contents and is started with runOnUiThread call
The progress dialog is correctly updated during the 1st part of the task, but not during the 2dn part.
I tried moving the buildTree part in the AsyncTask's onPostExecute but it doesn't help, this part of the code still causes the progress to freeze temporarily until this (sometimes quite lengthy) part of the work is done. I can not recode the buildTree part from scratch because it is based on external code I use.
Any tips on how to resolve this? Is there a method to force updating some dialog on screen?
The code goes here:
public class TreePane extends Activity {
private ProgressDialog progDialog = null;
public void onCreate(Bundle savedInstanceState) {
// first setup UI here
...
//now do the lengthy operation
new LoaderTask().execute();
}
protected class LoaderTask extends AsyncTask<Void, Integer, Void>
{
protected void onPreExecute() {
progDialog = new ProgressDialog(TreePane.this);
progDialog.setMessage("Loading data...");
progDialog.show();
}
protected void onPostExecute(final Void unused) {
if (progDialog.isShowing()) {
progDialog.dismiss();
}
}
protected void onProgressUpdate(Integer... progress) {
//progDialog.setProgress(progress[0]);
}
protected Void doInBackground(final Void... unused)
{
//this part does not block progress, that's OK
loadDB();
publishProgress(0);
//long UI thread operation here, blocks progress!!!!
runOnUiThread(new Runnable() {
public void run() {
buildTree();
}
});
return null;
}
}
public void buildTree()
{
//build list view within for loop
int nCnt = getCountHere();
for(int =0; i<nCnt; i++)
{
progDialog.setProgress(0);
//add tree item here
}
}
}
Don't run your whole buildTree() method inside the UI thread.
Instead, run only the changes you want to make to the UI in the UI thread:
protected Void doInBackground(final Void... unused)
{
//this part does not block progress, that's OK
loadDB();
publishProgress(0);
buildTree();
return null;
}
And then:
public void buildTree()
{
//build list view within for loop
int nCnt = getCountHere();
for(int =0; i<nCnt; i++)
{
progDialog.setProgress(0);
runOnUiThread(new Runnable() {
public void run() {
// update your UI here and return
}
});
// now you can update progress
publishProgress(i);
}
}
You should call AsyncTask's publishProgress method and not the progDialog.setProgress(0); as you call.
Also the buildTree shouln't run on the UI thread since it will block it.
run the logic from the doInBackground method.
note that you don't actually build the ListView, rather you should build it's data model.
look here
something like this:
protected Void doInBackground(final Void... unused)
{
//this part does not block progress, that's OK
loadDB();
publishProgress(0);
buildTree();
}
public void buildTree()
{
//build list view within for loop
int nCnt = getCountHere();
for(int =0; i<nCnt; i++)
{
publishProgress(i); //for exmple...
//add tree item here
}
}