新的Google Drive Rest API,依赖性过多

时间:2019-01-14 08:59:18

标签: android google-drive-api google-drive-android-api

似乎没有人喜欢重新制作长时间运作良好的作品。这次,由于Google驱动器android API的弃用,我们不得不实施新的api。我希望通过任何方式从应用程序中删除旧的gms类。但是,当我将所有依赖项添加到我的项目中并重新实现简单的备份/还原功能时,我吓坏了,与旧方法相比,该项目向该项目添加了大约5000种方法,总共增加了大约8000种方法。

implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.http-client:google-http-client-gson:1.26.0'
implementation('com.google.api-client:google-api-client-android:1.26.0') {
    exclude group: 'org.apache.httpcomponents'
}
implementation('com.google.apis:google-api-services-drive:v3-rev136-1.25.0') {
    exclude group: 'org.apache.httpcomponents'
}

纯APK大小增加了1.2 mb。这就是Proguard的缩小。没有它,大约有18 k的方法。

现在,我想知道为什么Google为了优化而在每个版本中都设置了如此多的后台限制(现在甚至警报都无法使用)。并迫使开发人员夸大其apk,从而增加下载大小,内存消耗和总体电池消耗。

有什么方法可以实现简单的备份/还原功能而又不会在我们的项目中添加太多废话?有人在乎吗?

1 个答案:

答案 0 :(得分:0)

最后,我经历了很多试验和错误,已经设法通过纯REST方式做到了。我已经删除了除“ com.google.android.gms:play-services-auth”之外的所有Google库,因为它提供了一项功能,供用户允许我的应用访问Google云端硬盘范围。

在这里,我将显示简单的CloudServiceImpl类,该类可以将备份写入Google云端硬盘,并从上次创建的备份中还原。如果您需要从特定的备份中还原,请随时进行修改:

.........................
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;

public class CloudServiceImpl implements OnSuccessListener<GoogleSignInAccount>, OnFailureListener {

    private static final String LINE_FEED = "\r\n";
    private static final String APP_FOLDER_ID = "appDataFolder";
    private static final String SCOPE_APPDATA = "https://www.googleapis.com/auth/drive.appdata";

    private static final String FILES_REST_URL = "https://www.googleapis.com/drive/v3/files";
    private static final String AUTH_REST_URL = "https://www.googleapis.com/oauth2/v4/token";

    private static final String AUTHORIZATION_PARAM = "Authorization";
    private static final String BEARER_VAL = "Bearer ";
    private static final String CONTENT_TYPE_PARAM = "Content-Type: ";

    private static final String DB_NAME = "prana_breath.sqlite";
    private static final String SQLITE_MIME = "application/x-sqlite3";

    private Activity mActivity;

    private int mNextGoogleApiOperation = INVALID;

    private String mAccessToken;
    private long mTokenExpired;
    private String mAuthCode;

    public CloudServiceImpl(final Activity activity) {
        mActivity = activity;
    }

    public final void disconnect() {
        mActivity = null;
        mNextGoogleApiOperation = INVALID;
        mAuthCode = null;
        mAccessToken = null;
        mTokenExpired = 0;
    }

    public final void connectAndStartOperation(final int nextOperation) {
        mNextGoogleApiOperation = nextOperation;
        onChangeProgressBarVisibility(View.VISIBLE);

        if (mAuthCode == null) {
            final GoogleSignInOptions signInOptions =
                    new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                            .requestEmail()
                            .requestScopes(new Scope(SCOPE_APPDATA))
                            .requestServerAuthCode(getString(R.string.default_web_client_id))
                            .build();

            final GoogleSignInClient client = GoogleSignIn.getClient(mActivity, signInOptions);

            mActivity.startActivityForResult(client.getSignInIntent(), RequestCode.CLOUD_RESOLUTION);
        } else {
            onGoogleDriveConnected(mNextGoogleApiOperation);
            mNextGoogleApiOperation = INVALID;
        }
    }

    public final void handleActivityResult(final int requestCode, final Intent data) {
        if (requestCode == RequestCode.CLOUD_RESOLUTION) {
            GoogleSignIn.getSignedInAccountFromIntent(data)
                        .addOnSuccessListener(this)
                        .addOnFailureListener(this);
        }
    }

//--------------------------------------------------------------------------------------------------
//  Event handlers
//--------------------------------------------------------------------------------------------------

