Android中的全量更新、增量更新以及热更新
package com.mvp.myapplication.update;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import androidx.core.content.FileProvider;
import com.mvp.myapplication.utils.MD5Util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class UpdateService extends Service {
public static final String KEY_MD5 = "MD5";
public static final String URL = "downloadUrl";
private boolean startDownload;//开始下载
public static final String TAG = "UpdateService";
private DownloadApk downloadApkTask;
private String downloadUrl;
private String mMd5;
private UpdateProgressListener updateProgressListener;
private LocalBinder localBinder = new LocalBinder();
public class LocalBinder extends Binder {
public void setUpdateProgressListener(UpdateProgressListener listener) {
UpdateService.this.setUpdateProgressListener(listener);
}
}
private void setUpdateProgressListener(UpdateProgressListener listener) {
this.updateProgressListener = listener;
}
/**
* 获取FileProvider的auth
*/
private static String getFileProviderAuthority(Context context) {
try {
for (ProviderInfo provider : context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS).providers) {
if (FileProvider.class.getName().equals(provider.name) && provider.authority.endsWith(".update_app.file_provider")) {
return provider.authority;
}
}
} catch (PackageManager.NameNotFoundException ignore) {
}
return null;
}
private static Intent installIntent(Context context, String path) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addCategory(Intent.CATEGORY_DEFAULT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri fileUri = FileProvider.getUriForFile(context, getFileProviderAuthority(context), new File(path));
intent.setDataAndType(fileUri, "application/-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setDataAndType(Uri.fromFile(new File(path)), "application/-archive");
}
return intent;
}
public UpdateService() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!startDownload && intent != null) {
startDownload = true;
mMd5 = intent.getStringExtra(KEY_MD5);
downloadUrl = intent.getStringExtra(URL);
downloadApkTask = new DownloadApk(this, mMd5);
downloadApkTask.execute(downloadUrl);
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return localBinder;
}
@Override
public boolean onUnbind(Intent intent) {
return true;
}
@Override
public void onDestroy() {
if (downloadApkTask != null) {
downloadApkTask.cancel(true);
}
if (updateProgressListener != null) {
updateProgressListener = null;
}
super.onDestroy();
}
private static String getSaveFileName(String downloadUrl) {
if (downloadUrl == null || TextUtils.isEmpty(downloadUrl)) {
return System.currentTimeMillis() + ".apk";
}
return downloadUrl.substring(downloadUrl.lastIndexOf("/"));
}
private static File getDownloadDir(UpdateService service) {
File downloadDir = null;
if (Environment.getExternalStorageDirectory().equals(Environment.MEDIA_MOUNTED)) {
downloadDir = new File(service.getExternalCacheDir(), "update");
} else {
downloadDir = new File(service.getCacheDir(), "update");
}
if (!downloadDir.exists()) {
downloadDir.mkdirs();
}
return downloadDir;
}
private void start() {
if (updateProgressListener != null) {
updateProgressListener.start();
}
}
private void update(int progress) {
if (updateProgressListener != null) {
updateProgressListener.update(progress);
}
}
private void success(String path) {
if (updateProgressListener != null) {
updateProgressListener.success(path);
}
Intent i = installIntent(this, path);
startActivity(i);//自动安装
stopSelf();
}
private void error() {
if (updateProgressListener != null) {
updateProgressListener.error();
}
stopSelf();
}
private static class DownloadApk extends AsyncTask<String, Integer, String> {
private final String md5;
private UpdateService updateService;
public DownloadApk(UpdateService service, String md5) {
this.updateService = service;
this.md5 = md5;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if (updateService != null) {
updateService.start();
}
}
@Override
protected String doInBackground(String... strings) {
final String downloadUrl = strings[0];
final File file = new File(UpdateService.getDownloadDir(updateService),
UpdateService.getSaveFileName(downloadUrl));
Log.d(TAG, "download url is " + downloadUrl);
Log.d(TAG, "download apk cache at " + file.getAbsolutePath());
File dir = file.getParentFile();
if (!dir.exists()) {
dir.mkdirs();
}
HttpURLConnection httpConnection = null;
InputStream is = null;
FileOutputStream fos = null;
long updateTotalSize = 0;
URL url;
try {
url = new URL(downloadUrl);
httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setConnectTimeout(20000);
httpConnection.setReadTimeout(20000);
Log.d(TAG, "download status code: " + httpConnection.getResponseCode());
if (httpConnection.getResponseCode() != 200) {
return null;
}
updateTotalSize = httpConnection.getContentLength();
if (file.exists()) {
if (updateTotalSize == file.length()) {
// 下载完成
if (TextUtils.isEmpty(md5) || MD5Util.getMD5String(file).toUpperCase().equals(md5.toUpperCase())) {
return file.getAbsolutePath();
}
} else {
file.delete();
}
}
file.createNewFile();
is = httpConnection.getInputStream();
fos = new FileOutputStream(file, false);
byte buffer[] = new byte[4096];
int readSize = 0;
long currentSize = 0;
while ((readSize = is.read(buffer)) > 0) {
fos.write(buffer, 0, readSize);
currentSize += readSize;
publishProgress((int) (currentSize * 100 / updateTotalSize));
}
// download success
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (httpConnection != null) {
httpConnection.disconnect();
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
if (TextUtils.isEmpty(md5) || MD5Util.getMD5String(file).toUpperCase().equals(md5.toUpperCase())) {
return file.getAbsolutePath();
}
} catch (IOException e) {
e.printStackTrace();
return file.getAbsolutePath();
}
Log.e(TAG, "md5 invalid");
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
if (updateService != null) {
updateService.update(values[0]);
}
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
if (updateService != null) {
if (s != null) {
updateService.success(s);
} else {
updateService.error();
}
}
}
}
public static class Builder {
private String downloadUrl;
private String md5;
private ServiceConnection serviceConnection;
protected Builder(String downloadUrl) {
this.downloadUrl = downloadUrl;
}
public static Builder create(String downloadUrl) {
if (downloadUrl == null) {
throw new NullPointerException("downloadUrl == null");
}
return new Builder(downloadUrl);
}
public String getMd5() {
return md5;
}
public Builder setMd5(String md5) {
this.md5 = md5;
return this;
}
public Builder build(Context context, UpdateProgressListener listener) {
if (context == null) {
throw new NullPointerException("context == null");
}
Intent intent = new Intent();
intent.setClass(context, UpdateService.class);
intent.putExtra(URL, downloadUrl);
intent.putExtra(KEY_MD5, md5);
UpdateProgressListener delegateListener = new UpdateProgressListener() {
@Override
public void start() {
if (listener != null) {
listener.start();
}
}
@Override
public void update(int var1) {
if (listener != null) {
listener.update(var1);
}
}
@Override
public void success(String path) {
try {
context.unbindService(serviceConnection);
} catch (Throwable t) {
Log.e("UpdateService", "解绑失败" + t.getMessage());
}
if (listener != null) {
listener.success(path);
}
}
@Override
public void error() {
try {
context.unbindService(serviceConnection);
} catch (Throwable t) {
Log.e("UpdateService", "解绑失败" + t.getMessage());
}
if (listener != null) {
listener.error();
}
}
};
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LocalBinder binder = (LocalBinder) service;
binder.setUpdateProgressListener(delegateListener);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
context.bindService(intent, serviceConnection, Context.BIND_IMPORTANT);
context.startService(intent);
return this;
}
}
public interface UpdateProgressListener {
void start();
void update(int var);
void success(String path);
void error();
}
}