Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
61 views
in Technique[技术] by (71.8m points)

Unpredictable result of DriveId.getResourceId() in Google Drive Android API

The issue is that the 'resourceID' from 'DriveId.getResourceId()' is not available (returns NULL) on newly created files (product of 'DriveFolder.createFile(GAC, meta, cont)'). If the file is retrieved by a regular list or query procedure, the 'resourceID' is correct.

I suspect it is a timing/latency issue, but it is not clear if there is an application action that would force refresh. The 'Drive.DriveApi.requestSync(GAC)' seems to have no effect.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

UPDATE (07/22/2015)
Thanks to the prompt response from Steven Bazyl (see comments below), I finally have a satisfactory solution using Completion Events. Here are two minified code snippets that deliver the ResourceId to the app as soon as the newly created file is propagated to the Drive:

File creation, add change subscription:

public class CreateEmptyFileActivity extends BaseDemoActivity {
  private static final String TAG = "_X_";

  @Override
  public void onConnected(Bundle connectionHint) {   super.onConnected(connectionHint);

    MetadataChangeSet meta = new MetadataChangeSet.Builder()
      .setTitle("EmptyFile.txt").setMimeType("text/plain")
      .build();

    Drive.DriveApi.getRootFolder(getGoogleApiClient())
      .createFile(getGoogleApiClient(), meta, null,
        new ExecutionOptions.Builder()
          .setNotifyOnCompletion(true)
          .build()
      )
      .setResultCallback(new ResultCallback<DriveFileResult>() {
        @Override
        public void onResult(DriveFileResult result) {
          if (result.getStatus().isSuccess()) {
            DriveId driveId = result.getDriveFile().getDriveId();
            Log.d(TAG, "Created a empty file: " + driveId);
            DriveFile file = Drive.DriveApi.getFile(getGoogleApiClient(), driveId);
            file.addChangeSubscription(getGoogleApiClient());
          }
        }
      });
  }
}

Event Service, catches the completion:

public class ChngeSvc extends DriveEventService {
  private static final String TAG = "_X_";

  @Override
  public void onCompletion(CompletionEvent event) {  super.onCompletion(event);
    DriveId driveId = event.getDriveId();
    Log.d(TAG, "onComplete: " + driveId.getResourceId());
    switch (event.getStatus()) {
      case CompletionEvent.STATUS_CONFLICT:  Log.d(TAG, "STATUS_CONFLICT"); event.dismiss(); break;
      case CompletionEvent.STATUS_FAILURE:   Log.d(TAG, "STATUS_FAILURE");  event.dismiss(); break;
      case CompletionEvent.STATUS_SUCCESS:   Log.d(TAG, "STATUS_SUCCESS "); event.dismiss(); break;
    }
  }
}

Under normal circumstances (wifi), I get the ResourceId almost immediately.

20:40:53.247﹕Created a empty file: DriveId:CAESABiiAiDGsfO61VMoAA==
20:40:54.305: onComplete, ResourceId: 0BxOS7mTBMR_bMHZRUjJ5NU1ZOWs

... done for now.

ORIGINAL POST, deprecated, left here for reference.

I let this answer sit for a year hoping that GDAA will develop a solution that works. The reason for my nagging is simple. If my app creates a file, it needs to broadcast this fact to its buddies (other devices, for instance) with an ID that is meaningful (that is ResourceId). It is a trivial task under the REST Api where ResourceId comes back as soon as the file is successfully created.

Needles to say that I understand the GDAA philosophy of shielding the app from network primitives, caching, batching, ... But clearly, in this situation, the ResourceID is available long before it is delivered to the app.

Originally, I implemented Cheryl Simon's suggestion and added a ChangeListener on a newly created file, hoping to get the ResourceID when the file is propagated. Using classic CreateEmptyFileActivity from android-demos, I smacked together the following test code:

public class CreateEmptyFileActivity extends BaseDemoActivity {
  private static final String TAG = "CreateEmptyFileActivity";

  final private ChangeListener mChgeLstnr = new ChangeListener() {
    @Override
    public void onChange(ChangeEvent event) {
      Log.d(TAG, "event: " + event + " resId: " + event.getDriveId().getResourceId());
    }
  };


