Improve performance of toggleCheckmark

pull/77/merge
Alinson S. Xavier 10 years ago
parent 5763d76167
commit bc54f3f916

@ -25,6 +25,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest; import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Repetition;
import org.isoron.uhabits.unit.HabitFixtures; import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -37,6 +38,7 @@ import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Random; import java.util.Random;
import static junit.framework.Assert.assertFalse;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
@ -174,4 +176,16 @@ public class RepetitionListTest extends BaseTest
from = to - 5 * DateHelper.millisecondsInOneDay; from = to - 5 * DateHelper.millisecondsInOneDay;
assertThat(habit.repetitions.count(from, to), equalTo(3)); assertThat(habit.repetitions.count(from, to), equalTo(3));
} }
@Test
public void getOldest()
{
long expectedOldestTimestamp = DateHelper.getStartOfToday() - 9 * DateHelper.millisecondsInOneDay;
assertThat(habit.repetitions.getOldestTimestamp(), equalTo(expectedOldestTimestamp));
Repetition oldest = habit.repetitions.getOldest();
assertFalse(oldest == null);
assertThat(oldest.timestamp, equalTo(expectedOldestTimestamp));
}
} }

@ -20,11 +20,13 @@
package org.isoron.uhabits.helpers; package org.isoron.uhabits.helpers;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import com.activeandroid.ActiveAndroid; import com.activeandroid.ActiveAndroid;
import com.activeandroid.Cache;
import com.activeandroid.Configuration; import com.activeandroid.Configuration;
import org.isoron.uhabits.BuildConfig; import org.isoron.uhabits.BuildConfig;
@ -163,4 +165,20 @@ public class DatabaseHelper
ActiveAndroid.initialize(dbConfig); ActiveAndroid.initialize(dbConfig);
} }
public static long longQuery(String query, String args[])
{
Cursor c = null;
try
{
c = Cache.openDatabase().rawQuery(query, args);
if (!c.moveToFirst()) return 0;
return c.getLong(0);
}
finally
{
if(c != null) c.close();
}
}
} }

