I have a web browser application.
A AutoCompleteTextView act as a Url textbox, and being fetch a list (Cursor Type) when application start.
Below code is work well, but i don't know it correct to use AsyncTask or not.
So, did the AutoCompleteTextView will freeze will application launch?
And i monitor the thread via Eclipse thread monitor, the AsyncTask#1 thread keep in wait status when done.So how can i close the thread???
Code:
public class BrowserActivity extends StandOutWindow {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Build the layout
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.activity_browser, frame, true);
AutoCompleteTextView txtUrl = (AutoCompleteTextView) v.findViewById(R.id.txtUrl);
//Run the async task
BrowserDataTask bdTask = new BrowserDataTask();
bdTask.execute(txtUrl);
}
//A method belong to BrowserActivity class and reusable.
public Cursor getBrowserData() {
String[] projection = new String[] { "_id", Browser.BookmarkColumns.TITLE,
Browser.BookmarkColumns.URL };
Cursor mCur = getContentResolver().query(android.provider.Browser.BOOKMARKS_URI,
projection, null, null, null);
return mCur;
}
//Sub Class of BrowserActivity
private class BrowserDataTask extends AsyncTask<AutoCompleteTextView, Integer, Cursor>
{
private AutoCompleteTextView m_acText;
#Override
protected Cursor doInBackground(AutoCompleteTextView...params) {
m_acText = params[0];
return getBrowserData();
}
#Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
}
#Override
protected void onPostExecute(Cursor result) {
//UrlAdapter, custome Cursor Adapter from other class.
UrlAdapter adapter = new UrlAdapter(BrowserActivity.this, result);
m_acText.setAdapter(adapter);
}
}
}
Why is it waiting?
AyncTask uses ThreadPoolExecutor and hence you they might not get destroyed but rather kept, because destroying and reinitializing them would be a definite waste. In case you really want to close it, call cancel() on it, that might help.
Will it freeze the app?
No, it won't.
Also, you are never setting your progress!
Related
I checked questions asked in StackOverflow but couldnt make to solve my issue.
I am trying to get some data in ListView in asynchronous way (to not overload UI Thread) and while i get the data i want to show a ProgressDialog. I am getting the data using ContentProvider and it loads the data successfully in ListView, however the progressDialog is not spinning, but after the list is shown with data it starts spinning and never stops (Keeps spinning).
How can i make ProgressDialog spin and dismiss() after the ListView is Visible.
Here is the activity which implements LoaderCallbacks:
public class AttractionsActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {
DatabaseHelper dbHelper;
private SimpleCursorAdapter cursorAdapter;
private ListView attractionsListView;
private ProgressDialog progressDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_listview);
attractionsListView = (ListView)findViewById(R.id.lvCustom);
progressDialog = new ProgressDialog(this);
progressDialog.setMessage("Loading");
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dbHelper = new DatabaseHelper(this);
dbHelper.open();
populateAttractionsListView();
dbHelper.close();
}
private void populateAttractionsListView() {
//final ImageView image = (ImageView) findViewById(R.id.ivAttImage);
//Initialize Loader
getLoaderManager().initLoader(0, null, this);
String[] from = {PoisContract.COLUMN_POI_MAIN_PIC, PoisContract.COLUMN_POI_NAME};
int[] to = {R.id.ivAttImage, R.id.tvAttName};
//Initialize CursorAdapter
cursorAdapter = new SimpleCursorAdapter(this, R.layout.attractions_listview_row, null, from, to, 0);
ViewBinder viewBinder = new ViewBinder() {
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
boolean binded = false;
if(view != null){
String imgName = cursor.getString(columnIndex);
int imgResId = getResources().getIdentifier(imgName, "drawable", getPackageName());
((ImageView) view).setImageResource(imgResId);
binded = true;
}
return binded;
}
};
//Set Adapter but there is no cursor right now
attractionsListView.setAdapter(cursorAdapter);
//Set Binder to the adapter
cursorAdapter.setViewBinder(viewBinder);
//Hide ListView as there is no data returned
attractionsListView.setVisibility(View.GONE);
}
private Cursor getAllAttractions(){
ContentResolver resolver = getContentResolver();
return resolver.query(PoisContract.CONTENT_URI, PoisContract.PROJECTION, null, null, null);
}
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
progressDialog.show(this, "Please wait...", "Loading List...");
CursorLoader cursorLoader = new CursorLoader(AttractionsActivity.this, PoisContract.CONTENT_URI, PoisContract.PROJECTION, null, null, null);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
cursorAdapter.swapCursor(cursor);
progressDialog.dismiss();
attractionsListView.setVisibility(View.VISIBLE);
}
#Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
//Data is not available anymore, delete reference
cursorAdapter.swapCursor(null);
}
however the progressDialog is not spinning, but after the list is
shown with data it starts spinning
This is happening because, despite (correctly)using a Loader to load the data, you also manually query the provider on the main UI thread through the call to the getAllAttractions() method(in the onCreate() method) which will block the UI thread. In fact I don't know why you did that as the returned Cursor is not used at all. Remove the:
Cursor cursor = getAllAttractions();
line from your activity's onCreate() method to solve this issue.
...and never stops (Keeps spinning).
This is happening because you call dismiss on the wrong ProgressDialog reference so you don't cancel the currently showing ProgressDialog. In the onCreate() method of the Activity you create a ProgressDialog reference, however in the onCreateLoader() callback you use the show() method which creates a new instance of a ProgressDialog(the method is static) so when you try to dismiss it in onLoadFinished() you dismiss the previously created ProgressDialog(which isn't even showing). To solve it use:
progressDialog = ProgressDialog.show(this, "Please wait...", "Loading List...");
in the onCreateLoader() callback.
As a side note you should always call a static method of a class by using the class name and not some object reference of that class, this will make thing more clear for you and also anyone who would later read your code.
Ok #Luksprog thank you very much for helping. I found the issue. The problem was in the ContentProvier AUTHORITY declaration. I misspelled the AUTHORITY String in one place. It took a day to figure out that stupid mistake.
I really appreciate your help
I am getting from time to time testing my app error:
03-04 20:57:01.929: E/TestApp(13673): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
From questions like this: Whats this exception?, and my own experience (I got this same error from time to time as in mentioned question) I would like to ask you guys what I can do to get rid of it?
As far as I know, I can do some stuff on AsyncTask connected to View, so I don't know why I am getting this info.
This is my code:
private MyDBAdapter mySQLiteAdapter;
private ListView wordList;
private AsyncDBDownload asycn;
private ProgressDialog dbUpdate;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.smart_guide_ocr);
asycn = new AsyncDBDownload();
wordList = (ListView) findViewById(R.id.wordsList);
//...
}
#Override
protected void onResume() {
super.onResume();
asycn.execute(null);
}
private class AsyncDBDownload extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... params) {
try {
refreshList();//upload of contetn and set of adapter
} catch (Exception ex) {
Log.e(TAG, ex.toString());
}
return null;
}
#Override
protected void onPostExecute(String result) {
dbUpdate.dismiss();
}
#Override
protected void onPreExecute() {
dbUpdate = ProgressDialog.show(TestAppActivity.this, "Wait",
"DB download");
}
}
private void refreshList() {
mySQLiteAdapter = new MyDBAdapter(TestAppActivity.this);
mySQLiteAdapter.open();
String[] columns = { MyDBAdapter.KEY_TRANSLATED, MyDBAdapter.KEY_WORD, MyDBAdapter.KEY_LANG,
MyDBAdapter.KEY_ID };
Cursor contentRead = mySQLiteAdapter.getAllEntries(false, columns,
null, null, null, null, MyDBAdapter.KEY_ID, null);
startManagingCursor(contentRead);
Log.d(TAG, Integer.toString(contentRead.getCount()));
RowItem adapterCursor = new RowItem(this, R.layout.save_word_row,
contentRead, columns, new int[] { R.id.translatedWord, R.id.orgWord, R.id.langInfo }, 0);
wordList.setAdapter(adapterCursor);
mySQLiteAdapter.close();
}
You must not call wordList.setAdapter(adapterCursor); from within refresList method. That's a way of "changing a view from a non-UI thread".
So, instead, save the adapterCursor instance and use it from within the onPostExecute method.
You can not manipulate your Views within a background task. Do all the loading you need in your AsyncTask, pass it back into the activity in onPostExecute and set your adapter then. Doing any form of UI manipulation in a background task or service will throw this error.
I have a problem with calling the setadapter() method from the inside of AsyncTaskActivity onPostExecute() method. I've read docs about AsyncTask: http://developer.android.com/reference/android/os/AsyncTask.html#onPostExecute(Result) but found no answer how to achieve that. Should I use an Intent to do so? I'm new to a Android development so please forgive me if it's a kind of stupid question.
MainActivity
public class MainActivity extends ListActivity {
private String[] columns = new String[] {"foreign_word", "native_word"};
private int[] target = new int[] { R.id.foreign_word, R.id.native_word };
private Cursor cur = null;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
new AsyncTaskActivity(this).execute();
}
public void setadapter(Cursor cur) {
SimpleCursorAdapter aaa = new SimpleCursorAdapter(this.getApplicationContext(), R.layout.list_entry, cur, columns, target, 0);
this.setListAdapter(aaa);
}
}
AsyncTaskActivity
public class AsyncTaskActivity extends AsyncTask<Void, Void, Cursor> {
Activity activity = null;
private SQLiteDatabase db = null;
private String[] selection = {"_id", "native_word", "foreign_word"};
Cursor cur = null;
public AsyncTaskActivity(Activity activity) {
this.activity = activity;
}
#Override
protected Cursor doInBackground(Void... params) {
DbAdapter mSQLadapter = new DbAdapter(activity);
db = mSQLadapter.getWritableDatabase();
Cursor cur = db.query("words", selection, "foreign_word='car'", null, null, null, null);
return cur;
}
protected void onPostExecute(Cursor cur) {
// I'd like to call setadapter() ,passing it Cursor as a parameter
}
}
Assuming that your AsyncTask is always called by MainActivity, do this
protected void onPostExecute(Cursor cur) {
MainActivity mActivity = (MainActivity) activity;
mActivity.setadapter();
}
You can specify more parameters for your setAdapter method so you can pass off the Cursor, String Array, etc.
However an even better method if the AsyncTask is only called from MainActivity is to make this AsyncTask a private inner class inside MainActivity. This will allow it to access all the global variables and methods of MainActivity
Your approach is a little unusual, but to answer the question asked:
Change activity to a ListActivity:
ListActivity activity = null;
And use:
protected void onPostExecute(Cursor cur) {
activity.setListAdapter(new SimpleCursorAdapter(activity, R.layout.list_entry, cur, columns, target, 0));
}
You should look into using the LoaderManager class, it creates and handles background threads for you. You should also consider using a ContentProvider, it handles a lot of busy work for you as well.
Lastly AsyncTaskActivity is not an Activity, so this class name is a little confusing...
I have a Tabs Activity that contains Fragments for tabs. One of the tabs displays a list of data from a SQLite databse table (or a different Fragment if the table is empty). When initially creating the tabs, the Tabs Activity checks to see if the table contains any data. If it does, then it initialises the list Fragment but doesn't set up the CursorAdapter. The CursorAdapter is initialised by an AsyncTask which syncs the SQLite database with a central database, and then creates the Cursors and CursorAdapters. The list Fragment displays a ProgressDialog while waiting for the AsyncTask to create the CursorAdapter. When the CurserAdapter is initialised, the ProgressDialog is dismissed but the ListView remains on its 'empty list' view, despite calls to notifyDataSetChanged(). If I switch tabs and come back, the ListView displays the data correctly. How can I make the ListView update once the CursorAdapter has been initialised?
Relevant bits of code:
Tabs:
private static ImageCursorAdapter friendCursorAdapter = null;
public static ImageCursorAdapter getFriendsListAdapter() {
return friendCursorAdapter;
}
public static void setFriendsListAdapter(ImageCursorAdapter adapter) {
friendCursorAdapter = adapter;
friendCursorAdapter.notifyDataSetChanged();
}
SyncTask:
protected Void doInBackground(Void... params) {
sql = "SELECT COUNT(*) FROM " + WhereWolfOpenHelper.FRIEND_TABLE_NAME;
statement = db.compileStatement(sql);
count = statement.simpleQueryForLong();
if(count>0) {
friendCursor = db.query(WhereWolfOpenHelper.FRIEND_TABLE_NAME, null, null, null, null, null, WhereWolfOpenHelper.FRIEND_FIRST_NAME_COLUMN+", "+WhereWolfOpenHelper.FRIEND_LAST_NAME_COLUMN);
}
statement.close();
}
#Override
protected void onPostExecute(Void param) {
if(friendCursor!=null) {
ImageCursorAdapter adapter = new ImageCursorAdapter(WhereWolfActivity.this, friendCursor, 0, ImageCursorAdapter.FRIENDS);
Tabs.setFriendsListAdapter(adapter);
}
}
FriendsList:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(Tabs.getFriendsListAdapter());
}
#Override
public void onStart() {
super.onStart();
if(Tabs.getFriendsListAdapter()==null) {
final ProgressDialog dialog = ProgressDialog.show(getActivity(), getString(R.string.loadingtitle), getString(R.string.loading), true, false);
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while(Tabs.getFriendsListAdapter()==null) {}
dialog.dismiss();
Tabs.getFriendsListAdapter().notifyDataSetChanged();
}
});
thread.start();
}
}
You are creating a new adapter, but I don't see any code to set the adapter to the list. Since the list knows nothing about your brand new adapter, calling notifyDataSetChanged() doesn't help. BTW, if you are already using fragments, you might consider using a Loader.
I have a SQLite query returning thousands of rows, which I want to visualize using a ListView.
In order to keep my UI thread responsive, I create the ListAdapter on a background thread.
However the statement that takes most time (and can cause ANR) is ListActivity.setListAdapter which I have to execute on the UI thread... Any advice?
public class CursorTestActivity extends ListActivity {
private static final String LOGTAG = "DBTEST";
private DatabaseManager mDbManager;
private Cursor mCursor;
private HandlerThread mIOWorkerThread;
private Handler mIOHandler;
private Handler mUIHandler;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDbManager = new DatabaseManager(this);
mUIHandler = new Handler();
createIOWorkerThread();
log("creating cursor");
mCursor = mDbManager.getCursor(); // does db.query(...)
startManagingCursor(mCursor);
mIOHandler.post(new Runnable() {
#Override
public void run() {
setMyListAdapter();
}
});
log("onCreate done");
}
private void setMyListAdapter() {
log("constructing adapter");
// CustomCursorAdapter implements bindView and newView
final CustomCursorAdapter listAdapter = new CustomCursorAdapter(this,
mCursor, false);
log("finished constructing adapter");
mUIHandler.post(new Runnable() {
#Override
public void run() {
log("setting list adapter");
setListAdapter(listAdapter); // gets slower the more rows are returned
log("setting content view");
setContentView(R.layout.main);
log("done setting content view");
}
});
}
private void createIOWorkerThread() {
mIOWorkerThread = new HandlerThread("io_thread");
mIOWorkerThread.start();
Looper looper = mIOWorkerThread.getLooper();
mIOHandler = new Handler(looper);
}
private void destroyIOWorkerThread() {
if (mIOWorkerThread == null)
return;
Looper looper = mIOWorkerThread.getLooper();
if (looper != null) {
looper.quit();
}
}
#Override
public void onDestroy() {
super.onDestroy();
if (mDbManager != null)
mDbManager.close();
destroyIOWorkerThread();
}
private static void log(String s) {
Log.d(LOGTAG, s);
}
}
Cursors are lazy loaded therefore at first data access the cursor is loaded - getCount is such access. The setAdapter method is invoking getCount on cursor - there is the performance issue.
You can set empty cursor in adapter and display empty list during loading cursor. Then use changeCursor method in adapter to change cursor to the new one.
You can fetch at first for example 100 rows in the first query, then load more rows in the background and changeCursor to the new one.
Or you can create own implementation of Cursor that has own implementation of getCount and fetches demanded rows on request.
consider using PagedListAdapter. it's something similar as proposed by #pawelziemba but provided by Android Framework https://developer.android.com/reference/android/arch/paging/PagedListAdapter -- if you can use Room framework you can get that for free
load data async in ViewHolder - remember to cancel work if viewHolder gets rebinded and cache loaded data if needed