android - tengo - Cómo evitar cargar siempre datos de aplicaciones en caché desde Google Drive
porque mi celular dice memoria llena si no tengo nada (1)
- La búsqueda en Google Drive es lenta. ¿Por qué no usar las propiedades de la carpeta base para almacenar la identificación del archivo zip? https://developers.google.com/drive/v2/web/properties
- Los nombres de archivo en Google Drive no son únicos, puede cargar varios archivos con los mismos nombres. El ID de archivo devuelto por Google, sin embargo, es único.
Actualmente, estoy usando la API de Android de Google Drive , para almacenar los datos de mi aplicación de Android, en la carpeta de aplicaciones de Google Drive .
Esto es lo que estoy haciendo al guardar los datos de mi aplicación
- Genere una suma de verificación para el archivo zip local actual.
- Busque en la Carpeta de la aplicación Google Drive para ver si hay un archivo zip de la Carpeta de la aplicación existente.
- Si es así, sobrescriba el contenido del archivo zip existente de la carpeta de aplicaciones, con los archivos zip locales actuales. Además, cambiaremos el nombre del nombre del archivo zip de la carpeta de aplicaciones existente, con la última suma de comprobación.
- Si no hay un archivo zip de la Carpeta de la Aplicación, genere un nuevo archivo zip de la Carpeta de la Aplicación, con el contenido del archivo zip local. Utilizaremos la última suma de comprobación como nombre de archivo zip de la carpeta de aplicaciones.
Aquí está el código que realiza las operaciones mencionadas anteriormente.
Genere un nuevo archivo zip de la carpeta de aplicaciones o actualice el archivo zip de la carpeta de aplicaciones existente
public static boolean saveToGoogleDrive(GoogleApiClient googleApiClient, File file, HandleStatusable h, PublishProgressable p) {
// Should we new or replace?
GoogleCloudFile googleCloudFile = searchFromGoogleDrive(googleApiClient, h, p);
try {
p.publishProgress(JStockApplication.instance().getString(R.string.uploading));
final long checksum = org.yccheok.jstock.gui.Utils.getChecksum(file);
final long date = new Date().getTime();
final int version = org.yccheok.jstock.gui.Utils.getCloudFileVersionID();
final String title = getGoogleDriveTitle(checksum, date, version);
DriveContents driveContents;
DriveFile driveFile = null;
if (googleCloudFile == null) {
DriveApi.DriveContentsResult driveContentsResult = Drive.DriveApi.newDriveContents(googleApiClient).await();
if (driveContentsResult == null) {
return false;
}
Status status = driveContentsResult.getStatus();
if (!status.isSuccess()) {
h.handleStatus(status);
return false;
}
driveContents = driveContentsResult.getDriveContents();
} else {
driveFile = googleCloudFile.metadata.getDriveId().asDriveFile();
DriveApi.DriveContentsResult driveContentsResult = driveFile.open(googleApiClient, DriveFile.MODE_WRITE_ONLY, null).await();
if (driveContentsResult == null) {
return false;
}
Status status = driveContentsResult.getStatus();
if (!status.isSuccess()) {
h.handleStatus(status);
return false;
}
driveContents = driveContentsResult.getDriveContents();
}
OutputStream outputStream = driveContents.getOutputStream();
InputStream inputStream = null;
byte[] buf = new byte[8192];
try {
inputStream = new FileInputStream(file);
int c;
while ((c = inputStream.read(buf, 0, buf.length)) > 0) {
outputStream.write(buf, 0, c);
}
} catch (IOException e) {
Log.e(TAG, "", e);
return false;
} finally {
org.yccheok.jstock.file.Utils.close(outputStream);
org.yccheok.jstock.file.Utils.close(inputStream);
}
if (googleCloudFile == null) {
// Create the metadata for the new file including title and MIME
// type.
MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
.setTitle(title)
.setMimeType("application/zip").build();
DriveFolder driveFolder = Drive.DriveApi.getAppFolder(googleApiClient);
DriveFolder.DriveFileResult driveFileResult = driveFolder.createFile(googleApiClient, metadataChangeSet, driveContents).await();
if (driveFileResult == null) {
return false;
}
Status status = driveFileResult.getStatus();
if (!status.isSuccess()) {
h.handleStatus(status);
return false;
}
} else {
MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
.setTitle(title).build();
DriveResource.MetadataResult metadataResult = driveFile.updateMetadata(googleApiClient, metadataChangeSet).await();
Status status = metadataResult.getStatus();
if (!status.isSuccess()) {
h.handleStatus(status);
return false;
}
}
Status status;
try {
status = driveContents.commit(googleApiClient, null).await();
} catch (java.lang.IllegalStateException e) {
// java.lang.IllegalStateException: DriveContents already closed.
Log.e(TAG, "", e);
return false;
}
if (!status.isSuccess()) {
h.handleStatus(status);
return false;
}
status = Drive.DriveApi.requestSync(googleApiClient).await();
if (!status.isSuccess()) {
// Sync request rate limit exceeded.
//
//h.handleStatus(status);
//return false;
}
return true;
} finally {
if (googleCloudFile != null) {
googleCloudFile.metadataBuffer.release();
}
}
}
Busque el archivo zip de la carpeta de aplicaciones existente
private static String getGoogleDriveTitle(long checksum, long date, int version) {
return "jstock-" + org.yccheok.jstock.gui.Utils.getJStockUUID() + "-checksum=" + checksum + "-date=" + date + "-version=" + version + ".zip";
}
// https://stackoverflow.com/questions/1360113/is-java-regex-thread-safe
private static final Pattern googleDocTitlePattern = Pattern.compile("jstock-" + org.yccheok.jstock.gui.Utils.getJStockUUID() + "-checksum=([0-9]+)-date=([0-9]+)-version=([0-9]+)//.zip", Pattern.CASE_INSENSITIVE);
private static GoogleCloudFile searchFromGoogleDrive(GoogleApiClient googleApiClient, HandleStatusable h, PublishProgressable p) {
DriveFolder driveFolder = Drive.DriveApi.getAppFolder(googleApiClient);
// https://stackoverflow.com/questions/34705929/filters-ownedbyme-doesnt-work-in-drive-api-for-android-but-works-correctly-i
final String titleName = ("jstock-" + org.yccheok.jstock.gui.Utils.getJStockUUID() + "-checksum=");
Query query = new Query.Builder()
.addFilter(Filters.and(
Filters.contains(SearchableField.TITLE, titleName),
Filters.eq(SearchableField.TRASHED, false)
))
.build();
DriveApi.MetadataBufferResult metadataBufferResult = driveFolder.queryChildren(googleApiClient, query).await();
if (metadataBufferResult == null) {
return null;
}
Status status = metadataBufferResult.getStatus();
if (!status.isSuccess()) {
h.handleStatus(status);
return null;
}
MetadataBuffer metadataBuffer = null;
boolean needToReleaseMetadataBuffer = true;
try {
metadataBuffer = metadataBufferResult.getMetadataBuffer();
if (metadataBuffer != null ) {
long checksum = 0;
long date = 0;
int version = 0;
Metadata metadata = null;
for (Metadata md : metadataBuffer) {
if (p.isCancelled()) {
return null;
}
if (md == null || !md.isDataValid()) {
continue;
}
final String title = md.getTitle();
// Retrieve checksum, date and version information from filename.
final Matcher matcher = googleDocTitlePattern.matcher(title);
String _checksum = null;
String _date = null;
String _version = null;
if (matcher.find()){
if (matcher.groupCount() == 3) {
_checksum = matcher.group(1);
_date = matcher.group(2);
_version = matcher.group(3);
}
}
if (_checksum == null || _date == null || _version == null) {
continue;
}
try {
checksum = Long.parseLong(_checksum);
date = Long.parseLong(_date);
version = Integer.parseInt(_version);
} catch (NumberFormatException ex) {
Log.e(TAG, "", ex);
continue;
}
metadata = md;
break;
} // for
if (metadata != null) {
// Caller will be responsible to release the resource. If release too early,
// metadata will not readable.
needToReleaseMetadataBuffer = false;
return GoogleCloudFile.newInstance(metadataBuffer, metadata, checksum, date, version);
}
} // if
} finally {
if (needToReleaseMetadataBuffer) {
if (metadataBuffer != null) {
metadataBuffer.release();
}
}
}
return null;
}
El problema ocurre durante la carga de datos de la aplicación. Imagine las siguientes operaciones
-
Suba datos zip a la
carpeta
de la
aplicación Google Drive
por primera vez.
La suma de verificación es
12345
. El nombre de archivo que se usa es...checksum=12345...zip
-
Busque datos zip desde la
carpeta de la aplicación Google Drive
.
Capaz de encontrar el archivo con nombre de archivo
...checksum=12345...zip
. Descargue el contenido. Verifique que la suma de comprobación del contenido también sea12345
. -
Sobrescriba los nuevos datos zip en el archivo existente de la
carpeta de la aplicación Google Drive
.
La nueva suma de comprobación de datos zip es
67890
. Se cambia el nombre del archivo zip de la carpeta de la aplicación existente a...checksum=67890...zip
-
Busque datos zip desde la
carpeta de la aplicación Google Drive
.
Capaz de encontrar el archivo con nombre de archivo
...checksum=67890...zip
. Sin embargo, después de descargar el contenido, la suma de comprobación del contenido sigue siendo12345
.
Descargar el archivo zip de la carpeta de aplicaciones
public static CloudFile loadFromGoogleDrive(GoogleApiClient googleApiClient, HandleStatusable h, PublishProgressable p) {
final java.io.File directory = JStockApplication.instance().getExternalCacheDir();
if (directory == null) {
org.yccheok.jstock.gui.Utils.showLongToast(R.string.unable_to_access_external_storage);
return null;
}
Status status = Drive.DriveApi.requestSync(googleApiClient).await();
if (!status.isSuccess()) {
// Sync request rate limit exceeded.
//
//h.handleStatus(status);
//return null;
}
GoogleCloudFile googleCloudFile = searchFromGoogleDrive(googleApiClient, h, p);
if (googleCloudFile == null) {
return null;
}
try {
DriveFile driveFile = googleCloudFile.metadata.getDriveId().asDriveFile();
DriveApi.DriveContentsResult driveContentsResult = driveFile.open(googleApiClient, DriveFile.MODE_READ_ONLY, null).await();
if (driveContentsResult == null) {
return null;
}
status = driveContentsResult.getStatus();
if (!status.isSuccess()) {
h.handleStatus(status);
return null;
}
final long checksum = googleCloudFile.checksum;
final long date = googleCloudFile.date;
final int version = googleCloudFile.version;
p.publishProgress(JStockApplication.instance().getString(R.string.downloading));
final DriveContents driveContents = driveContentsResult.getDriveContents();
InputStream inputStream = null;
java.io.File outputFile = null;
OutputStream outputStream = null;
try {
inputStream = driveContents.getInputStream();
outputFile = java.io.File.createTempFile(org.yccheok.jstock.gui.Utils.getJStockUUID(), ".zip", directory);
outputFile.deleteOnExit();
outputStream = new FileOutputStream(outputFile);
int read = 0;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
} catch (IOException ex) {
Log.e(TAG, "", ex);
} finally {
org.yccheok.jstock.file.Utils.close(outputStream);
org.yccheok.jstock.file.Utils.close(inputStream);
driveContents.discard(googleApiClient);
}
if (outputFile == null) {
return null;
}
return CloudFile.newInstance(outputFile, checksum, date, version);
} finally {
googleCloudFile.metadataBuffer.release();
}
}
Primero pensé
Status status = Drive.DriveApi.requestSync(googleApiClient).await()
no hace bien el trabajo.
Falla en la mayoría de las situaciones, con el mensaje de error Se
Sync request rate limit exceeded.
De hecho, el límite
requestSync
impuesto en
requestSync
hace que la API no sea particularmente útil -
Android Google Play / Drive Api
Sin embargo, incluso cuando
requestSync
loadFromGoogleDrive
,
loadFromGoogleDrive
solo puede obtener el último nombre de archivo, pero el contenido de la suma de verificación está desactualizado.
Estoy 100% seguro de que
loadFromGoogleDrive
me devuelve un contenido de datos en caché, con las siguientes observaciones.
-
driveFile.open
unDownloadProgressListener
endriveFile.open
, bytesDownloaded es 0 y bytesExpected es -1. - Si uso la API de Google Drive Rest , con el siguiente código de escritorio , puedo encontrar el último nombre de archivo con el contenido de suma de verificación correcto.
-
Si desinstalo mi aplicación de Android y la reinstalo nuevamente,
loadFromGoogleDrive
podrá obtener el último nombre de archivo con el contenido de suma de verificación correcto.
¿Hay alguna forma sólida de evitar cargar siempre los datos de la aplicación en caché de Google Drive?
Me las arreglo para producir una demostración. Estos son los pasos para reproducir este problema.
Paso 1: descargue el código fuente
https://github.com/yccheok/google-drive-bug
Paso 2: configuración en la consola API
Paso 3: Presione el botón GUARDAR "123.TXT" CON CONTENIDO "123"
Se creará un archivo con el nombre de archivo "123.TXT", contenido "123" en la carpeta de la aplicación.
Paso 4: Presione el botón GUARDAR "456.TXT" CON CONTENIDO "456"
El archivo anterior cambiará de nombre a "456.TXT", con el contenido actualizado a "456"
Paso 5: Presione el botón CARGAR EL ÚLTIMO ARCHIVO GUARDADO
Se encontró un archivo con el nombre de archivo "456.TXT", pero se lee el contenido en caché anterior "123". Esperaba contenido "456".
Tenga en cuenta que, si
- Desinstalar la aplicación de demostración.
- Vuelva a instalar la aplicación de demostración.
- Presione el botón CARGAR EL ÚLTIMO ARCHIVO GUARDADO, se encuentra el archivo con el nombre de archivo "456.TXT" y el contenido "456".
Había presentado un informe de problemas oficialmente: https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=4727
Otra información
Así es como se ve debajo de mi dispositivo: http://youtu.be/kuIHoi4A1c0
Me doy cuenta de que no todos los usuarios tendrán este problema. Por ejemplo, lo había probado con otro Nexus 6, Google Play Services 9.4.52 (440-127739847). El problema no aparece.
Había compilado un APK para probarlo: https://github.com/yccheok/google-drive-bug/releases/download/1.0/demo.apk