返回

Android: 下载 Google Drive 文件到本地 | 教程

Android

Android程序化下载和保存 Google Drive 中选定文件到本地目录

当需要在 Android 应用程序中集成 Google Drive API,并允许用户选择和下载文件到本地存储时,开发者经常会遇到一些挑战。 这篇文章讨论了如何通过编程方式从 Google Drive 下载所选文件并将其保存到本地目录。 提供的示例代码将有助于理解实现过程,并避开一些常见的坑。

问题分析

从 Google Drive 下载文件,主要遇到的问题在于,虽然可以通过 DriveIdDriveFile 访问到文件,但实际的文件内容读取和保存却需要额外的步骤。 直接使用这些对象并不能直接获取文件的数据流,需要进一步利用 Google Drive API 提供的功能。

解决方案

提供两种方法,分别使用 Google Drive API v3 和 Google Play Services 的 Drive API 实现文件的下载,适用于不同场景,各有利弊,可以选择最适合自身项目的方案。

方案一:使用 Google Drive API v3

此方案绕过 Google Play Services Drive API,直接使用 Google Drive API v3。它需要用户授权并通过网络请求下载文件,更为通用,也更灵活。

操作步骤:

  1. 添加依赖 : 在 build.gradle 文件中添加 Google API Client 依赖。

    dependencies {
        implementation 'com.google.api-client:google-api-client-android:1.32.1'
        implementation 'com.google.apis:google-api-services-drive:v3-rev20210811-1.32.1'
        implementation 'com.google.oauth-client:google-oauth-client-java6:1.32.1'
        implementation 'com.google.auth:google-auth-library-oauth2-http:0.27.0'
    }
    
  2. 权限申请 : 确保应用程序拥有网络访问和存储权限。在 AndroidManifest.xml 文件中声明:

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    
  3. 文件下载函数 : 创建一个函数来下载文件。它需要 DriveId、本地存储路径以及访问令牌。

    import com.google.api.client.http.GenericUrl;
    import com.google.api.client.http.HttpResponse;
    import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
    import com.google.api.client.http.javanet.NetHttpTransport;
    import com.google.api.client.json.jackson2.JacksonFactory;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    public class DriveDownloadHelper {
    
        public static boolean downloadFile(String fileId, File localFile, String accessToken) {
            try {
                NetHttpTransport transport = new NetHttpTransport();
                JacksonFactory jsonFactory = JacksonFactory.getDefaultInstance();
                GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
    
    
                String downloadUrl = "https://www.googleapis.com/drive/v3/files/" + fileId + "?alt=media";
                GenericUrl url = new GenericUrl(downloadUrl);
                HttpResponse response = transport.createRequestFactory(credential).buildGetRequest(url).execute();
                InputStream inputStream = response.getContent();
    
                FileOutputStream outputStream = new FileOutputStream(localFile);
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                outputStream.close();
                inputStream.close();
                return true;
    
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }
    }
    

    这个函数使用 Google API Client 库构建一个带有授权头的 HTTP 请求,然后下载文件。 它从Google Drive获取InputStream并将其写入提供的 File 对象。

  4. 获取 Access Token : 你需要一个有效的 Access Token 才能授权 API 请求。可以参考 Google OAuth 2.0 的文档来获取。

    // 示例,不建议硬编码
    String accessToken = "YOUR_ACCESS_TOKEN";
    

    应该通过 OAuth 2.0 流程从 Google 获取有效的 Access Token,而不要硬编码。

  5. 调用下载 : 在 onActivityResult 中调用下载函数:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            if (requestCode == REQUEST_CODE_OPENER) {
                DriveId driveId = data.getParcelableExtra(OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);
                String fileId = driveId.getResourceId();
    
                File localFile = new File(getExternalFilesDir(null), "downloaded_file.pdf"); // 或者 .docx 等
                //在后台线程下载
                new Thread(() -> {
                    boolean success = DriveDownloadHelper.downloadFile(fileId, localFile, accessToken);
                    runOnUiThread(() -> {
                        if (success) {
                            // 下载成功处理
                        } else {
                            // 下载失败处理
                        }
                    });
                }).start();
    
            }
        }
    }
    

    务必在后台线程执行下载操作,防止阻塞UI线程。下载完成后,在主线程更新 UI。