    @Override
    public void onSuccess(GoogleSignInAccount googleAccount) {
        mAuthCode = googleAccount.getServerAuthCode();
//        DebugHelper.log("getServerAuthCode:", googleAccount.getServerAuthCode());

        onChangeProgressBarVisibility(View.GONE);
        onChangeProgressDlgVisibility(View.VISIBLE);
        onGoogleDriveConnected(mNextGoogleApiOperation);
        mNextGoogleApiOperation = INVALID;
    }

    @Override
    public void onFailure(@NonNull Exception e) {
        onChangeProgressBarVisibility(View.GONE);
        onChangeProgressDlgVisibility(View.GONE);
        mNextGoogleApiOperation = INVALID;
        ToastHelper.showToastSafe(getString(R.string.error_toast) + ": " + e.getMessage());
    }

    private void onGoogleDriveConnected(final int operation) {
        switch (operation) {
            case CloudHelper.BACKUP_CODE:
                onBackupToDriveAsync();
                break;

            case CloudHelper.RESTORE_CODE:
                onRestoreFromDriveAsync();
                break;
        }
    }

//--------------------------------------------------------------------------------------------------
//  Private methods
//--------------------------------------------------------------------------------------------------

    private boolean isRequestInvalid() {
        return mActivity == null;
    }

