The AsyncTask class starts many other threads because i am using a third party library. I am not sure how many threads the library opens but i want to wait for them all to finish before populating my listview with the data they fetch.
At the moment I am sleeping 10000 milliseconds but this is not practical because I don't know how big the list it.
What is the correct solution for this problem?
task = new mTask();
task.execute(appsList);
new Thread(new Runnable() {
public void run() {
populateList();
}
}).start();
}
private class mTask extends AsyncTask<List<ApplicationInfo>, Void, Void> {
ProgressDialog progress;
@Override
protected void onPreExecute() {
progress = new ProgressDialog(MainActivity.this);
progress.setIndeterminate(true);
progress.show();
super.onPreExecute();
}
@SuppressWarnings("deprecation")
@Override
protected Void doInBackground(List<ApplicationInfo>... params) {
appDataManager = new AppDataManager(MainActivity.this,
mySQLiteAdapter, MainActivity.this);
appDataManager.work(params[0]);
return null;
}
@Override
protected void onPostExecute(Void result) {
mySQLiteAdapter.close();
progress.dismiss();
super.onPostExecute(result);
}
}
@SuppressWarnings("deprecation")
public void populateList() {
try {
Thread.sleep(10000)
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
runOnUiThread(new Runnable() {
public void run() {
Cursor cursor;
mySQLiteAdapter.openToRead();
cursor = mySQLiteAdapter.queueAll();
ArrayList<String> appsList = new ArrayList<String>();
if (cursor.moveToFirst()) {
do {
appsList.add(cursor.getString(1));
} while (cursor.moveToNext());
}
cursor.moveToFirst();
ArrayAdapter<String> adp = new ArrayAdapter<String>(
MainActivity.this, android.R.layout.simple_list_item_1,
appsList);
listContent.setAdapter(adp);
cursor.close();
mySQLiteAdapter.close();
Log.i("finished", "finished");
}
});
}
AppDataManager
public void work(List<ApplicationInfo> appsList) {
List<ApplicationInfo> appList = appsList;
mySQLiteAdapter.openToWrite();
mySQLiteAdapter.deleteAll();
mySQLiteAdapter.close();
for (int i = 0; i < 5; i++) {
String name = appList.get(i).name;
String pack = appList.get(i).packageName;
// TODO AsyncTask
getHtml(pack, name);
}
}
public void getHtml(final String pack, final String name) {
String url = MARKET_URL + pack;
//AndroidQuery library. fetch html
aq.ajax(url, String.class, 1000, new AjaxCallback<String>() {
@Override
public void callback(String url, String htm, AjaxStatus status) {
Log.i("status", status.getMessage());
parseHtml(htm, pack, name);
}
});
}
First, I would move the call to populateList
into the onPostExecute
method of your AsyncTask
. I would also rewrite populateList
to remove the sleep and assume that it is running on the UI thread (eliminate the runOnUiThread
call and move the body of the run
method directly into populateList
).
Now to prevent the AsyncTask
from completing doInBackground
until the AppDataManager
finishes its work. Start by defining a completion flag and a lock object on which you can synchronize:
private class mTask extends AsyncTask<List<ApplicationInfo>, Void, Void> {
boolean complete;
static Object LOCK = new Object();
. . .
}
Then modify the AppDataManager
class to provide a call-back to another object when the work is done. Define a field callback
and update your api and methods:
public void work(List<ApplicationInfo> appsList, Runnable callback) {
this.callback = callback; // define a field named "callback"
List<ApplicationInfo> appList = appsList;
mySQLiteAdapter.openToWrite();
mySQLiteAdapter.deleteAll();
mySQLiteAdapter.close();
for (int i = 0; i < 5; i++) {
String name = appList.get(i).name;
String pack = appList.get(i).packageName;
// TODO AsyncTask
getHtml(pack, name);
}
}
public void getHtml(final String pack, final String name) {
String url = MARKET_URL + pack;
//AndroidQuery library. fetch html
aq.ajax(url, String.class, 1000, new AjaxCallback<String>() {
@Override
public void callback(String url, String htm, AjaxStatus status) {
Log.i("status", status.getMessage());
parseHtml(htm, pack, name);
if (callback != null) {
callback.run();
}
}
});
}
Now modify your doInBackground
method to wait for the flag to be set:
protected Void doInBackground(List<ApplicationInfo>... params) {
appDataManager = new AppDataManager(MainActivity.this,
mySQLiteAdapter, MainActivity.this);
appDataManager.work(params[0], new Runnable() {
public void run() {
synchronized (LOCK) {
complete = true;
LOCK.notifyAll();
}
}
});
// wait for appDataManager.work() to finish...
synchronized (LOCK) {
while (!complete) {
LOCK.wait();
}
}
return null;
}
This should do the job. However, you probably should elaborate on this to deal with errors of various sorts (eg, provide an error notification mechanism for AppDataManager
).
UPDATE I finally noticed that you are doing five network transactions in AppDataManager
. So instead of running the callback immediately inside the callback method for ajax()
, decrement a counter and only call back once the counter reaches 0. Initialize the counter to 5 in work()
before you enter the loop that calls getHtml()
. Since the counter will be modified by separate threads, access to it needs to be synchronized. (Alternatively, you can use an AtomicInteger
for the counter.
CyclicBarrier seems like the solution if you're waiting for threads to reach a common point
Also, instead of creating a thread to call populatelist()
which is itselt creating the thread runOnUiThread
, I'd look at publishProgress()
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.