Android应用内部检查更新

准备工作:

    1、下载Tomcat用来存放高版本的apk,不知道Tomcat如何安装使用的可以查看下这篇文章:Apache Tomcat 安装配置图文详细教程

    2、进入Tomcat安装目录下的bin点击startup.bat开启运行Tomcat服务器(我的是window系统)

    3、电脑 和 手机连接同一局域网下的WiFi,并且电脑要关闭防火墙

                                                

4、放一个高版本apk到Tomcat服务器上:

                                                  

 

 

下面开始编码啦

1、检查Tomcat服务器上是否有最新版本。

// DownloadUtil.java

  /**
   * 检查T服务器上是否有最新版本
   * @param listener:
   */
  public void checkAppVersion(VersionUpdateListener listener){
    LogUtil.i(TAG, "checkAppVersion()...... listener=" + listener);

    // 手机当前安装应用的版本
    int localVersionCode = 1;
    Context context = MyApp.getContext();
    try {
      localVersionCode = context.getPackageManager().
              getPackageInfo(context.getPackageName(), 0).versionCode;
    } catch (PackageManager.NameNotFoundException e) {
      e.printStackTrace();
    }

    DownloadService downloadService = mRetrofit.create(DownloadService.class);
    Call<AppVersionBean> appVersionBeanCall = downloadService.checkAppVersion();

    int localVersion = localVersionCode;
    appVersionBeanCall.enqueue(new Callback<AppVersionBean>() {
      @Override
      public void onResponse(@NotNull Call<AppVersionBean> call, Response<AppVersionBean> response) {

        int responseCode = response.code();
        LogUtil.i(TAG, "checkAppVersion()->onResponse()...  responseCode=" + responseCode);

        AppVersionBean appVersionBean = response.body();
        if (responseCode == 200 && appVersionBean != null) {
          int versionCode = appVersionBean.getElements().get(0).getVersionCode();
          LogUtil.i(TAG, "checkAppVersion()->onResponse()...  localVersion="
                  + localVersion + ", appVersionBean=" + appVersionBean);
          if (versionCode > localVersion) {
            if (listener != null){
              listener.neetUpdateVersion();
            }
          }
        }else {
          if (listener != null){
            listener.checkNewVersionFail(responseCode);
          }
        }
      }


  /**
   * 获取服务器高版本状态监听
   */
  public interface VersionUpdateListener{
    void  neetUpdateVersion();
    void  checkNewVersionFail(int errCode);
  }

2、如果服务器有最新应用版本则下载:

// DownloadUtil.java

  public void downloadApk(DownloadFileListener downloadFileListener){
    stopDownloadApk = false;
    if (apkFile.isAbsolute()){
      apkFile.delete();
    }
    download(null, apkFile.getPath(), downloadFileListener);
  }

  public void download(String url, final String path, final DownloadFileListener downloadFileListener) {
    LogUtil.i(TAG, "download()......url=" + url);
    DownloadService service = mRetrofit.create(DownloadService.class);

    Call<ResponseBody> call = service.downloadApk();
    call.enqueue(new Callback<ResponseBody>() {
      @Override
      public void onResponse(@NonNull Call<ResponseBody> call, @NonNull final Response<ResponseBody> response) {

        LogUtil.i(TAG, "download()->onResponse()... isCanceled = " + call.isCanceled() + ",isExecuted=" + call.isExecuted());
        //将Response写入到从磁盘中,注意这个方法是运行在子线程中的
        if (response.code() == 200 && response.body() != null){
          writeResponseToDisk(call, response, downloadFileListener);
        }
      }

      @Override
      public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable throwable) {
        downloadFileListener.onDownloadFail("网络错误~");
      }
    });
  }

  private void writeResponseToDisk(Call<ResponseBody> call, Response<ResponseBody> response, DownloadFileListener downloadFileListener) {
    //从response获取输入流以及总大小
    writeFileFromStream(apkFile, call, response, downloadFileListener);
  }

  /** 将输入流写入文件 */
  private void writeFileFromStream(File pathFile, Call<ResponseBody> call, Response<ResponseBody> response, DownloadFileListener downloadFileListener) {
    LogUtil.i(TAG, "writeFileFromIS()... apkFile.path = " + pathFile.getPath());
    downloadFileListener.onDownloadStart();

    if (!pathFile.exists()) {
      if (!pathFile.getParentFile().exists()){
        pathFile.getParentFile().mkdir();
      }

      try {
        pathFile.createNewFile();
      } catch (IOException e) {
        downloadFileListener.onDownloadFail("create New File IOException");
        e.printStackTrace();
      }
    }

    final ResponseBody body = response.body();
    long totalLength = body.contentLength();
    InputStream is = body.byteStream();

    OutputStream os = null;
    long currLength = 0;
    try {
      os = new BufferedOutputStream(new FileOutputStream(pathFile));
      byte[] data = new byte[sBufferSize];
      int len;
      while ((len = is.read(data, 0, sBufferSize)) != -1 && !stopDownloadApk) {

        os.write(data, 0, len);
        currLength += len;

        //计算当前下载进度
        downloadFileListener.onDownloadProgress((int) (100 * currLength / totalLength));
      }
      //下载完成,并返回保存的文件路径
      if (currLength == totalLength){
        downloadFileListener.onDownloadFinish(pathFile.getAbsolutePath());
      }else if (stopDownloadApk){
        call.cancel();
        downloadFileListener.onDownloadCancel();
      }else{
        downloadFileListener.onDownloadFail("download apk incomplete!");
      }

    } catch (IOException e) {
      e.printStackTrace();
      downloadFileListener.onDownloadFail(e.getMessage());
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
      try {
        if (os != null) {
          os.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }


/**
 * 下载文件时的状态监听器
 */
public interface DownloadFileListener {

    void onDownloadStart();//下载开始
 
    void onDownloadProgress(int progress);//下载进度
 
    void onDownloadFinish(String path);//下载完成
 
    void onDownloadFail(String errorInfo);//下载失败

    void onDownloadCancel();//下载请求取消

}

3、下载后完成后打开系统安装界面安装。

// DownloadUtil.java

  /**
   * 安装APK
   */
  public void installAPK() {
    LogUtil.i(TAG, "installAPK()...");
    try {
      if (!apkFile.exists()) {
        return;
      }

      Context context = MyApp.getContext();
      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//安装完成后打开新版本
      intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给目标应用一个临时授权

      LogUtil.i(TAG, "installAPK()...  Build.VERSION.SDK_INT=" + Build.VERSION.SDK_INT);

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//判断版本大于等于7.0

        //如果SDK版本>=24,即:Build.VERSION.SDK_INT >= 24,使用FileProvider兼容安装apk
        String packageName = context.getApplicationContext().getPackageName();
        String authority = packageName + ".fileprovider";
        Uri apkUri = FileProvider.getUriForFile(context, authority, apkFile);
        intent.setDataAndType(apkUri, URI_DATA_TYPE);
      } else {
        intent.setDataAndType(Uri.fromFile(apkFile), URI_DATA_TYPE);
      }
      context.startActivity(intent);
      //注意有些人添加这个后,导致下载后不能导致,提示解析包出现问题
//      android.os.Process.killProcess(android.os.Process.myPid());

    } catch (Exception e) {
      e.printStackTrace();
      LogUtil.i(TAG, "installAPK()... e=" + e);
    }
  }

4、如上步骤123(检查服务器版本、下载高版本、安装应用)都是 DownloadUtil.java文件的,如下是该文件的完整代码

public class DownloadUtil {

  private static final String TAG = DownloadUtil.class.getSimpleName();
  private static final String URI_DATA_TYPE = "application/vnd.android.package-archive";
  private static final int sBufferSize = 8192;
  private final Retrofit mRetrofit;
  private static DownloadUtil sDownloadUtil;
  // 保存APK的文件名
  private static final String saveFileName = "NewVersion.apk";
  private static File apkFile;
  private volatile boolean stopDownloadApk = false;

  public DownloadUtil(){

    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .build();

    mRetrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            //通过线程池获取一个线程,指定callback在子线程中运行。
            .callbackExecutor(Executors.newSingleThreadExecutor())
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    apkFile = new File(MyApp.getContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), saveFileName);

  }

  public void stopDownloadApk(){
    stopDownloadApk = true;
  }

  public Retrofit getRetrofit(){
    return mRetrofit;
  }

  public static DownloadUtil getInstance(){
    if (sDownloadUtil == null) {
      synchronized (DownloadUtil.class){
        if (sDownloadUtil == null) {
          sDownloadUtil = new DownloadUtil();
        }
      }
    }
    return sDownloadUtil;
  }

  /**
   * 获取服务器高版本状态监听
   */
  public interface VersionUpdateListener{
    void  neetUpdateVersion();
    void  checkNewVersionFail(int errCode);
  }

  /**
   * 下载文件时的状态监听器
   */
  public interface DownloadFileListener {

    void onDownloadStart();//下载开始

    void onDownloadProgress(int progress);//下载进度

    void onDownloadFinish(String path);//下载完成

    void onDownloadFail(String errorInfo);//下载失败

    void onDownloadCancel();//下载请求取消

  }

  /**
   * 检查T服务器上是否有最新版本
   * @param listener:
   */
  public void checkAppVersion(VersionUpdateListener listener){
    LogUtil.i(TAG, "checkAppVersion()...... listener=" + listener);

    // 手机当前安装应用的版本
    int localVersionCode = 1;
    Context context = MyApp.getContext();
    try {
      localVersionCode = context.getPackageManager().
              getPackageInfo(context.getPackageName(), 0).versionCode;
    } catch (PackageManager.NameNotFoundException e) {
      e.printStackTrace();
    }

    DownloadService downloadService = mRetrofit.create(DownloadService.class);
    Call<AppVersionBean> appVersionBeanCall = downloadService.checkAppVersion();

    int localVersion = localVersionCode;
    appVersionBeanCall.enqueue(new Callback<AppVersionBean>() {
      @Override
      public void onResponse(@NotNull Call<AppVersionBean> call, Response<AppVersionBean> response) {

        int responseCode = response.code();
        LogUtil.i(TAG, "checkAppVersion()->onResponse()...  responseCode=" + responseCode);

        AppVersionBean appVersionBean = response.body();
        if (responseCode == 200 && appVersionBean != null) {
          int versionCode = appVersionBean.getElements().get(0).getVersionCode();
          LogUtil.i(TAG, "checkAppVersion()->onResponse()...  localVersion="
                  + localVersion + ", appVersionBean=" + appVersionBean);
          if (versionCode > localVersion) {
            if (listener != null){
              listener.neetUpdateVersion();
            }
          }
        }else {
          if (listener != null){
            listener.checkNewVersionFail(responseCode);
          }
        }
      }

      @Override
      public void onFailure(Call<AppVersionBean> call, Throwable t) {
        if (listener != null){
          listener.checkNewVersionFail(1);
        }
      }
      
    });
  }

  public void downloadApk(DownloadFileListener downloadFileListener){
    stopDownloadApk = false;
    if (apkFile.isAbsolute()){
      apkFile.delete();
    }
    download(null, apkFile.getPath(), downloadFileListener);
  }

  public void download(String url, final String path, final DownloadFileListener downloadFileListener) {
    LogUtil.i(TAG, "download()......url=" + url);
    DownloadService service = mRetrofit.create(DownloadService.class);

    Call<ResponseBody> call = service.downloadApk();
    call.enqueue(new Callback<ResponseBody>() {
      @Override
      public void onResponse(@NonNull Call<ResponseBody> call, @NonNull final Response<ResponseBody> response) {

        LogUtil.i(TAG, "download()->onResponse()... isCanceled = " + call.isCanceled() + ",isExecuted=" + call.isExecuted());
        //将Response写入到从磁盘中,注意这个方法是运行在子线程中的
        if (response.code() == 200 && response.body() != null){
          writeResponseToDisk(call, response, downloadFileListener);
        }
      }

      @Override
      public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable throwable) {
        downloadFileListener.onDownloadFail("网络错误~");
      }
    });
  }

  private void writeResponseToDisk(Call<ResponseBody> call, Response<ResponseBody> response, DownloadFileListener downloadFileListener) {
    //从response获取输入流以及总大小
    writeFileFromStream(apkFile, call, response, downloadFileListener);
  }

  /** 将输入流写入文件 */
  private void writeFileFromStream(File pathFile, Call<ResponseBody> call, Response<ResponseBody> response, DownloadFileListener downloadFileListener) {
    LogUtil.i(TAG, "writeFileFromIS()... apkFile.path = " + pathFile.getPath());
    downloadFileListener.onDownloadStart();

    if (!pathFile.exists()) {
      if (!pathFile.getParentFile().exists()){
        pathFile.getParentFile().mkdir();
      }

      try {
        pathFile.createNewFile();
      } catch (IOException e) {
        downloadFileListener.onDownloadFail("create New File IOException");
        e.printStackTrace();
      }
    }

    final ResponseBody body = response.body();
    long totalLength = body.contentLength();
    InputStream is = body.byteStream();

    OutputStream os = null;
    long currLength = 0;
    try {
      os = new BufferedOutputStream(new FileOutputStream(pathFile));
      byte[] data = new byte[sBufferSize];
      int len;
      while ((len = is.read(data, 0, sBufferSize)) != -1 && !stopDownloadApk) {

        os.write(data, 0, len);
        currLength += len;

        //计算当前下载进度
        downloadFileListener.onDownloadProgress((int) (100 * currLength / totalLength));
      }
      //下载完成,并返回保存的文件路径
      if (currLength == totalLength){
        downloadFileListener.onDownloadFinish(pathFile.getAbsolutePath());
      }else if (stopDownloadApk){
        call.cancel();
        downloadFileListener.onDownloadCancel();
      }else{
        downloadFileListener.onDownloadFail("download apk incomplete!");
      }

    } catch (IOException e) {
      e.printStackTrace();
      downloadFileListener.onDownloadFail(e.getMessage());
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
      try {
        if (os != null) {
          os.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * 安装APK
   */
  public void installAPK() {
    LogUtil.i(TAG, "installAPK()...");
    try {
      if (!apkFile.exists()) {
        return;
      }

      Context context = MyApp.getContext();
      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//安装完成后打开新版本
      intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给目标应用一个临时授权

      LogUtil.i(TAG, "installAPK()...  Build.VERSION.SDK_INT=" + Build.VERSION.SDK_INT);

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//判断版本大于等于7.0

        //如果SDK版本>=24,即:Build.VERSION.SDK_INT >= 24,使用FileProvider兼容安装apk
        String packageName = context.getApplicationContext().getPackageName();
        String authority = packageName + ".fileprovider";
        Uri apkUri = FileProvider.getUriForFile(context, authority, apkFile);
        intent.setDataAndType(apkUri, URI_DATA_TYPE);
      } else {
        intent.setDataAndType(Uri.fromFile(apkFile), URI_DATA_TYPE);
      }
      context.startActivity(intent);
      //注意有些人添加这个后,导致下载后不能导致,提示解析包出现问题
//      android.os.Process.killProcess(android.os.Process.myPid());

    } catch (Exception e) {
      e.printStackTrace();
      LogUtil.i(TAG, "installAPK()... e=" + e);
    }
  }

}

AppVersionBean应用版本信息实体类(也就是打包apk自动生的output-metadata.json,里面记录了versionCode,这里我用来检查获取服务器高版本用的,你也可以不用这么麻烦自己写个简单的只要包含一个versionCode高版本号信息的json放到服务器就行): 

/**
 *  app版本信息实体类,用于app内部在线更新使用
 */
public class AppVersionBean {

    private int version;
    private ArtifactTypeDTO artifactType;
    private String applicationId;
    private String variantName;
    private List<ElementsDTO> elements;
    private String elementType;

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public ArtifactTypeDTO getArtifactType() {
        return artifactType;
    }

    public void setArtifactType(ArtifactTypeDTO artifactType) {
        this.artifactType = artifactType;
    }

    public String getApplicationId() {
        return applicationId;
    }

    public void setApplicationId(String applicationId) {
        this.applicationId = applicationId;
    }

    public String getVariantName() {
        return variantName;
    }

    public void setVariantName(String variantName) {
        this.variantName = variantName;
    }

    public List<ElementsDTO> getElements() {
        return elements;
    }

    public void setElements(List<ElementsDTO> elements) {
        this.elements = elements;
    }

    public String getElementType() {
        return elementType;
    }

    public void setElementType(String elementType) {
        this.elementType = elementType;
    }

    public static class ArtifactTypeDTO {
        private String type;
        private String kind;

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getKind() {
            return kind;
        }

        public void setKind(String kind) {
            this.kind = kind;
        }

        @Override
        public String toString() {
            return "ArtifactTypeDTO{" +
                    "type='" + type + '\'' +
                    ", kind='" + kind + '\'' +
                    '}';
        }
    }

    public static class ElementsDTO {
        private String type;
        private List<?> filters;
        private int versionCode;
        private String versionName;
        private String outputFile;

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public List<?> getFilters() {
            return filters;
        }

        public void setFilters(List<?> filters) {
            this.filters = filters;
        }

        public int getVersionCode() {
            return versionCode;
        }

        public void setVersionCode(int versionCode) {
            this.versionCode = versionCode;
        }

        public String getVersionName() {
            return versionName;
        }

        public void setVersionName(String versionName) {
            this.versionName = versionName;
        }

        public String getOutputFile() {
            return outputFile;
        }

        public void setOutputFile(String outputFile) {
            this.outputFile = outputFile;
        }

        @Override
        public String toString() {
            return "ElementsDTO{" +
                    "type='" + type + '\'' +
                    ", filters=" + filters +
                    ", versionCode=" + versionCode +
                    ", versionName='" + versionName + '\'' +
                    ", outputFile='" + outputFile + '\'' +
                    '}';
        }
    }

    @Override
    public String toString() {
        return "AppVersionBean{" +
                "version=" + version +
                ", artifactType=" + artifactType +
                ", applicationId='" + applicationId + '\'' +
                ", variantName='" + variantName + '\'' +
                ", elements=" + elements +
                ", elementType='" + elementType + '\'' +
                '}';
    }
}

4、定义一个使用网络访问api的java接口

public interface DownloadService {

    /**
     * output-metadata.json 这是是打包apk时自动生成的,这里用来放到服务器上判断服务器apk版本号的
     * @return
     */
    @GET("output-metadata.json")
    Call<AppVersionBean> checkAppVersion();

    /**
     * 下载apk
     * @return
     */
    @Streaming
    @GET("AppInsideUpdate.apk")
    Call<ResponseBody> downloadApk();
}

app模块的build.gradle添加retrofit依赖

    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

下面来看看整个升级的实际效果(由于我手机安装的是debug版本所有最后面安装失败,如果手机安装正式版apk就能正常安装): 

注意事项点:

1、记得添加安装需要的权限和android 7.0以上需要通过FileProvider来获取文件路径uri。(项目的res目录新建xml目录,再新建network_config.xml文件)

    <!-- 8.0手机安装软件 -->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

    <!-- 网络权限 -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- 存储权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

         <!-- android 7.0通过FileProvider来获取文件路径uri -->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.liuxx.appinsideupdate.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

file_paths.xml文件代码:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!--安装包文件存储路径-->
    <external-path name="sdcard" path="/" />
</paths>

2、如果你使用的是http来访问网络而不是https的话需要在manifest文件的application标签中添加如下代码表示允许明文传输。(项目的res目录新建xml目录,再新建network_config.xml文件)

    <application
        ......
        android:networkSecurityConfig="@xml/network_config">
network_config.xml文件内容:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

由于篇幅有限,介绍可能没有面面俱到,需要完整代码及Tomat服务器(已经放了高版本的apk)的压缩包的朋友可以到这里下载(导入代码运行时记得代码中修改局域网的ip, 手机和电脑连接同一局域网,打开Tomcat服务器,关闭电脑防火墙):App内部升级代码和放有高版本apk的Tomcat服务器压缩包

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值