android-如何避免始终从Google云端硬盘加载缓存的应用数据

目前,我正在使用Google Drive Android API将Android应用程序数据存储到Google Drive App文件夹中。

这是我保存应用程序数据时正在做的事情

  1. 生成当前本地zip文件的校验和。
  2. 在Google云端硬盘应用文件夹中搜索,以查看是否存在现有的应用文件夹zip文件。
  3. 如果存在,请使用当前的本地zip文件覆盖现有“应用文件夹” zip文件的内容。 另外,我们将使用最新的校验和重命名现有的App Folder zip文件名。
  4. 如果没有现有的App Folder zip文件,请生成一个包含本地zip文件内容的新的App Folder zip文件。 我们将使用最新的校验和作为App Folder zip文件名。

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

生成新的App Folder zip文件,或更新现有的App Folder 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();
        }
    }
}

搜索现有的App Folder压缩文件

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 Drive应用文件夹。 校验和为12345。使用的文件名为loadFromGoogleDrive
  2. 从Google云端硬盘应用文件夹中搜索zip数据。 能够找到文件名loadFromGoogleDrive的文件。下载内容。 验证内容的校验和也为driveFile.open
  3. 将新的zip数据覆盖到现有的Google Drive App文件夹文件中。 新的zip数据校验和为loadFromGoogleDrive。现有的app文件夹zip文件重命名为driveFile.open
  4. 从Google云端硬盘应用文件夹中搜索zip数据。 可以找到文件名为loadFromGoogleDrive的文件。但是,下载内容后,内容的校验和仍旧为loadFromGoogleDrive

下载App Folder 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()

做得不好。 它在大多数情况下都会失败,并显示错误消息loadFromGoogleDrive,实际上,在driveFile.open中施加的硬限制使该API并不是特别有用-Android Google Play / Drive Api


但是,即使loadFromGoogleDrive成功,driveFile.open仍只能获取最新的文件名,但过时的校验和内容。

我有100%的把握loadFromGoogleDrive返回的是缓存的数据内容,并有以下观察结果。

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

有什么健壮的方法可以避免始终从Google云端硬盘加载缓存的应用数据?


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

步骤1:下载源代码

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

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

enter image description here

步骤3:按带有内容“ 123”的保存“ 123.TXT”按钮

enter image description here

在应用程序文件夹中将创建一个文件名为“ 123.TXT”,内容为“ 123”的文件。

第4步:按下带有内容“ 456”的保存“ 456.TXT”按钮

enter image description here

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

步骤5:按下按钮LOAD LAST SAVED FILE

enter image description here

找到文件名“ 456.TXT”的文件,但读取了先前的缓存内容“ 123”。 我期待内容“ 456”。

请注意,如果我们

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

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


其他资讯

这是我的设备下的外观-[http://youtu.be/kuIHoi4A1c0]

我意识到,并非所有用户都会遇到此问题。 例如,我已经测试了另一个Nexus 6,即Google Play服务9.4.52(440-127739847)。 该问题没有出现。

我已经为测试目的编译了一个APK-[https://github.com/yccheok/google-drive-bug/releases/download/1.0/demo.apk]

1个解决方案
1 votes
  1. 在Google云端硬盘上的搜索速度很慢。为什么不使用基本文件夹的属性来存储zip文件的ID?[https://developers.google.com/drive/v2/web/properties]
  2. Google云端硬盘上的文件名不是唯一的,您可以上传多个具有相同名称的文件。 但是,Google返回的文件ID是唯一的。
ashishb answered 2019-10-09T17:57:38Z
translate from https://stackoverflow.com:/questions/38934769/how-to-avoid-from-always-loading-cached-app-data-from-google-drive