Android中的全量更新、增量更新以及热更新

时间:2025-05-11 07:23:35
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(); } }