do not update InstalledAppProvider if already current

This adds a check of whether the database has the current APK in it, based
on PackageInfo's lastUpdateTime field.  This avoids recalculating the hash
of the whole APK, which is quite time and resource intensive.
This commit is contained in:
Hans-Christoph Steiner 2016-05-30 23:01:50 +02:00
parent 90467bf8bf
commit 748352e5a1
2 changed files with 18 additions and 10 deletions

View File

@ -27,12 +27,12 @@ public class InstalledAppProvider extends FDroidProvider {
public static class Helper { public static class Helper {
/** /**
* @return The keys are the app ids (package names), and their corresponding values are * @return The keys are the package names, and their corresponding values are
* the version code which is installed. * the {@link PackageInfo#lastUpdateTime last update time} in milliseconds.
*/ */
public static Map<String, Integer> all(Context context) { public static Map<String, Long> all(Context context) {
Map<String, Integer> cachedInfo = new HashMap<>(); Map<String, Long> cachedInfo = new HashMap<>();
final Uri uri = InstalledAppProvider.getContentUri(); final Uri uri = InstalledAppProvider.getContentUri();
final String[] projection = InstalledAppProvider.DataColumns.ALL; final String[] projection = InstalledAppProvider.DataColumns.ALL;
@ -43,7 +43,7 @@ public class InstalledAppProvider extends FDroidProvider {
while (!cursor.isAfterLast()) { while (!cursor.isAfterLast()) {
cachedInfo.put( cachedInfo.put(
cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)), cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)),
cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE)) cursor.getLong(cursor.getColumnIndex(DataColumns.LAST_UPDATE_TIME))
); );
cursor.moveToNext(); cursor.moveToNext();
} }
@ -223,6 +223,11 @@ public class InstalledAppProvider extends FDroidProvider {
return getAppUri(values.getAsString(DataColumns.PACKAGE_NAME)); return getAppUri(values.getAsString(DataColumns.PACKAGE_NAME));
} }
/**
* Update is not supported for {@code InstalledAppProvider}. Instead, use
* {@link #insert(Uri, ContentValues)}, and it will overwrite the relevant
* row, if one exists. This just throws {@link UnsupportedOperationException}
*/
@Override @Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
throw new UnsupportedOperationException("\"Update' not supported for installed appp provider. Instead, you should insert, and it will overwrite the relevant rows if one exists."); throw new UnsupportedOperationException("\"Update' not supported for installed appp provider. Instead, you should insert, and it will overwrite the relevant rows if one exists.");

View File

@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit;
* versus what Android says is installed, or processing {@link Intent}s that come * versus what Android says is installed, or processing {@link Intent}s that come
* from {@link android.content.BroadcastReceiver}s for {@link Intent#ACTION_PACKAGE_ADDED} * from {@link android.content.BroadcastReceiver}s for {@link Intent#ACTION_PACKAGE_ADDED}
* and {@link Intent#ACTION_PACKAGE_REMOVED} * and {@link Intent#ACTION_PACKAGE_REMOVED}
* <p> * <p/>
* Since {@link android.content.ContentProvider#insert(Uri, ContentValues)} does not check * Since {@link android.content.ContentProvider#insert(Uri, ContentValues)} does not check
* for duplicate records, it is entirely the job of this service to ensure that it is not * for duplicate records, it is entirely the job of this service to ensure that it is not
* inserting duplicate versions of the same installed APK. On that note, * inserting duplicate versions of the same installed APK. On that note,
@ -94,7 +94,7 @@ public class InstalledAppProviderService extends IntentService {
* is in sync with what the {@link PackageManager} tells us is installed. Once * is in sync with what the {@link PackageManager} tells us is installed. Once
* completed, the relevant {@link android.content.ContentProvider}s will be * completed, the relevant {@link android.content.ContentProvider}s will be
* notified of any changes to installed statuses. * notified of any changes to installed statuses.
* <p> * <p/>
* The installed app cache could get out of sync, e.g. if F-Droid crashed/ or * The installed app cache could get out of sync, e.g. if F-Droid crashed/ or
* ran out of battery half way through responding to {@link Intent#ACTION_PACKAGE_ADDED}. * ran out of battery half way through responding to {@link Intent#ACTION_PACKAGE_ADDED}.
* This method returns immediately, and will continue to work in an * This method returns immediately, and will continue to work in an
@ -103,15 +103,18 @@ public class InstalledAppProviderService extends IntentService {
* {@link Process#THREAD_PRIORITY_LOWEST}. * {@link Process#THREAD_PRIORITY_LOWEST}.
*/ */
public static void compareToPackageManager(Context context) { public static void compareToPackageManager(Context context) {
Map<String, Integer> cachedInfo = InstalledAppProvider.Helper.all(context); Map<String, Long> cachedInfo = InstalledAppProvider.Helper.all(context);
List<PackageInfo> packageInfoList = context.getPackageManager() List<PackageInfo> packageInfoList = context.getPackageManager()
.getInstalledPackages(PackageManager.GET_SIGNATURES); .getInstalledPackages(PackageManager.GET_SIGNATURES);
// TODO check packageInfo.lastUpdateTime for freshness
for (PackageInfo packageInfo : packageInfoList) { for (PackageInfo packageInfo : packageInfoList) {
insert(context, packageInfo);
if (cachedInfo.containsKey(packageInfo.packageName)) { if (cachedInfo.containsKey(packageInfo.packageName)) {
if (packageInfo.lastUpdateTime > cachedInfo.get(packageInfo.packageName)) {
insert(context, packageInfo);
}
cachedInfo.remove(packageInfo.packageName); cachedInfo.remove(packageInfo.packageName);
} else {
insert(context, packageInfo);
} }
} }