@ -21,10 +21,10 @@ package org.isoron.uhabits.models;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.activeandroid.ActiveAndroid;
import com.activeandroid.Cache; import com.activeandroid.Cache;
import com.activeandroid.query.Delete; import com.activeandroid.query.Delete;
import com.activeandroid.query.Select; import com.activeandroid.query.Select;
@ -134,10 +134,9 @@ public class CheckmarkList
*/ */
protected void computeAll() protected void computeAll()
{ {
Repetition oldestRep = habit.repetitions.getOldest(); long fromTimestamp = habit.repetitions.getOldestTimestamp();
if(oldestRep == null) return; if(fromTimestamp == 0) return;
Long fromTimestamp = oldestRep.timestamp;
Long toTimestamp = DateHelper.getStartOfToday(); Long toTimestamp = DateHelper.getStartOfToday();
compute(fromTimestamp, toTimestamp); compute(fromTimestamp, toTimestamp);
@ -150,9 +149,9 @@ public class CheckmarkList
* @param from timestamp for the beginning of the interval * @param from timestamp for the beginning of the interval
* @param to timestamp for the end of the interval * @param to timestamp for the end of the interval
*/ */
protected void compute(long from, long to) protected void compute(long from, final long to)
{ {
long day = DateHelper.millisecondsInOneDay; final long day = DateHelper.millisecondsInOneDay;
Checkmark newestCheckmark = findNewest(); Checkmark newestCheckmark = findNewest();
if(newestCheckmark != null) if(newestCheckmark != null)
@ -165,9 +164,9 @@ public class CheckmarkList
.selectFromTo(fromExtended, to) .selectFromTo(fromExtended, to)
.execute(); .execute();
int nDays = (int) ((to - from) / day) + 1; final int nDays = (int) ((to - from) / day) + 1;
int nDaysExtended = (int) ((to - fromExtended) / day) + 1; int nDaysExtended = (int) ((to - fromExtended) / day) + 1;
int checks[] = new int[nDaysExtended]; final int checks[] = new int[nDaysExtended];
for (Repetition rep : reps) for (Repetition rep : reps)
{ {
@ -187,24 +186,38 @@ public class CheckmarkList
checks[i] = Checkmark.CHECKED_IMPLICITLY; checks[i] = Checkmark.CHECKED_IMPLICITLY;
} }
ActiveAndroid.beginTransaction();
long timestamps[] = new long[nDays];
for (int i = 0; i < nDays; i++)
timestamps[i] = to - i * day;
insert(timestamps, checks);
}
private void insert(long timestamps[], int values[])
{
String query = "insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
SQLiteDatabase db = Cache.openDatabase();
db.beginTransaction();
try try
{ {
for (int i = 0; i < nDays; i++) SQLiteStatement statement = db.compileStatement(query);
statement.bindString(1, habit.getId().toString());
for (int i = 0; i < timestamps.length; i++)
{ {
Checkmark c = new Checkmark(); statement.bindLong(2, timestamps[i]);
c.habit = habit; statement.bindLong(3, values[i]);
c.timestamp = to - i * day; statement.execute();
c.value = checks[i];
c.save();
} }
ActiveAndroid.setTransactionSuccessful(); db.setTransactionSuccessful();
} }
finally finally
{ {
ActiveAndroid.endTransaction(); db.endTransaction();
} }
} }

@ -28,7 +28,9 @@ import com.activeandroid.Cache;
import com.activeandroid.query.Delete; import com.activeandroid.query.Delete;
import com.activeandroid.query.From; import com.activeandroid.query.From;
import com.activeandroid.query.Select; import com.activeandroid.query.Select;
import com.activeandroid.util.SQLiteUtils;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import java.util.Arrays; import java.util.Arrays;
@ -96,22 +98,21 @@ public class RepetitionList
timestamp = DateHelper.getStartOfDay(timestamp); timestamp = DateHelper.getStartOfDay(timestamp);
if (contains(timestamp)) if (contains(timestamp))
{
delete(timestamp); delete(timestamp);
}
else else
{ insert(timestamp);
Repetition rep = new Repetition();
rep.habit = habit;
rep.timestamp = timestamp;
rep.save();
}
habit.scores.invalidateNewerThan(timestamp); habit.scores.invalidateNewerThan(timestamp);
habit.checkmarks.deleteNewerThan(timestamp); habit.checkmarks.deleteNewerThan(timestamp);
habit.streaks.deleteNewerThan(timestamp); habit.streaks.deleteNewerThan(timestamp);
} }
private void insert(long timestamp)
{
String[] args = { habit.getId().toString(), Long.toString(timestamp) };
SQLiteUtils.execSql("insert into Repetitions(habit, timestamp) values (?,?)", args);
}
/** /**
* Returns the oldest repetition for the habit. If there is no repetition, returns null. * Returns the oldest repetition for the habit. If there is no repetition, returns null.
* Repetitions in the future are discarded. * Repetitions in the future are discarded.
@ -124,6 +125,21 @@ public class RepetitionList
return (Repetition) select().limit(1).executeSingle(); return (Repetition) select().limit(1).executeSingle();
} }
/**
* Returns the timestamp of the oldest repetition. If there are no repetitions, returns zero.
* Repetitions in the future are discarded.
*
* @return timestamp of the oldest repetition
*/
public long getOldestTimestamp()
{
String[] args = { habit.getId().toString(), Long.toString(DateHelper.getStartOfToday()) };
String query = "select timestamp from Repetitions where habit = ? and timestamp <= ? " +
"order by timestamp limit 1";
return DatabaseHelper.longQuery(query, args);
}
/** /**
* Returns the total number of repetitions for each month, from the first repetition until * Returns the total number of repetitions for each month, from the first repetition until
* today, grouped by day of week. The repetitions are returned in a HashMap. The key is the * today, grouped by day of week. The repetitions are returned in a HashMap. The key is the

@ -21,6 +21,7 @@ package org.isoron.uhabits.models;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -28,8 +29,8 @@ import com.activeandroid.Cache;
import com.activeandroid.query.Delete; import com.activeandroid.query.Delete;
import com.activeandroid.query.From; import com.activeandroid.query.From;
import com.activeandroid.query.Select; import com.activeandroid.query.Select;
import com.activeandroid.util.SQLiteUtils;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import java.io.IOException; import java.io.IOException;
@ -104,10 +105,9 @@ public class ScoreList
*/ */
private void computeAll() private void computeAll()
{ {
Repetition oldestRep = habit.repetitions.getOldest(); long fromTimestamp = habit.repetitions.getOldestTimestamp();
if(oldestRep == null) return; if(fromTimestamp == 0) return;
long fromTimestamp = oldestRep.timestamp;
long toTimestamp = DateHelper.getStartOfToday(); long toTimestamp = DateHelper.getStartOfToday();
compute(fromTimestamp, toTimestamp); compute(fromTimestamp, toTimestamp);
} }
@ -136,28 +136,51 @@ public class ScoreList
from = newestScore.timestamp + day; from = newestScore.timestamp + day;
final int checkmarkValues[] = habit.checkmarks.getValues(from, to); final int checkmarkValues[] = habit.checkmarks.getValues(from, to);
final int firstScore = newestScoreValue;
final long beginning = from; final long beginning = from;
DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command()
int lastScore = newestScoreValue;
int size = checkmarkValues.length;
long timestamps[] = new long[size];
long values[] = new long[size];
for (int i = 0; i < checkmarkValues.length; i++)
{
int checkmarkValue = checkmarkValues[checkmarkValues.length - i - 1];
lastScore = Score.compute(freq, lastScore, checkmarkValue);
timestamps[i] = beginning + day * i;
values[i] = lastScore;
}
insert(timestamps, values);
}
private void insert(long timestamps[], long values[])
{
String query = "insert into Score(habit, timestamp, score) values (?,?,?)";
SQLiteDatabase db = Cache.openDatabase();
db.beginTransaction();
try
{ {
@Override SQLiteStatement statement = db.compileStatement(query);
public void execute() statement.bindString(1, habit.getId().toString());
for (int i = 0; i < timestamps.length; i++)
{ {
int lastScore = firstScore; statement.bindLong(2, timestamps[i]);
statement.bindLong(3, values[i]);
for (int i = 0; i < checkmarkValues.length; i++) statement.execute();
{
int checkmarkValue = checkmarkValues[checkmarkValues.length - i - 1];
Score s = new Score();
s.habit = habit;
s.timestamp = beginning + day * i;
s.score = lastScore = Score.compute(freq, lastScore, checkmarkValue);
s.save();
}
} }
});
db.setTransactionSuccessful();
}
finally
{
db.endTransaction();
}
} }
/** /**
@ -185,9 +208,9 @@ public class ScoreList
*/ */
public int getValue(long timestamp) public int getValue(long timestamp)
{ {
Score s = get(timestamp); computeAll();
if(s == null) return 0; String[] args = { habit.getId().toString(), Long.toString(timestamp) };
else return s.score; return SQLiteUtils.intQuery("select score from Score where habit = ? and timestamp = ?", args);
} }
/** /**

Loading…
Cancel
Save