    @SuppressLint("StaticFieldLeak")
    private void onBackupToDriveAsync() {
        final AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... parameters) {
                BackupDelegate.backupPrefs(); // Here you could write your preferences to the database (Remove it if not needed)
                writeDbToDrive();
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                onChangeProgressDlgVisibility(View.GONE);
                onChangeProgressBarVisibility(View.GONE);
            }
        };
        asyncTask.execute();
    }

    @SuppressLint("StaticFieldLeak")
    private void onRestoreFromDriveAsync() {
        final AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... parameters) {
                readDbFromDrive();
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                onChangeProgressDlgVisibility(View.GONE);
                onChangeProgressBarVisibility(View.GONE);
            }
        };
        asyncTask.execute();
    }

    /**
     * https://developers.google.com/drive/api/v3/multipart-upload
     */
    private void writeDbToDrive() {
        HttpURLConnection conn = null;
        OutputStream os = null;

        final String accessToken = requestAccessToken();
        if (accessToken == null || isRequestInvalid()) return;

        try {
            final String boundary = "pb" + System.currentTimeMillis();
            final URL url = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart");
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setUseCaches(false);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setConnectTimeout(5000);
            conn.setRequestProperty(AUTHORIZATION_PARAM, BEARER_VAL + accessToken);
            conn.setRequestProperty("Content-Type", "multipart/related; boundary=" + boundary);

            /////// Prepare data
            final String timestamp =  new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.US).format(new Date());
            // Prepare file metadata (Change your backup file name here)
            final StringBuilder b = new StringBuilder();
            b.append('{')
             .append("\"name\":").append('\"').append("prana_breath_").append(timestamp).append(".db").append('\"').append(',')
             .append("\"mimeType\":").append("\"application\\/x-sqlite3\"").append(',')
             .append("\"parents\":").append("[\"").append(APP_FOLDER_ID).append("\"]")
             .append('}');
            final String metadata = b.toString();
            final byte[] data = readFile(getAppDbFile());

            /////// Calculate body length
            int bodyLength = 0;
            // MetaData part
            b.setLength(0);
            b.append("--").append(boundary).append(LINE_FEED);
            b.append(CONTENT_TYPE_PARAM).append("application/json; charset=UTF-8").append(LINE_FEED);
            b.append(LINE_FEED);
            b.append(metadata).append(LINE_FEED);
            b.append(LINE_FEED);
            b.append("--").append(boundary).append(LINE_FEED);
            b.append(CONTENT_TYPE_PARAM).append(SQLITE_MIME).append(LINE_FEED);
            b.append(LINE_FEED);
            final byte[] beforeFilePart = b.toString().getBytes("UTF_8");
            bodyLength += beforeFilePart.length;

            bodyLength += data.length; // File

            b.setLength(0);
            b.append(LINE_FEED);
            b.append("--").append(boundary).append("--");
            final byte[] afterFilePart = b.toString().getBytes("UTF_8");
            bodyLength += afterFilePart.length;

            conn.setRequestProperty("Content-Length", String.valueOf(bodyLength));
            if (BuildConfig.DEBUG_MODE) DebugHelper.log("LENGTH", bodyLength);

            /////// Write to socket
            os = conn.getOutputStream();

            os.write(beforeFilePart);
            os.write(data);
            os.write(afterFilePart);
            os.flush();

            final String msg = conn.getResponseMessage();
            final int code = conn.getResponseCode();

            if (code == 200) {
                ToastHelper.showToastSafe(R.string.backup_success_toast);
            } else {
                ToastHelper.showToastSafe(getString(R.string.error_toast) + ": " + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
            ToastHelper.showToastSafe(e.getMessage());
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                }
            }
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    /**
     * https://developers.google.com/drive/api/v3/manage-downloads
     */
    private void readDbFromDrive() {
        if (isRequestInvalid()) return;

        HttpURLConnection conn = null;
        InputStream is = null;

        final String accessToken = requestAccessToken();
        if (accessToken == null || isRequestInvalid()) return;

        try {
            final String dbFileId = getLatestDbFileIdOnDrive();

            if (isRequestInvalid()) return;

            if (dbFileId == null || dbFileId.length() == 0 || dbFileId.equals(NULL_STR)) {
                return;
            }

            final String request = FILES_REST_URL + '/' + dbFileId + "?alt=media";
            final URL url = new URL(request);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setUseCaches(false);
            conn.setDoInput(true);
            conn.setConnectTimeout(5000);
            conn.setRequestProperty(AUTHORIZATION_PARAM, BEARER_VAL + accessToken);

            is = conn.getInputStream();
            if (restoreDbFromDrive(is)) BackupDelegate.totalRefreshAfterRestore();
        } catch (Exception e) {
            ToastHelper.showToastSafe(e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    /**
     * https://developers.google.com/drive/api/v3/reference/files/list
     * @return
     */
    private final String getLatestDbFileIdOnDrive() {
        HttpURLConnection conn = null;
        InputStream is = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            final StringBuilder b = new StringBuilder();
            b.append(FILES_REST_URL).append('?')
             .append("spaces=").append(APP_FOLDER_ID).append('&')
             .append("orderBy=").append(URLEncoder.encode("createdTime desc", "UTF_8")).append('&')
             .append("pageSize=").append("2");

            final URL url = new URL(b.toString());
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setUseCaches(false);
            conn.setDoInput(true);
            conn.setConnectTimeout(5000);
            conn.setRequestProperty(AUTHORIZATION_PARAM, BEARER_VAL + mAccessToken);

            final int responseCode = conn.getResponseCode();
            if (200 <= responseCode && responseCode <= 299) {
                is = conn.getInputStream();
                isr = new InputStreamReader(is);
                br = new BufferedReader(isr);
            } else {
                ToastHelper.showToastSafe(conn.getResponseMessage());
                return null;
                /*is = conn.getErrorStream();
                isr = new InputStreamReader(is);
                br = new BufferedReader(isr);*/
            }
            b.setLength(0);
            String output;
            while ((output = br.readLine()) != null) {
                b.append(output);
            }

            final JSONObject jsonResponse = new JSONObject(b.toString());
            final JSONArray files = jsonResponse.getJSONArray("files");
            if (files.length() == 0) {
                ToastHelper.showToastSafe(R.string.no_backup_toast);
                return null;
            }
            final JSONObject file = files.getJSONObject(0);
            return file.getString("id");
        } catch (Exception e) {
            ToastHelper.showToastSafe(e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
            if (conn != null) {
                conn.disconnect();
            }
        }
        return null;
    }

    /**
     * https://developers.google.com/identity/protocols/OAuth2WebServer#exchange-authorization-code
     *
     */
    private String requestAccessToken() {
        if (mAccessToken != null && SystemClock.elapsedRealtime() < mTokenExpired) return mAccessToken;
        mTokenExpired = 0;
        mAccessToken = null;

        HttpURLConnection conn = null;
        OutputStream os = null;
        InputStream is = null;
        InputStreamReader isr = null;
        BufferedReader br = null;

        try {
            final URL url = new URL(AUTH_REST_URL);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setUseCaches(false);
            conn.setDoInput(true);
            conn.setDoOutput(true);
            conn.setConnectTimeout(3000);
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            final StringBuilder b = new StringBuilder();
            b.append("code=").append(mAuthCode).append('&')
             .append("client_id=").append(getString(R.string.default_web_client_id)).append('&')
             .append("client_secret=").append(getString(R.string.client_secret)).append('&')
             .append("redirect_uri=").append("").append('&')
             .append("grant_type=").append("authorization_code");

            final byte[] postData = b.toString().getBytes("UTF_8");

            os = conn.getOutputStream();
            os.write(postData);

            final int responseCode = conn.getResponseCode();
            if (200 <= responseCode && responseCode <= 299) {
                is = conn.getInputStream();
                isr = new InputStreamReader(is);
                br = new BufferedReader(isr);
            } else {
                ToastHelper.showToastSafe(conn.getResponseMessage());
                return null;
            }

            b.setLength(0);
            String output;
            while ((output = br.readLine()) != null) {
                b.append(output);
            }

            final JSONObject jsonResponse = new JSONObject(b.toString());
            mAccessToken = jsonResponse.getString("access_token");
            mTokenExpired = SystemClock.elapsedRealtime() + jsonResponse.getLong("expires_in") * 1000;
            return mAccessToken;
        } catch (Exception e) {
            ToastHelper.showToastSafe(e.getMessage());
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
            if (conn != null) {
                conn.disconnect();
            }
        }
        return null;
    }

    private boolean restoreDbFromDrive(final InputStream src) throws IOException {
        if (src == null) {
            ToastHelper.showToastSafe(R.string.no_backup_toast);
        } else {
            DbOpenHelper.getInstance().close(); // It is your SQLiteOpenHelper implementation (Close db before replacing it)
            writeStreamToFileOutput(src, new FileOutputStream(getAppDbFile()));
            return true;
        }
        return false;
    }

    private static byte[] readFile(File file) throws IOException {           
        RandomAccessFile f = new RandomAccessFile(file, "r");
        try {
            long longlength = f.length();
            int length = (int) longlength;
            if (length != longlength)
            throw new IOException("File size >= 10 Mb");

            byte[] data = new byte[length];
            f.readFully(data);
            return data;
        } finally {
            f.close();
        }
    }

    public static void writeStreamToFileOutput(final InputStream src, final FileOutputStream dst) throws IOException {
        try {
            final byte[] buffer = new byte[4 * 1024]; // or other buffer size
            int read;

            while ((read = src.read(buffer)) != -1) {
                dst.write(buffer, 0, read);
            }

            dst.flush();
        } finally {
            src.close();
            dst.close();
        }
    }

    private static File getAppDbFile() {
        return mActivity.getApplicationContext().getDatabasePath(DB_NAME);
    }
}

CloudHelper类允许以不同的样式覆盖CloudServiceImpl

public class CloudHelper {
    public static final BACKUP_CODE = 1;
    public static final RESTORE_CODE = 2;

    @Nullable
    private static CloudServiceImpl sCloudServiceImpl;

    public static void connectAndStartOperation(final MainActivity activity, final int nextOperation) {
        if (sCloudServiceImpl == null) {
            sCloudServiceImpl = new CloudServiceImpl(activity);
        }
        sCloudServiceImpl.connectAndStartOperation(nextOperation);
    }

    public static void disconnect() {
        if (sCloudServiceImpl != null) {
            sCloudServiceImpl.disconnect();
            sCloudServiceImpl = null;
        }
    }

    public static void handleActivityResult(final int requestCode, final Intent data) {
        if (sCloudServiceImpl != null) sCloudServiceImpl.handleActivityResult(requestCode, data);
    }
}

在您的活动中:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    CloudHelper.handleActivityResult(requestCode, data);
}

 @Override
protected void onDestroy() {
    CloudHelper.disconnect();
    super.onDestroy();
}

public void onBackupClick() {
    CloudHelper.connectAndStartOperation(CloudHelper.BACKUP_CODE);
}

public void onRestoreClick() {
    CloudHelper.connectAndStartOperation(CloudHelper.RESTORE_CODE);
}

这个例子很冗长。但是它增加了<20种方法,而10k方法却没有。
另外,您还需要将string.xml default_web_client_idclient_secret添加到项目中。您会在Google API控制台中找到它,但是这次使用的是“ Web客户端(由Google Service自动创建)”,而不是您以前用于旧版Google Drive API的客户端ID。