diff --git a/.gitignore b/.gitignore index 1c7687f38..8b44c8900 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ docs/ gen/ local.properties crowdin.yaml +local diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java index 912009ece..36f4df5d3 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java @@ -24,6 +24,7 @@ import android.content.*; import android.os.*; import android.support.annotation.*; import android.support.test.*; +import android.util.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.preferences.*; @@ -31,6 +32,7 @@ import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.utils.*; import org.junit.*; +import java.io.*; import java.util.*; import java.util.concurrent.*; @@ -132,4 +134,18 @@ public class BaseAndroidTest fail(); } } + + protected void startTracing() + { + File dir = FileUtils.getFilesDir(targetContext, "Profile"); + assertNotNull(dir); + String tracePath = dir.getAbsolutePath() + "/performance.trace"; + Log.d("PerformanceTest", String.format("Saving trace file to %s", tracePath)); + Debug.startMethodTracingSampling(tracePath, 0, 1000); + } + + protected void stopTracing() + { + Debug.stopMethodTracing(); + } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/performance/PerformanceTest.java b/app/src/androidTest/java/org/isoron/uhabits/performance/PerformanceTest.java new file mode 100644 index 000000000..765c4cd3a --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/performance/PerformanceTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.performance; + +import android.support.test.filters.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.junit.*; + +@MediumTest +public class PerformanceTest extends BaseAndroidTest +{ + private Habit habit; + + @Override + public void setUp() + { + super.setUp(); + habit = fixtures.createLongHabit(); + } + + @Test(timeout = 1000) + public void testRepeatedGetTodayValue() + { + for (int i = 0; i < 100000; i++) + { + habit.getScores().getTodayValue(); + habit.getCheckmarks().getTodayValue(); + } + } + +} diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java index 86488a8d1..cf617628f 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -108,7 +108,7 @@ public abstract class CheckmarkList * * @return value of today's checkmark */ - public final int getTodayValue() + public int getTodayValue() { Checkmark today = getToday(); if (today != null) return today.getValue(); diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java index 7c9b552e0..42de31712 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java @@ -24,7 +24,6 @@ import android.support.annotation.*; import android.support.annotation.Nullable; import com.activeandroid.*; -import com.activeandroid.query.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.sqlite.records.*; @@ -38,38 +37,54 @@ import java.util.*; */ public class SQLiteCheckmarkList extends CheckmarkList { + @Nullable private HabitRecord habitRecord; @NonNull private final SQLiteUtils sqlite; + @Nullable + private Integer todayValue; + + @NonNull + private final SQLiteStatement invalidateStatement; + + @NonNull + private final SQLiteStatement addStatement; + + @NonNull + private final SQLiteDatabase db; + + private static final String ADD_QUERY = + "insert into Checkmarks(habit, timestamp, value) values (?,?,?)"; + + private static final String INVALIDATE_QUERY = + "delete from Checkmarks where habit = ? and timestamp >= ?"; + public SQLiteCheckmarkList(Habit habit) { super(habit); sqlite = new SQLiteUtils<>(CheckmarkRecord.class); + + db = Cache.openDatabase(); + addStatement = db.compileStatement(ADD_QUERY); + invalidateStatement = db.compileStatement(INVALIDATE_QUERY); } @Override public void add(List checkmarks) { check(habit.getId()); - - String query = - "insert into Checkmarks(habit, timestamp, value) values (?,?,?)"; - - SQLiteDatabase db = Cache.openDatabase(); db.beginTransaction(); try { - SQLiteStatement statement = db.compileStatement(query); - for (Checkmark c : checkmarks) { - statement.bindLong(1, habit.getId()); - statement.bindLong(2, c.getTimestamp()); - statement.bindLong(3, c.getValue()); - statement.execute(); + addStatement.bindLong(1, habit.getId()); + addStatement.bindLong(2, c.getTimestamp()); + addStatement.bindLong(3, c.getValue()); + addStatement.execute(); } db.setTransactionSuccessful(); @@ -115,12 +130,10 @@ public class SQLiteCheckmarkList extends CheckmarkList @Override public void invalidateNewerThan(long timestamp) { - new Delete() - .from(CheckmarkRecord.class) - .where("habit = ?", habit.getId()) - .and("timestamp >= ?", timestamp) - .execute(); - + todayValue = null; + invalidateStatement.bindLong(1, habit.getId()); + invalidateStatement.bindLong(2, timestamp); + invalidateStatement.execute(); observable.notifyListeners(); } @@ -179,4 +192,11 @@ public class SQLiteCheckmarkList extends CheckmarkList for (CheckmarkRecord r : records) checkmarks.add(r.toCheckmark()); return checkmarks; } + + @Override + public int getTodayValue() + { + if(todayValue == null) todayValue = super.getTodayValue(); + return todayValue; + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java index 6278863e9..758ac993a 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java @@ -19,12 +19,12 @@ package org.isoron.uhabits.models.sqlite; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; +import android.database.*; +import android.database.sqlite.*; import android.support.annotation.*; import android.support.annotation.Nullable; -import com.activeandroid.Cache; +import com.activeandroid.*; import com.activeandroid.query.*; import org.isoron.uhabits.models.*; @@ -43,10 +43,16 @@ public class SQLiteRepetitionList extends RepetitionList @Nullable private HabitRecord habitRecord; + private SQLiteStatement addStatement; + public SQLiteRepetitionList(@NonNull Habit habit) { super(habit); sqlite = new SQLiteUtils<>(RepetitionRecord.class); + + SQLiteDatabase db = Cache.openDatabase(); + String addQuery = "insert into repetitions(habit, timestamp) values (?,?)"; + addStatement = db.compileStatement(addQuery); } /** @@ -61,11 +67,9 @@ public class SQLiteRepetitionList extends RepetitionList public void add(Repetition rep) { check(habit.getId()); - - RepetitionRecord record = new RepetitionRecord(); - record.copyFrom(rep); - record.habit = habitRecord; - record.save(); + addStatement.bindLong(1, habit.getId()); + addStatement.bindLong(2, rep.getTimestamp()); + addStatement.execute(); observable.notifyListeners(); } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java index e44e7e6ed..f5c291900 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java @@ -24,7 +24,6 @@ import android.support.annotation.*; import android.support.annotation.Nullable; import com.activeandroid.*; -import com.activeandroid.query.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.sqlite.records.*; @@ -37,12 +36,30 @@ import java.util.*; */ public class SQLiteScoreList extends ScoreList { + @Nullable private HabitRecord habitRecord; @NonNull private final SQLiteUtils sqlite; + @Nullable + private Integer todayValue; + + @NonNull + private final SQLiteStatement invalidateStatement; + + @NonNull + private final SQLiteStatement addStatement; + + public static final String ADD_QUERY = + "insert into Score(habit, timestamp, score) values (?,?,?)"; + + public static final String INVALIDATE_QUERY = + "delete from Score where habit = ? " + "and timestamp >= ?"; + + private final SQLiteDatabase db; + /** * Constructs a new ScoreList associated with the given habit. * @@ -52,28 +69,25 @@ public class SQLiteScoreList extends ScoreList { super(habit); sqlite = new SQLiteUtils<>(ScoreRecord.class); + + db = Cache.openDatabase(); + addStatement = db.compileStatement(ADD_QUERY); + invalidateStatement = db.compileStatement(INVALIDATE_QUERY); } @Override public void add(List scores) { check(habit.getId()); - String query = - "insert into Score(habit, timestamp, score) values (?,?,?)"; - - SQLiteDatabase db = Cache.openDatabase(); db.beginTransaction(); - try { - SQLiteStatement statement = db.compileStatement(query); - for (Score s : scores) { - statement.bindLong(1, habit.getId()); - statement.bindLong(2, s.getTimestamp()); - statement.bindLong(3, s.getValue()); - statement.execute(); + addStatement.bindLong(1, habit.getId()); + addStatement.bindLong(2, s.getTimestamp()); + addStatement.bindLong(3, s.getValue()); + addStatement.execute(); } db.setTransactionSuccessful(); @@ -126,12 +140,10 @@ public class SQLiteScoreList extends ScoreList @Override public void invalidateNewerThan(long timestamp) { - new Delete() - .from(ScoreRecord.class) - .where("habit = ?", habit.getId()) - .and("timestamp >= ?", timestamp) - .execute(); - + todayValue = null; + invalidateStatement.bindLong(1, habit.getId()); + invalidateStatement.bindLong(2, timestamp); + invalidateStatement.execute(); getObservable().notifyListeners(); } @@ -204,4 +216,11 @@ public class SQLiteScoreList extends ScoreList for (ScoreRecord r : records) scores.add(r.toScore()); return scores; } + + @Override + public int getTodayValue() + { + if (todayValue == null) todayValue = super.getTodayValue(); + return todayValue; + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java index 8cf10b49f..8d6068579 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java @@ -19,10 +19,11 @@ package org.isoron.uhabits.models.sqlite; +import android.database.sqlite.*; import android.support.annotation.*; import android.support.annotation.Nullable; -import com.activeandroid.query.*; +import com.activeandroid.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.sqlite.records.*; @@ -41,10 +42,17 @@ public class SQLiteStreakList extends StreakList @NonNull private final SQLiteUtils sqlite; + private final SQLiteStatement invalidateStatement; + public SQLiteStreakList(Habit habit) { super(habit); sqlite = new SQLiteUtils<>(StreakRecord.class); + + SQLiteDatabase db = Cache.openDatabase(); + String invalidateQuery = "delete from Streak where habit = ? " + + "and end >= ?"; + invalidateStatement = db.compileStatement(invalidateQuery); } @Override @@ -73,12 +81,9 @@ public class SQLiteStreakList extends StreakList @Override public void invalidateNewerThan(long timestamp) { - new Delete() - .from(StreakRecord.class) - .where("habit = ?", habit.getId()) - .and("end >= ?", timestamp - DateUtils.millisecondsInOneDay) - .execute(); - + invalidateStatement.bindLong(1, habit.getId()); + invalidateStatement.bindLong(2, timestamp - DateUtils.millisecondsInOneDay); + invalidateStatement.execute(); observable.notifyListeners(); }