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实现文件增量更新 (超详细)

手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)

android 客户端增量更新