似乎没有人喜欢重新制作长时间运作良好的作品。这次,由于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,从而增加下载大小,内存消耗和总体电池消耗。
有什么方法可以实现简单的备份/还原功能而又不会在我们的项目中添加太多废话?有人在乎吗?
答案 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_id
和client_secret
添加到项目中。您会在Google API控制台中找到它,但是这次使用的是“ Web客户端(由Google Service自动创建)”,而不是您以前用于旧版Google Drive API的客户端ID。