  @Override
  public void onConnected(Bundle connectionHint) {   super.onConnected(connectionHint);

    MetadataChangeSet meta = new MetadataChangeSet.Builder()
      .setTitle("EmptyFile.txt").setMimeType("text/plain")
      .build();

    Drive.DriveApi.getRootFolder(getGoogleApiClient())
      .createFile(getGoogleApiClient(), meta, null)
      .setResultCallback(new ResultCallback<DriveFileResult>() {
        @Override
        public void onResult(DriveFileResult result) {
          if (result.getStatus().isSuccess()) {
            DriveId driveId = result.getDriveFile().getDriveId();
            Log.d(TAG, "Created a empty file: " + driveId);
            Drive.DriveApi.getFile(getGoogleApiClient(), driveId).addChangeListener(getGoogleApiClient(), mChgeLstnr);
          }
        }
      });
  }
}

... and was waiting for something to happen. File was happily uploaded to the Drive within seconds, but no onChange() event. 10 minutes, 20 minutes, ... I could not find any way how to make the ChangeListener to wake up.

So the only other solution, I could come up was to nudge the GDAA. So I implemented a simple handler-poker that tickles the metadata until something happens:

public class CreateEmptyFileActivity extends BaseDemoActivity {
  private static final String TAG = "CreateEmptyFileActivity";

  final private ChangeListener mChgeLstnr = new ChangeListener() {
    @Override
    public void onChange(ChangeEvent event) {
      Log.d(TAG, "event: " + event + " resId: " + event.getDriveId().getResourceId());
    }
  };

  static DriveId driveId;
  private static final int ENOUGH = 4;    // nudge 4x,  1+2+3+4 = 10seconds
  private static int mWait = 1000;
  private int mCnt;
  private Handler mPoker;
  private final Runnable mPoke = new Runnable() { public void run() {
    if (mPoker != null && driveId != null && driveId.getResourceId() == null && (mCnt++ < ENOUGH)) {
      MetadataChangeSet meta = new MetadataChangeSet.Builder().build();
      Drive.DriveApi.getFile(getGoogleApiClient(), driveId).updateMetadata(getGoogleApiClient(), meta).setResultCallback(
        new ResultCallback<DriveResource.MetadataResult>() {
          @Override
          public void onResult(DriveResource.MetadataResult result) {
            if (result.getStatus().isSuccess() && result.getMetadata().getDriveId().getResourceId() != null)
              Log.d(TAG, "resId COOL " + result.getMetadata().getDriveId().getResourceId());
            else
              mPoker.postDelayed(mPoke, mWait *= 2);
          }
        }
      );
    } else {
      mPoker = null;
    }
  }};

  @Override
  public void onConnected(Bundle connectionHint) {   super.onConnected(connectionHint);

    MetadataChangeSet meta = new MetadataChangeSet.Builder()
      .setTitle("EmptyFile.txt").setMimeType("text/plain")
      .build();

    Drive.DriveApi.getRootFolder(getGoogleApiClient())
      .createFile(getGoogleApiClient(), meta, null)
      .setResultCallback(new ResultCallback<DriveFileResult>() {
        @Override
        public void onResult(DriveFileResult result) {
          if (result.getStatus().isSuccess()) {
            driveId = result.getDriveFile().getDriveId();
            Log.d(TAG, "Created a empty file: " + driveId);
            Drive.DriveApi.getFile(getGoogleApiClient(), driveId).addChangeListener(getGoogleApiClient(), mChgeLstnr);

            mCnt = 0;
            mPoker = new Handler();
            mPoker.postDelayed(mPoke, mWait);
          }
        }
      });
  }
}

And voila, 4 seconds (give or take) later, the ChangeListener delivers a new shiny ResourceId. Of course, the ChangeListener becomes thus obsolete, since the poker routine gets the ResourceId as well.

So this is the answer for those who can't wait for the ResourceId. Which brings up the follow-up question:

Why do I have to tickle metadata (or re-commit content), very likely creating unnecessary network traffic, to get onChange() event, when I see clearly that the file has been propagated a long time ago, and GDAA has the ResourceId available?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...