Cursor fetching performance in non UI thread - android

I've got a performance problem while fetching data from a cursor in AsyncTaskLoader. Testing in android API level 10. For example 2 classes with cursor - TestFragmentUI fetching data in UI thread and TestFragment fetching data in none UI thread.
public class TestFragment extends Fragment implements LoaderManager.LoaderCallbacks<Object> {
...
#Override
public void onActivityCreated(Bundle savedInstanceState) {
...
getLoaderManager().initLoader(0, null, this);
...
}
public android.support.v4.content.Loader<Object> onCreateLoader(
int id, Bundle args) {
return new Loader(getActivity());
}
class Loader extends AsyncTaskLoader<Object> {
public Loader(Context context) {
super(context);
}
public Object loadInBackground() {
...
DataBaseHelper helper = new DataBaseHelper(getContext());
SQLiteDatabase database = helper.getReadableDatabase();
long start = System.currentTimeMillis();
Cursor data = database.rawQuery(String.format(SQL_LOAD, parametr), null);
while (data.moveToNext()) {
String number = data.getString(data.getColumnIndex("number"));
}
data.close();
Log.i(TAG, "load: " + (System.currentTimeMillis() - start));
...
}
}
...
}
and
public class TestFragmentUI extends Fragment {
...
#Override
public void onActivityCreated(Bundle savedInstanceState) {
...
DataBaseHelper helper = new DataBaseHelper(getActivity());
SQLiteDatabase database = helper.getReadableDatabase();
...
long start = System.currentTimeMillis();
Cursor data = database.rawQuery(String.format(SQL_LOAD, parametr), null);
while (data.moveToNext()) {
String number = data.getString(data.getColumnIndex("number"));
}
data.close();
Log.i(TAG, "load: " + (System.currentTimeMillis() - start));
...
}
...
}
In emulator TestFragment class output time that is 10 times slower than output TestFragmentUI and visually it is very noticeable.
Any ideas?

use it on a real device (your phone) to test since emulators are slow

The answer was to change background process priority (Process.setThreadPriority) coz by default AsyncTask runs at background priority.

Related

Android Service can't open database, because (activity / context /this) is null