方案二:使用 Google Play Services Drive API

如果应用程序已经集成了 Google Play Services,并且使用了其提供的 Drive API,可以通过该 API 获取文件内容。这种方法更直接,也更容易集成,但依赖于 Google Play Services。

操作步骤:

  1. 修改 onActivityResult : 获取 DriveFile 后,创建一个 DownloadAsyncTask 下载文件。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case REQUEST_CODE_OPENER:
                    DriveId driveId = data.getParcelableExtra(
                            OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);
                    DriveFile file = driveId.asDriveFile();
    
                    File localFile = new File(getExternalFilesDir(null), "downloaded_file.pdf");
                    new DownloadAsyncTask(mGoogleApiClient, file, localFile).execute();
                    break;
                case REQUEST_CODE_RESOLUTION:
                    mGoogleApiClient.connect();
                    break;
            }
        }
    }
    
  2. 创建 AsyncTask : 创建一个继承自 AsyncTask 的类来处理文件下载:

    import android.os.AsyncTask;
    import android.util.Log;
    
    import com.google.android.gms.common.api.GoogleApiClient;
    import com.google.android.gms.common.api.ResultCallback;
    import com.google.android.gms.drive.DriveFile;
    import com.google.android.gms.drive.DriveApi;
    import com.google.android.gms.drive.MetadataBuffer;
    import com.google.android.gms.drive.MetadataChangeSet;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    class DownloadAsyncTask extends AsyncTask<Void, Void, Boolean> {
    
        private GoogleApiClient mGoogleApiClient;
        private DriveFile mFile;
        private File mLocalFile;
    
        public DownloadAsyncTask(GoogleApiClient googleApiClient, DriveFile file, File localFile) {
            this.mGoogleApiClient = googleApiClient;
            this.mFile = file;
            this.mLocalFile = localFile;
        }
    
        @Override
        protected Boolean doInBackground(Void... params) {
            try {
                DriveApi.DriveContentsResult driveContentsResult = mFile.open(mGoogleApiClient, DriveFile.MODE_READ_ONLY, null).await();
                if (!driveContentsResult.getStatus().isSuccess()) {
                    return false;
                }
    
    
                InputStream inputStream = driveContentsResult.getDriveContents().getInputStream();
                OutputStream outputStream = new FileOutputStream(mLocalFile);
    
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
    
                inputStream.close();
                outputStream.close();
                driveContentsResult.getDriveContents().discard(mGoogleApiClient); // 记得关闭并释放资源
                return true;
            } catch (IOException e) {
                Log.e("DownloadAsyncTask", "Error downloading file", e);
                return false;
            }
        }
    
        @Override
        protected void onPostExecute(Boolean result) {
            if (result) {
                // 下载成功
                Log.i("DownloadAsyncTask", "File downloaded to: " + mLocalFile.getAbsolutePath());
            } else {
                // 下载失败
                Log.e("DownloadAsyncTask", "File download failed");
            }
        }
    }
    

    这个 AsyncTask 在后台线程打开 DriveFile,获取其 InputStream,然后将其写入本地文件。请务必关闭流并 discard DriveContents 以释放资源。

安全建议

  • 权限控制 : 仔细评估应用需要的 Google Drive 权限。 只请求所需的最小权限。
  • 数据加密 : 如果下载的文件包含敏感信息,考虑在存储到本地之前对其进行加密。
  • 异常处理 : 完善错误处理机制,优雅地处理网络连接问题、权限问题等。

总结

这篇文章提供了两种从 Google Drive 下载文件并在 Android 应用中保存的方案,各具优势。在方案选择上,需要考虑现有项目的依赖以及希望拥有的灵活性,保证文件可以安全地保存到本地。