如何避免始终从Google云端硬盘加载caching的应用数据

目前,我正在使用Google云端硬盘Android API将我的Android应用数据存储到Google云端硬盘应用文件夹

这是我在保存我的应用程序数据时所要做的

  1. 为当前的本地zip文件生成校验和。
  2. Google云端硬盘应用程序文件夹中search,查看是否有现有的应用程序文件夹zip文件。
  3. 如果有,则用当前的本地zip文件覆盖现有App文件夹zip文件的内容。 此外,我们将重命名现有的应用程序文件夹zip文件名,以及最新的校验和。
  4. 如果没有现有的应用程序文件夹压缩文件,生成一个新的应用程序文件夹压缩文件,与本地压缩文件的内容。 我们将使用最新的校验和作为App Folder zip文件名。

这是执行上述操作的代码。

生成新的应用程序文件夹zip文件,或更新现有的应用程序文件夹zip文件

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(); } } } 

search现有的应用程序文件夹zip文件

 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; } 

加载应用程序数据期间发生问题。 想象下面的操作

  1. 首次将zip数据上传到Google云端硬盘应用文件夹 。 校验和是12345 。 正在使用的文件名是...checksum=12345...zip
  2. Google云端硬盘应用文件夹中searchzip数据。 能够find文件的文件名...checksum=12345...zip 。 下载内容。 validation内容的校验和也是12345
  3. 覆盖现有Google云端硬盘应用程序文件夹文件中的新压缩数据。 新的zip数据校验和是67890 。 现有的应用程序文件夹的zip文件被重命名为...checksum=67890...zip
  4. Google云端硬盘应用文件夹中searchzip数据。 能够find文件的文件名...checksum=67890...zip但是,在下载内容之后,内容的校验和还是老12345

下载App文件夹的zip文件

 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(); } } 

首先,我想

 Status status = Drive.DriveApi.requestSync(googleApiClient).await() 

做得不好。 它在大多数情况下失败,错误消息Sync request rate limit exceeded. 实际上,在requestSync施加的硬性限制,使得该API不是特别有用 – Android Google Play / Drive Api


但是,即使requestSync成功时, loadFromGoogleDrive仍然只能获取最新的文件名,但是校验和内容已经过时。

我100%确定loadFromGoogleDrive正在返回一个caching的数据内容,具有以下意见。

  1. 我在driveFile.open安装DownloadProgressListener ,bytesDownloaded为0,bytesExpected为-1。
  2. 如果我使用Google Drive Rest API和以下桌面代码 ,则可以find具有正确校验和内容的最新文件名。
  3. 如果我卸载我的Android应用程序并重新安装, loadFromGoogleDrive将能够获得正确的校验和内容的最新文件名。

有没有强大的方法,以避免始终从Google Drive加载caching的应用程序数据?


我设法制作一个演示。 以下是重现此问题的步骤。

第1步:下载源代码

https://github.com/yccheok/google-drive-bug

第2步:在API控制台中进行设置

在这里输入图像说明

步骤3:按内容“123”button保存“123.TXT”

在这里输入图像说明

一个文件名为“123.TXT”,内容为“123”的文件将在app文件夹中创build。

步骤4:按内容“456”button保存“456.TXT”

在这里输入图像说明

之前的文件将被重命名为“456.TXT”,内容更新为“456”

第五步:按下LOAD LAST SAVED FILE

在这里输入图像说明

find具有文件名“456.TXT”的文件,但读取先前的caching内容“123”。 我期待内容“456”。

请注意,如果我们

  1. 卸载演示应用程序。
  2. 重新安装演示应用程序。
  3. 按下LOAD LAST SAVED FILEbutton,find文件名为“456.TXT”且内容为“456”的文件。

我已正式提交问题报告 – https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=4727


其他信息

这就是我的设备 – http://youtu.be/kuIHoi4A1c0

我意识到,并不是所有的用户都会遇到这个问题。 例如,我已经testing了另一个Nexus 6,Google Play服务9.4.52(440-127739847)。 问题没有出现。

我编译了一个用于testing目的的APK – https://github.com/yccheok/google-drive-bug/releases/download/1.0/demo.apk

  1. 在Google云端硬盘上search很慢。 为什么不使用基文件夹的属性来存储压缩文件的ID? https://developers.google.com/drive/v2/web/properties
  2. Google云端硬盘上的文件名称不是唯一的,您可以上传多个具有相同名称的文件。 然而,Google返回的文件ID是唯一的。