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
}
}
Related
I know how to use AsyncTask to download file, create a zip file or so.. as I call publishProgress() in my loop.
I got stuck when doInBackground() has a single slow line, no loops here, just creating an object where its constructor has slow loops.
I'm not sure about the reasonable way of updating progress in such case.
Here's a sample code:
public class Session {
private QQActivity activity;
public int createdParts;
public DailyClass daily;
private void checkDaily() {
if(!isDailyReady){
new SetAsyncQQDaily().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
class SetAsyncQQDaily extends AsyncTask<Void, Void, String> {
#Override
protected String doInBackground(Void... params) {
String sdq = null;
daily = new DailyClass(Session.this); //Very very Slow!
// Do other network http
sdq = new String(Base64.encode(bos.toByteArray(),Base64.DEFAULT));
// Do some work
return sdq;
}
#Override
protected void onPostExecute(String sdq) {
//Never mind
}
#Override
protected void onPreExecute() {
Toast.makeText(activity,"Preparing the daily. Get ready!",Toast.LENGTH_LONG).show();
}
#Override
protected void onProgressUpdate(Void... values) {
//TODO: Update Value of leftBar
activity.leftBar.setProgress((100*createdParts)/Utils.DAILY_PART_COUNT);
}
}
}
In the slow constructor class, I can set-back an integer of the current progress: createdParts, but cannot call publishProgress.
public class DailyClass implements Serializable {
public DailyClass(Session session){
for(int i=1;i<=partCount;i++ ){ //Very slow loop
session.createdParts = i; //TODO: reflect value to progress bar!?
for(int j=0;j<questionsCount;j++){
objects[i-1][j] = createDefined(i);
}
Log.d("Daily","created part"+i);
}
}
//Bla .. !
}
I also though of passing the object of the AsyncTask to the slow constructor in order to call publishProgress() from there, but cannot. As publishProgress() is accessible only from doInBackground()
What's the best practice?
I have a catch 22 here. If I use a AsyncTask to run my network activity I can't update my user interface from that thread.
MainActivity.onCreate(...){
myAsyncTask.execute();
//E/AndroidRuntime(1177): Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
}
The Network activity will be continuous and needs to happen on a different thread. So, I turned to super.runOnUiThread to fix the error above and because it accepts Runnable as its parameter. Unfortunatly, the Javadocs are not clear in that I don't know if super.runOnUiThread is going to make a Thread or just call run directly. Apparently it does not make a Thread, because I get this exception: android.os.NetworkOnMainThreadException
Given that I have a one-screen App that need a connection. What is the simplest way to make this work?
If I use a AsyncTask to run my network activity I can't update my user interface from that thread
That is why AsyncTask has onPostExecute(). Put your UI update logic in there (or onProgressUpdate(), if you wish to update the UI as the background work is going on).
As CommonsWare said you can use onProgressUpdate() to update your UI .Here is an example, I used it to make a cool splash screen.
https://www.dropbox.com/s/cyz7112k4m1booh/1.png
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
bar=(ProgressBar) findViewById(R.id.progressBar);
new PrefetchData().execute();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.splash, menu);
return true;
}
private class PrefetchData extends AsyncTask<String,String,String> {
#Override
protected void onPreExecute() {
super.onPreExecute();
// before making http calls
}
#Override
protected String doInBackground(String... arg0) {
Random ran=new Random();
int count;
int total=0;
try {
while(total <= 100){
count=ran.nextInt(30);
total+=count;
Thread.sleep(1000);
if (total >= 100) publishProgress(""+100);
//here publishProgress() will invoke onProgressUpdate() automatically .
else publishProgress(""+(int) total);
}
}catch (InterruptedException e) {
e.printStackTrace();
Log.e("Error:",e.getMessage());
}
return null;
}
protected void onProgressUpdate(String... progress) {
bar.setProgress(Integer.parseInt(progress[0]));
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
Intent i = new Intent(SplashActivity.this, MainActivity.class);
startActivity(i);
finish();
}
}
I'm following this tutorial
to learn how to make progress bars. I'm trying to show the progress bar on top of my activity and have it update the activity's table view in the background.
So I created an async task for the dialog that takes a callback:
package com.lib.bookworm;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
public class UIThreadProgress extends AsyncTask<Void, Void, Void> {
private UIThreadCallback callback = null;
private ProgressDialog dialog = null;
private int maxValue = 100, incAmount = 1;
private Context context = null;
public UIThreadProgress(Context context, UIThreadCallback callback) {
this.context = context;
this.callback = callback;
}
#Override
protected Void doInBackground(Void... args) {
while(this.callback.condition()) {
this.callback.run();
this.publishProgress();
}
return null;
}
#Override protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
dialog.incrementProgressBy(incAmount);
};
#Override
protected void onPreExecute() {
super.onPreExecute();
dialog = new ProgressDialog(context);
dialog.setCancelable(true);
dialog.setMessage("Loading...");
dialog.setProgress(0);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMax(maxValue);
dialog.show();
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (this.dialog.isShowing()) {
this.dialog.dismiss();
}
this.callback.onThreadFinish();
}
}
In My Activity:
final String page = htmlPage.substring(start, end).trim();
//Create new instance of the AsyncTask..
new UIThreadProgress(this, new UIThreadCallback() {
#Override
public void run() {
row_id = makeTableRow(row_id, layout, params, matcher); //ADD a row to the table layout.
}
#Override
public void onThreadFinish() {
System.out.println("FINISHED!!");
}
#Override
public boolean condition() {
return matcher.find();
}
}).execute();
So the above creates an async task to run to update a table layout activity while showing the progress bar that displays how much work has been done..
However, I get an error saying that only the thread that started the activity can update its views. I tried changing my Async Task's run to the following:
MainActivity.this.runOnUiThread(new Runnable() {
#Override public void run() {
row_id = makeTableRow(row_id, layout, params, matcher); //ADD a row to the table layout.
}
}
But this gives me synchronization errors.. Any ideas how I can display progress and at the same time update my table in the background?
Currently my UI looks like:
Whatever update that you are doing in the UI do it in progress update, use Global Variables to pass values or use Getter Setter.
Here is a simple example, from one of my current project.
It changes the width of the LinearLayout, which acts as progress bar and also updates the textview with X%. Am updating by calling onProgressUpdate
public class Updater extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
width = getWindowManager().getDefaultDisplay().getWidth();
Log.wtf(tag, "width" + width);
}
#Override
protected Void doInBackground(Void... params) {
while (updated < sleep) {
try {
Thread.sleep(updateEveryXmillSec);
updated = updated + updateEveryXmillSec;
publishProgress();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
mTextView.setText((int) (100 * updated / sleep) + " %");
xwidth = (width * ((int) (100 * updated / sleep)) / 100);
mLayout.setLayoutParams(new RelativeLayout.LayoutParams(xwidth,
height));
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
startActivity(new Intent(getApplicationContext(), Main.class));
finish();
}
}
Call new Updater().execute(); to trigger the action.
You should split your UI data from the Row Data. Make a RowObject which contains the data to display in the table:
class RowData {
String program;
String course;
String bookName;
// get/set etc
}
You can fill this object in the UIThreadProgress class run method and push it to a synced list.
In onProcessUpdate() you can than build the View Object based on the synced list and add it to the View Hierachie. You are on the UI thread now, and adding should be possible.
You have to care about a synced list during this. Because the Background Thread and the UI Thread will adding and removing objects at the same time. a synchronized will help here. Depending on the speed of your algorithm to calculate the needed data, a faster approach than the synced list is better. But the Idea is always the same. You have to split your data and the View Operations.
I have a function that dynamically creates all my user interface.
What can I do to show a dialog progress while my function is executing, and then dismiss the dialog when my function has finished the user interface?
This is an example of my code:
Sorry, I'm new to android, it is hard for me to understand some code... I will write my code here...
I have this function:
public void principal() {
//CODE TO CREATE ALL THE USER INTERFACE DYNAMICALLY
}
and I have the asyncTask like this:
public class EjecutarTarea extends AsyncTask<Void, Void, Void>
{
protected void onPreExecute() {
dialog = new ProgressDialog(StadioNenaActivity.this);
dialog.setMessage("Cargando..");
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.show();
}
protected Void doInBackground(Void... unused) {
Principal();
return null;
}
#Override
protected void onProgressUpdate(Void... unused) {
}
#Override
protected void onPostExecute(Void unused) {
dialog.dismiss();
}
}
When I execute the asynctask in the onCreate, it crashes:
new EjecutarTarea().execute();
Use an AsyncTask. It gives a fantastic and easy way to load stuff in the background and them paste views in the main thread.
Here is an example of my AsyncTask:
private class LoadingTask extends AsyncTask<Void, Integer, Void> {
private ProgressBar mProg;
private TextView mLoadingText;
#Override protected void onPreExecute() {
mProg = (ProgressBar)findViewById(R.id.launcherbar_loadprogress);
mProg.setTouchDelegate(null);
mProg.setClickable(false);
mProg.setLongClickable(false);
mProg.setOnTouchListener(null);
mProg.setMax(100);
mLoadingText = (TextView) findViewById(R.id.launcheractivity_loadingwhat);
}
#Override protected Void doInBackground(Void... voids) {
try { Thread.sleep(1000); } catch (InterruptedException e) { }
setProgressAndSleep(R.string.loading_log, 29, 250);
LOG.setContext(LauncherActivity.this);
LOG.setDebug(true);
setProgressAndSleep(R.string.loading_database, 43, 250);
AppionDatabase.setContext(LauncherActivity.this);
setProgressAndSleep(R.string.loading_sensors, 57, 250);
Sensor.init(LauncherActivity.this);
setProgressAndSleep(R.string.loading_jobs, 71, 250);
Job.init(LauncherActivity.this);
setProgressAndSleep(R.string.loading_workbenches, 86, 250);
WorkbenchState.init(LauncherActivity.this);
setProgressAndSleep(R.string.loading_application_state, 100, 250);
ApplicationState.setContext(LauncherActivity.this);
startService(new Intent(LauncherActivity.this, BluetoothConnectionService.class));
return null;
}
#Override public void onProgressUpdate(Integer... prog) {
mLoadingText.setText(prog[0]);
mProg.setProgress(prog[1]);
}
#Override public void onPostExecute(Void voids) {
startActivity(new Intent(LauncherActivity.this, HomescreenActivity.class));
}
private void setProgressAndSleep(int text, int progress, int duration) {
try {
publishProgress(new Integer[] {text, progress});
Thread.sleep(duration);
} catch (InterruptedException e) {
LOG.e(TAG, "Failed to sleep thread while loading Application Contexts!", e);
}
}
}
Edit NOTE I recommend not keeping the setProgressAndSleep(int, int int) method. I only use it cause it loads too fast and I really wanted the loading bar. :-P
All changes in Android UI will makes in UI Thread. To run your function in UI thread you need to use mathos runOnUIThread() from activity http://developer.android.com/reference/android/app/Activity.html#runOnUiThread(java.lang.Runnable)
Hope it help you!
Your best shot is, probably, to use ViewSwitcher to have temporary view with progress bar while you construct other view. Do operations that take time in doInBackground() of AsyncTask but actual operations on UI such as flipping the view must be done in postExecute() or via runOnUIThread(). Progress updates may be done in onProgressUpdate()
I am facing the issue with displaying progressbar onItem selected in option menu.
My code is here:
case R.id.mnuLogout:
showDialog(Constants.PROGRESS_DIALOG);
closeOptionsMenu();
if(MyApp.IsLoggedOut())
handler.sendEmptyMessage(Constants.LOGOUT);
else
handler.sendEmptyMessage(Constants.ERROR_MSG);
Progressbar is displayed after completion of IsLogged method.
You're calling get() right after the AsyncTask as executed and lose asynchronous behavior because this method waits until task is finished. You should add all the code in try/catch block to AsyncTask.onPostExecute() method and also dismiss the dialog from this method.
void doLogout() {
new LogoutTask().execute();
}
void dispatchLogoutFinished() {
dismissDialog(Constants.PROGRESS_DIALOG);
if (MyApp.IsLoggedOut()) {
// do something
} else {
// do something else
}
}
private class LogoutTask extends AsyncTask<Void, Void, Void> {
protected void onPreExecute() {
TheActivity.this.showDialog(Constants.PROGRESS_DIALOG);
}
protected Void doInBackground(Void... params) {
return null;
}
protected void onPostExecute(Long result) {
TheActivity.this.dispatchLogoutFinished();
}
}
And I don't think you need to send messages to the handler. The dispatchLogoutFinished() is executed on the UI thread, so there's no need for synchronization.