I need from MyService (extends Service) start new AsyncLoadJSONDoc(this).execute(), but problem is "this" can't be there.
If im working from Activity there is no problem, i just have to create constructor for taking activity in asynctask.
The main problem is i need OPEN my database below in asynctask from service (null pointer exception)
Service is checking cloud for new information and saving in the android database. (every minute)
public class AsyncLoadJSONDoc extends AsyncTask<URL, Integer, ArrayList<Storage.JSONDocument>> {
private static final String TAG = "AsyncLoadJSONDoc";
Activity mActivity;`
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'kk:mm:ss.SSS'Z'");
Date date = new Date();
private static final String TAG = "AsyncLoadJSONDoc";
Activity mActivity;
public AsyncLoadJSONDoc(Activity activity){
mActivity = activity;
}
#Override
protected ArrayList<Storage.JSONDocument> doInBackground(URL... params) {
StorageService storageService = App42API.buildStorageService();
Storage storage = storageService.findAllDocuments(
Constants.CLOUD_DB_NAME, Constants.COLLECTION_NAME);
ArrayList<Storage.JSONDocument> jsonDocList = storage.getJsonDocList();
Log.i(TAG, "Velikost databaze> " + jsonDocList.size());
return jsonDocList;
}
protected void onPostExecute(ArrayList<Storage.JSONDocument> jsonDocList) {
saveToDatabase(jsonDocList);
}
private void saveToDatabase(ArrayList<Storage.JSONDocument> jsonDocs) {
Log.i(TAG, "saveToDatabase");
Log.i(TAG + " Start>", df.format(date));
SQLiteDatabase db = (new DatabaseHelper(mActivity)).getWritableDatabase(); // <---There is null exception
ContentValues cv=new ContentValues();
DatabaseHelper dq = new DatabaseHelper(mActivity);
dq.onUpgrade(db, 1, 2);
for(Storage.JSONDocument oneJsonDoc : jsonDocs) {
cv.put(Constants.UPDATED_AT, oneJsonDoc.getUpdatedAt());
cv.put(Constants.CREATED_AT, oneJsonDoc.getCreatedAt());
cv.put(Constants.DOC_ID, oneJsonDoc.getDocId());
cv.put(Constants.JSON, oneJsonDoc.getJsonDoc());
if (oneJsonDoc.getLocation() == null) {
Log.i(TAG, "Location NULL");
cv.put(Constants.LATITUDE, (byte[]) null);
cv.put(Constants.LONGITUDE, (byte[]) null);
} else {
cv.put(Constants.LATITUDE, oneJsonDoc.getLocation().getLat().floatValue());
cv.put(Constants.LONGITUDE, oneJsonDoc.getLocation().getLng().floatValue());
}
db.insert(Constants.TABLE_NAME, Constants.JSON, cv);
Log.i(TAG, "insert");
}
db.close();
Log.i(TAG + " End>", df.format(date));
}
}
that is a very simple fix, the DatabaseOpenHelper only needs a context, not the actual activity, so u can just change the constructor:
public class AsyncLoadJSONDoc extends AsyncTask<URL, Integer, ArrayList<Storage.JSONDocument>> {
private Context mContext;
public AsyncLoadJSONDoc(Context context){
mContext = context.getApplicationContext();
}
and replace with mContext in new DatabaseHelper(mContext)
Also you should delete those lines:
DatabaseHelper dq = new DatabaseHelper(mActivity);
dq.onUpgrade(db, 1, 2);
They are simply wrong. You should never call onUpgrade, the system automatically calls it for you when necessary.

Releasing instance of Fragment without UI when setRetainInstance(true)

I need to add a row to my SQLiteDatabase and as a result i want to obtain newly created ID of the row. I extended AsyncTask class in order to run this task in the background thread, here it is:
private class InsertLangTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
try {
if (mDB.isOpen()) {
try {
mId = mDB.insertOrThrow(DBS.D.TN, null, mCV);
}
catch (SQLException e) {
StringBuilder query = new StringBuilder();
query.append("SELECT " + BaseColumns._ID + " FROM " + DBS.D.TN + " WHERE ");
Object[] bindArgs = new Object[mCV.size()];
int i = 0;
for (Map.Entry<String, Object> entry: mCV.valueSet()) {
query.append((i > 0) ? " AND " : "");
query.append(entry.getKey());
query.append(" = ?");
bindArgs[i++] = entry.getValue();
}
SQLiteStatement statement = mDB.compileStatement(query.toString());
for (i = 0; i < bindArgs.length; i++) {
DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]);
}
try {
mId = statement.simpleQueryForLong();
} finally {
statement.close();
}
}
}
}
catch (NullPointerException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
if (mActivity != null) { mActivity.onLangInserted(mId); }
super.onPostExecute(result);
}
}
This InsertLangTask class is enclosed in Fragment class where i implements only onCreate(), onAttach() and onDetach(), also i create a method inside to initialize members (cannot do it in constructor, as it must be empty in Fragment classes). I wrap AsyncTask in this Fragment class in order to preserve my mId member and pass it back to my activity even if the configuration was changed and activity was destroyed. I know that it's not exactly the case of long-term operation, but it can possibly be. Here are the parts of this class:
public class InsertLangFragment extends Fragment {
public static interface OnLangInsertedListener {
void onLangInserted(long id);
}
SQLiteDatabase mDB = null;
ContentValues mCV = null;
private OnLangInsertedListener mActivity = null;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
public void RunTask(SQLiteDatabase db, ContentValues cv) {
new InsertLangTask().execute();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onDetach() {
super.onDetach();
mActivity = null;
}
}
And in my Activity i just write this when i want to start this process:
InsertLangFragment fragment = new InsertLangFragment();
getFragmentManager().beginTransaction().add(fragment, "insertTaskFragment").commit();
fragment.RunTask(mSQLiteDatabase, mContentValues);
Obtaining mId in implemented callback
public void onLangInserted(long id) {
}
I used setRetainInstance(true) so this fragment will never be destroyed (except back button) and will be located in memory all time. The question is how should i properly destroy fragment instance? I suppose that i have three possible outcomes:
1) It's not necessary to destroy this Fragments.
2) I should put getFragmentManager().remove("insertTaskFragment").commit() at the end of onLangInserted() in my Activity
3) I should put setRetainInstance(false) at the end of onPostExecute() in AsyncTask.
Please give me an advice how should i manage this Fragment properly.
Thank you. Please don't be too strict, i'm a newbie here.

Software design: where to put database open and close

I'm developing an android 3.1 application.
This question is not specific for Android, it is about how to design a class that access a database. I asked here because my code is for Android.
I have a class, DBManager, to work with Sqlite database. This is a part of its implementation:
public class DBManager
{
// Variable to hold the database instance
private SQLiteDatabase db;
// Database open/upgrade helper
private DatabaseHelper dbHelper;
public DBManager(Context _context)
{
Log.v("DBManager", "constructor");
dbHelper = new DatabaseHelper(_context, SqlConstants.DATABASE_NAME, null, SqlConstants.DATABASE_VERSION);
}
public DBManager open() throws SQLException
{
Log.v("DBManager", "open");
db = dbHelper.getWritableDatabase();
return this;
}
public void close()
{
Log.v("DBManager", "close");
db.close();
}
...
/**
* Query all forms available locally.
* #return A list with all forms (form.name and form.FormId) available on local db
* or null if there was a problem.
*/
public ArrayList<Form> getAllForms()
{
Log.v("DBManager", "getAllForms");
ArrayList<Form> list = null;
Cursor c = null;
try
{
c = this.getAllFormsCursor();
if (c != null)
{
int formNameIndex = c.getColumnIndex(SqlConstants.COLUMN_FORM_NAME);
int formIdIndex = c.getColumnIndex(SqlConstants.COLUMN_FORM_ID);
c.moveToFirst();
if (c.getCount() > 0)
{
list = new ArrayList<Form>(c.getCount());
do
{
Form f = new Form();
f.Name = c.getString(formNameIndex);
f.FormId = c.getString(formIdIndex);
list.add(f);
}
while (c.moveToNext());
}
}
}
catch (Exception e)
{
e.printStackTrace();
list = null;
}
finally
{
if (c != null)
c.close();
}
return list;
}
private Cursor getAllFormsCursor()
{
Log.v("DBManager", "getAllFormsCursor");
return db.query(SqlConstants.TABLE_FORM,
new String[] {
SqlConstants.COLUMN_FORM_ID,
SqlConstants.COLUMN_FORM_NAME}, null, null, null, null, null);
}
}
And this is an AsyncTask that uses DBManager:
private class DbFormListAsyncTask extends AsyncTask<Void, Void, ArrayList<Form>>
{
private Context mContext;
private ProgressDialog loadingDialog;
private DBManager dbMan;
DbFormListAsyncTask(Context context)
{
this.mContext = context;
loadingDialog = new ProgressDialog(mContext);
loadingDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
loadingDialog.setMessage("Retriving forms. Please wait...");
loadingDialog.setCancelable(false);
loadingDialog.show();
}
#Override
protected ArrayList<Form> doInBackground(Void... arg0)
{
dbMan = new DBManager(mContext);
dbMan.open();
return dbMan.getAllForms();
}
protected void onPostExecute(ArrayList<Form> forms)
{
if (forms != null)
{
ListActivity act = (ListActivity) mContext;
act.setListAdapter(new AvaFormAdapter(act, R.layout.ava_list_item, forms));
}
else
{
TextView errorMsg = (TextView)
((FormsListActivity) mContext).findViewById(R.id.formErrorMsg);
errorMsg.setText("Problem getting forms. Please try again later.");
}
loadingDialog.dismiss();
if (dbMan != null)
dbMan.close();
}
}
As you can see I have to:
Create DBManager instance.
Open database with dbMan.open()
Call dbMan.getAllForms()
Close database with dbMan.close() on onPostExecute.
I thought that I could add db.open() and db.close() on dbMan.getAllForms() to avoid calling it every time I use dbMan.getAllForms().
What do you think about this? What is the best approach?
I would put it inside getAllForms() or do something like that
protected ArrayList<Form> doInBackground(Void... arg0)
{
dbMan = new DBManager(mContext);
dbMan.open();
ArrayList<Form> resutl = dbMan.getAllForms();
dbMan.close();
return result;
}
Since you don't need the db connection after you have the result you can close it right away.
Edit: if you run that AsyncTask several times then opening/closing will introduce unnecessary overhead. In that case you may want to instanciate the dbManager from your Activity (maybe open() in the constructor of DbManager) and close it once you leave your activity. Then pass Dbmanager to the AsyncTask.
Make your database helper class a singleton, and don't explicitly close the SQLiteDatabase. It will be closed and flushed when your app's process exits.

How to use managedQuery from an asyncTask

I am trying to make an Android music player. To make things easier, I have decided to copy the Artists on the phone to a local DB and then make some custom queries to the local data. I know how to copy the managedQuery to a db, but cannot do so on an AsyncTask since managedQuery is only accessible by an Activity class. I am trying to do this call in my Application class upon app startup. Does anyone know a way to call managedQuery inside of the AsyncTask? I really do not want to do this in my first activity that is called since it will slow my load speed significantly.
This is what I would like to do, although I know this will not compile...
public class AplayApplication extends Application implements
OnSharedPreferenceChangeListener {
private static final String TAG = AplayApplication.class.getSimpleName();
private SharedPreferences prefs;
protected MusicData musicData;
protected PlayerHandler mMediaPlayer;
protected boolean isPlaying;
private boolean prefUseDefaultShuffle;
private boolean prefUseSmartShuffle;
private int prefArtistSkipDuration;
private int prefUnheardArtistPct;
protected TabHost tabHost;
protected Song currentSong;
protected int currentSongPosition;
private static final String PREFERENCE_KEY = "seekBarPreference";
protected boolean hasLoadedSongs;
private static AplayApplication aplayapp;
#Override
public void onCreate() {
super.onCreate();
prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.registerOnSharedPreferenceChangeListener(this);
setPrefs();
Log.i(TAG, "Application started");
mMediaPlayer = new PlayerHandler();
// code in question below this line
musicData = new MusicData(this); // this creates instance of database helper to access db
// will call execute on async task here.
// new getArtist().execute();
}
private class getArtists extends AsyncTask<Void, Void, Boolean>{
Cursor artCursor;
#Override
protected Boolean doInBackground(Void... params) {
String[] proj = {
MediaStore.Audio.Artists._ID,MediaStore.Audio.Artists.ARTIST,
};
artCursor = managedQuery(
MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, proj, null,
null, MediaStore.Audio.Artists.ARTIST + " ASC");
ContentValues values = new ContentValues();
artCursor.moveToPosition(-1);
while (artCursor.moveToNext()) {
values.put(
MusicData.S_DISPLAY,
newMusicCursor.getString(newMusicCursor
.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME)));
values.put(MusicData.S_ARTIST, newMusicCursor
.getString(newMusicCursor
.getColumnIndex(MediaStore.Audio.Media.ARTIST)));
values.put(MusicData.S_FILE, newMusicCursor
.getString(newMusicCursor
.getColumnIndex(MediaStore.Audio.Media.DATA)));
this.musicData.insertMastSong(values);
}
return true;
}
//// code continues.....
As Sparky says, you could use CursorLoader instead of managedQuery.
If you are developing for sdk 8 you need to add Support Package to your project.
To avoid delay your application start maybe you could use a Service.
This is a little example for use a service, get the data from an url and then insert it to the database
public class GetArticlesService extends IntentService {
private static final String TAG = "GetArticlesService";
public GetArticlesService() {
super("GetdArticlesService");
}
/* (non-Javadoc)
* #see android.app.IntentService#onHandleIntent(android.content.Intent)
*/
#Override
protected void onHandleIntent(Intent intent) {
String url = "http://example.com/artists.json";
String response = null;
try {
response = Utils.httpPost(getApplicationContext(), url + "/api/GetArticles", null);
} catch (HttpHostConnectException e) {
e.printStackTrace();
}
if(!TextUtils.isEmpty(response)){
try {
JSONArray list = new JSONArray(response);
if(list.length() > 0){
ContentValues toInsert = new ContentValues[];
JSONObject art = null;
int cant = list.length();
for(int i = 0;i< cant; i++){
toInsert.clear();
art = list.getJSONObject(i);
toInsert = new ContentValues();
toInsert.put(Articles._ID, art.getInt("id"));
toInsert.put(Articles.DESCRIPTION, art.getString("description"));
toInsert.put(Articles.BARCODE, art.getString("barcode"));
toInsert.put(Articles.RUBRO, art.getString("rubro"));
toInsert.put(Articles.CLAVE, art.getString("clave"));
getContentResolver().inert(Articles.CONTENT_URI, toInsert);
}
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
managedQuery is deprecated. Use CursorLoader instead.

I have problems when using sqlite inside AsyncTask in android?

I face two main problems when using a sqlite command inside an AsncTask in android.
When I execute a select command on the first try I get no results but on the second try (loading a activity that has this Asynctask) I do get results.
Sometimes I get an error that the database is not closed despite that it is already closed/
What is the problem with this?
UPDATE:
This is the code that retrive data from database (db.getAllMessage)
private ArrayList<FriendMessagesResulted> getMessagesFromCach(Context c){
FriendMessagesResulted messagesResulted1 = new FriendMessagesResulted();
DBAdapter db = new DBAdapter(c);
Cursor c1;
db.open();
c1 = db.getAllMessage(Settings.getCurrentUserId(c),Integer.parseInt(fId));
Log.d("***Database count",c1.getCount()+" from: "+Settings.getCurrentUserId(c)+" to:"+Integer.parseInt(fId));
c1.moveToFirst();
if(c1.getCount()>0)
status=true;
if (messagesResultedList == null) {
messagesResultedList = new ArrayList<FriendMessagesResulted>();
}
else
messagesResultedList.clear();
while (c1.isAfterLast() == false) {
if(Integer.parseInt(c1.getString(0))>maxId)
maxId=Integer.parseInt(c1.getString(0));
messagesResulted1.set_mId(Integer.parseInt(c1.getString(0)));
messagesResulted1.set_msgTxt("MD:"+c1.getString(3));
messagesResulted1.set_MessageTime(c1.getString(4));
messagesResulted1.set_dir(c1.getString(5));
messagesResultedList.add(messagesResulted1);
c1.moveToNext();
}
db.close();
c1.close();
return messagesResultedList;
}
and this the code for AsyncTask, where I call get getMessagesFromCach method
private void getMessages(final Context c)
{
handler = new Handler();
r=new Runnable() {
public void run() {
class RecentMessageLoader extends AsyncTask<Void, Void, ArrayList<FriendMessagesResulted>>{
ArrayList<FriendMessagesResulted> messagesResultedList=null;
#Override
protected ArrayList<FriendMessagesResulted> doInBackground(Void... params) {
if(!finishLoadingPastMessages)
{
messagesResultedList=getMessagesFromCach(c);
if(!status){
Log.d("Where are you","I'm in JSON");
messagesResultedList=getMessagesFromJSON(c);
}
}
else{
Log.d("Where are you","I'm in Recent messages");
messagesResultedList=getRecentMessages(c,Settings.getCurrentUserId(c),Integer.parseInt(fId));
}
return messagesResultedList;
}
protected void onPostExecute( ArrayList<FriendMessagesResulted> FMRList ) {
// to disappear loading message
d.dismiss();
finishLoadingPastMessages=true;
if(FMRList!=null){
for(int i=FMRList.size()-1;i>=0;i--)
addMessage(FMRList.get(i),c);
}
handler.postDelayed(r, 2000);
}
}
new RecentMessageLoader().execute();
}
};
handler.post(r);
}
UPDATE 2 : Cach class ..
public class Cach {
static DBAdapter db;
public Cach(Context c)
{
}
public static void AddMessages(Context c,
int id,
int fromId,
int toId,
String message,
String dir,
String MessageTime)
{
db = new DBAdapter(c);
db.open();
long id2;
id2 = db.insertMessage(id, fromId, toId, message, dir,MessageTime);
db.close();
}
}
It seems the problem is with the type of variables you are using.. there must be Static variables of instance variables which are getting set from many sources... try not to use static variables and use local variables I mean in the methods implicitly.

Categories

Resources