Android中的全量更新增量更新以及热更新
Posted hzulwy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android中的全量更新增量更新以及热更新相关的知识,希望对你有一定的参考价值。
在客户端开发过程中,我们可能会遇到这样一种需求:点击某个按钮弹出一个弹窗,提示我们可以更新到apk的某个版本,或者我们可以通过服务端接口进行强制更新。在这种需求中,我们是不需要通过应用商店来更新我们的apk的,而是直接在apk内部进行版本更新。这次我们就来看看实现这种应用内更新的几种方式。当然,这种玩法只能在国内玩,海外的话会被Googleplay据审的。如果是海外的应用要更新apk,只能在GooglePlay上上传新版本的包。
全量更新
什么是全量更新呢?举个例子,假设现在用户手机上的apk是1.0版本,如果想要升级到2.0版本,全量更新的处理方式则是把2.0版本的apk全部下载下来进行覆盖安装。那么,我们该如果设计一个合理的全量更新方案呢?
- 服务端
需要提供一个接口,这个接口返回来的body中包含新版本的包的下载地址以及该包的md5值用于下载完成之后进行校验用 - 客户端
访问该服务端接口,下载新版本的包(其实就是字节流的读写),然后进行覆盖安装
做完上面这2点其实就可以实现一个较为完整的全量更新功能。
客户端核心代码如下:
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/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
else
intent.setDataAndType(Uri.fromFile(new File(path)), "application/vnd.android.package-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 <以上是关于Android中的全量更新增量更新以及热更新的主要内容,如果未能解决你的问题,请参考以下文章
手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)
手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)
手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)
手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)