写项目版本升级这种功能用到的地方太多了,常见的apk升级方式主要有俩种,其一为应用内升级app,采用网络请求比较版本信息后,直接下载apk包升级安装,其二为跳转对应手机的应用商店让用户自行下载 ~

版本更新、升级方式

当前项目内进行版本升级,apk下载后替换升级包

跳转到应用商店的app详情页,由用户自行更新、下载

apk相关blog

  • 版本升级、更新
  • apk静默安装、卸载
  • Dialog花样使用

2021年已经适配Android 7.0、8.0、9.0,请放心使用,如未适配可能产生以下问题~

  • 安装报错 FileUriExposedException
  • 请求报错 UnknownServiceException
  • apk下载后无法调起安装界面


  • 实现效果
  • 基础配置
  • 7.0、9.0适配
  • 权限导入
  • build依赖
  • 方法归纳
  • 版本信息
  • 升级弹框
  • 下载安装
  • 实现过程
  • 老版(兼容7.0之前)
  • 新版(兼容7.0、8.0、9.0)
  • 所遇问题解析
  • Apk安装阶段报错:FileUriExposedException
  • 下载成功,无法调起新版apk
  • 安装完成,重新启动app


实现效果

android 手机怎么把android9升级到android10 android版本怎么升级到9.0.0_Android

基础配置

以下俩点均为必须进行适配的版本,如不适配很难正常使用 ~

7.0、9.0适配

7.0适配 - 以下仅是部分代码配置,具体配置方式可前往Android7.0中的共享文件 ~进行查看,当然也可以继续往下看

<provider
      android:name="android.support.v4.content.FileProvider"
      android:authorities="com.nk.machine.fileprovider"
      android:exported="false"
      android:grantUriPermissions="true">
      <meta-data
          android:name="android.support.FILE_PROVIDER_PATHS"
          android:resource="@xml/file_provider" />
  </provider>

9.0适配 - Android9.0 - http请求限制,9.0之后之后禁止http访问,如需http访问需要进行域名授权 ~

权限导入

8.0适配 - Android 8.0兼容适配

<!--安装应用的权限(8.0及以上会需要用户手动打开允许安装未知应用的权限,但在provider_paths的配置路径里,可以配置不需要用户手动打开权限,也可跳转自动安装) -->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions" />
    <!--应用卸载权限,可不加!-->
    <uses-permission android:name="permission.REQUEST_DELETE_PACKAGES" />
    <uses-permission
        android:name="android.permission.DELETE_PACKAGES"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.READ_PROFILE"/>
    <uses-permission android:name="android.permission.WRITE_PROFILE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
build依赖

build.gradle(Model)加入okhttp的依赖

implementation 'com.squareup.okhttp3:okhttp:3.11.0'
    implementation 'com.squareup.okio:okio:1.15.0'

方法归纳

此处放置在版本更新中常用的一些方法 …

版本信息
  • 获取版本号
/**
     * 返回版本号
     * 对应build.gradle中的versionCode
     * @param context context
     * @return String
     */
    public static String getVersionCode(Context context) {
        String versionCode = "";
        try {
            PackageManager packageManager = context.getPackageManager();
            PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
            versionCode = String.valueOf(packInfo.versionCode);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return versionCode;
    }
  • 获取版本名称
/**
     * 获取版本名称
     */
    public static String getVersionName(Context mContext) {
        // 获取package管理者  需要上下文
        PackageManager packageManager = mContext.getPackageManager();
        //参数说明   参数一是获取哪一个包名的package的信息 (application包括了activity所以也会包括activity的信息)
        //参数二的信息就是设置是不是之后去
        //获取包名的方法
        String packageName = mContext.getPackageName();

        try {
            PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
            //获取里面的信息
            String versionName = packageInfo.versionName;
            return versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        // 获取里面的信息
        // applicationInfo.
        return null;
    }
升级弹框
  • 比较当前版本与线上版本进行,然后弹出是否升级提示框

提示框可用系统提供的,也可以用自定义的,这个主要看UI,UE如何决定,下面附带我的弹框

/**
     * 弹出提示更新的dialog
     */
    private void showUpdateDialog() {
        CustomDialog customDialog = new CustomDialog(SettingActivity.this);
        customDialog.setMsg("检查到有最新版本,是否更新?");
        customDialog.setTitle("版本更新提示");
        customDialog.setCancelText("暂不更新");
        customDialog.setConfirmText("立刻更新");
        customDialog.setOnSureClickListener(new CustomDialog.OnSureClickListener() {
            @Override
            public void sureClick() {
                //下载APK
                downloadApk();
            }
        });
        customDialog.show();
    }
  • CustomDialog - 自定义升级提示框(非必要实现)

以下的方式是使用伪自定义控件实现的特定Dialog,如果嫌麻烦可以去看看我写的Dialog花样使用,不同点主要是Dialog实现简单,便捷一些 ~

Effect :

android 手机怎么把android9升级到android10 android版本怎么升级到9.0.0_版本更新_02


Code:

package com.bakheet.garage.widget;

import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.widget.TextView;

import com.bakheet.garage.R;
import com.bakheet.garage.utils.ToolUtil;

/**
 * date  2017/11/27.
 * desc:
 */
public class CustomDialog extends Dialog {

    private TextView tvTitle;
    private TextView tvMsg;
    private TextView tvCancel;
    private TextView tvConfirm;
    private String sureString, titleString, msgString, msgPeople, cancelText, confirmText;

    private OnSureClickListener onSureClickListener;
    private OnCancelClickListener onCancelClickListener;
    private int msgColor;

    public CustomDialog(@NonNull Context context) {
        super(context, R.style.CarDialog);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.dialog_custom_view);
        //setCanceledOnTouchOutside(false);
        initView();
        initData();
        initEvent();
    }

    private void initView() {
        tvTitle = (TextView) findViewById(R.id.tv_title);
        tvMsg = (TextView) findViewById(R.id.tv_msg);
        tvCancel = (TextView) findViewById(R.id.tv_cancel);
        tvConfirm = (TextView) findViewById(R.id.tv_confirm);
    }

    private void initData() {
    //这里的ToolUtil.isStringNull相当于 TextUtils.empty()的判空
        if (!ToolUtil.isStringNull(sureString)) {
            tvConfirm.setText(sureString);
        }
        if (!ToolUtil.isStringNull(titleString)) {
            tvTitle.setVisibility(View.VISIBLE);
            tvTitle.setText(titleString);
        } else {
            tvTitle.setVisibility(View.GONE);
        }
        if (!ToolUtil.isStringNull(msgString)) {
            tvMsg.setText(msgString);
        }
        if (msgColor != 0) {
            tvMsg.setTextColor(msgColor);
        }
        if (!ToolUtil.isStringNull(sureString)) {
            tvConfirm.setText(sureString);
        }
        if (!ToolUtil.isStringNull(cancelText)) {
            tvCancel.setText(cancelText);
        } else {
            tvCancel.setText("取消");
        }

        if (!ToolUtil.isStringNull(confirmText)) {
            tvConfirm.setText(confirmText);
        } else {
            tvConfirm.setText("确定");
        }
    }

    public void setCancelText(String text) {
        this.cancelText = text;
    }

    public void setConfirmText(String text) {
        this.confirmText = text;
    }

    public void setTitle(String title) {
        this.titleString = title;
    }

    public void setMsgColor(int color) {
        this.msgColor = color;
    }

    public void setMsg(String msg) {
        this.msgString = msg;
    }

    public void setMsgPeople(String msgPeople) {
        this.msgPeople = msgPeople;
    }

    public void setSureString(String sureString) {
        this.sureString = sureString;
    }

    private void initEvent() {
        tvCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onCancelClickListener != null) {
                    onCancelClickListener.cancelClick();
                }
                CustomDialog.this.dismiss();
            }
        });
        tvConfirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onSureClickListener != null) {
                    onSureClickListener.sureClick();
                    CustomDialog.this.dismiss();
                }
            }
        });
    }

    /**
     * 设置确定按钮和取消被点击的接口
     */
    public interface OnSureClickListener {
        void sureClick();
    }

    public interface OnCancelClickListener {
        void cancelClick();
    }

    /**
     * 设置取消按钮的显示内容和监听
     */
    public void setNoOnclickListener(OnCancelClickListener onCancelClickListener) {
        this.onCancelClickListener = onCancelClickListener;
    }

    /**
     * 设置确定按钮的显示内容和监听
     */
    public void setOnSureClickListener(OnSureClickListener onSureClickListener) {
        this.onSureClickListener = onSureClickListener;
    }
}

dialog_custom_view

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@drawable/bg_rec_r3_white"
        android:minWidth="270dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/d10"
            android:text="版本升级"
            android:textColor="@color/gray2"
            android:textSize="@dimen/s18"
            android:visibility="visible"/>


        <TextView
            android:id="@+id/tv_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/d10"
            android:textColor="@color/black3"
            tools:text="If you are happy ,you can do everything"/>

        <LinearLayout
            android:layout_marginTop="@dimen/d15"
            android:layout_width="match_parent"
            android:layout_height="@dimen/d45"
            android:background="@drawable/bg_rec_r4_down"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tv_cancel"
                android:layout_width="@dimen/d0"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="@string/dialog_cancel"
                android:textColor="@color/gray3"
                android:textSize="@dimen/s14"/>

            <View
                android:layout_width="@dimen/d1"
                android:layout_height="match_parent"
                android:layout_marginBottom="@dimen/d12"
                android:layout_marginTop="@dimen/d12"
                android:background="@color/gray4"/>

            <TextView
                android:id="@+id/tv_confirm"
                android:layout_width="@dimen/d0"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="@string/dialog_confirm"
                android:textColor="@color/blue1"
                android:textSize="@dimen/s14"/>
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>
下载安装
  • 加入下载进度条,然后下载最新apk
/**
     * 从服务器端下载最新apk
     */
    private void downloadApk() {
        //显示下载进度
        ProgressDialog dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.setProgressNumberFormat("");
        dialog.setCancelable(false);
        dialog.show();

        //访问网络下载apk
        new Thread(new DownloadApk(dialog)).start();
    }
  • 开始下载apk
private class DownloadApk implements Runnable {
        private ProgressDialog dialog;
        InputStream is;
        FileOutputStream fos;

        public DownloadApk(ProgressDialog dialog) {
            this.dialog = dialog;
        }
        
        @Override
        public void run() {
            OkHttpClient client = new OkHttpClient();
            String url = "apk下载地址(一般是构造中传进的参数)";
            Request request = new Request.Builder().get().url(url).build();
            try {
                okhttp3.Response response = client.newCall(request).execute();
                if (response.isSuccessful()) {
                    Log.d("tag", "开始下载apk");
                    //获取内容总长度
                    long contentLength = response.body().contentLength();
                    //设置最大值
                    dialog.setMax((int) contentLength);
                    //保存到sd卡
                    File apkFile = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis() + ".apk");
                    fos = new FileOutputStream(apkFile);
                    //获得输入流
                    is = response.body().byteStream();
                    //定义缓冲区大小
                    byte[] bys = new byte[1024];
                    int progress = 0;
                    int len = -1;
                    while ((len = is.read(bys)) != -1) {
                        try {
                            Thread.sleep(1);
                            fos.write(bys, 0, len);
                            fos.flush();
                            progress += len;
                            //设置进度
                            dialog.setProgress(progress);
                        } catch (InterruptedException e) {
                            Message msg = Message.obtain();
                            msg.what = SHOW_ERROR;
                            msg.obj = "ERROR:10002";
                            handler.sendMessage(msg);
                            //                            load2Login();
                        }
                    }
                    //下载完成,提示用户安装
                    installApk(apkFile);
                }
            } catch (IOException e) {
                Message msg = Message.obtain();
                msg.what = SHOW_ERROR;
                msg.obj = "ERROR:10003";
                handler.sendMessage(msg);
                //                load2Login();
            } finally {
                //关闭io流
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    is = null;
                }
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    fos = null;
                }
            }
            dialog.dismiss();
        }
    }
  • 下载完成,提示用户安装

Android 7.0 之前

private void installApk(File file) {
        //调用系统安装程序
        Intent intent = new Intent();
        intent.setAction("android.intent.action.VIEW");
        intent.addCategory("android.intent.category.DEFAULT");
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        startActivityForResult(intent, 119);
    }

Android 7.0 之后

首先要适配8.0的一个安装权限,同时要是适配7.0共享文件的授权操作

/**
     * 兼容8.0
     */
    private void checkIsAndroidO(File file) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (!context.getPackageManager().canRequestPackageInstalls()) {
                //打开权限
                Uri packageURI = Uri.parse("package:" + context.getPackageName());
                //注意这个是8.0新API
                Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
                context.startActivity(intent);
                installApk(file);
            } else {
                //有权限,直接安装
                installApk(file);
            }
        } else {
            //安卓8.0以下,直接安装
            installApk(file);
        }
    }
    
    /**
     * 下载完成,提示用户安装
     */
    private void installApk(File file) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(new File(file.getPath())), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        context.startActivity(intent);
    }

实现过程
老版(兼容7.0之前)

SettingActivity

package com.bakheet.garage.mine.activity;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.bakheet.garage.R;
import com.bakheet.garage.base.ActivityCollector;
import com.bakheet.garage.base.BaseActivity;
import com.bakheet.garage.http.HttpManager;
import com.bakheet.garage.http.ObjectResult;
import com.bakheet.garage.utils.DeviceUtils;
import com.bakheet.garage.utils.SpUtil;
import com.bakheet.garage.utils.TlogUtils;
import com.bakheet.garage.utils.ToastUtils;
import com.bakheet.garage.widget.CustomDialog;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import butterknife.BindView;
import butterknife.OnClick;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import retrofit2.Call;
import retrofit2.Response;

/**
 * @author YongLiu
 * @date 2017/11/27
 */
public class SettingActivity extends BaseActivity {
    @BindView(R.id.rl_version)
    RelativeLayout mRlVersion;
    private String versionCode;
    private String backGroundVersion = "5";
    public static final int SHOW_UPDATE_DIALOG = 0;
    public static final int SHOW_ERROR = 1;
    
    @Override
    protected int getLayoutId() {
        return R.layout.activity_setting;
    }

    @Override
    protected void init(Bundle savedInstanceState) {
        setToolBarTitle(getString(R.string.title_setting));
		//获取当前版本
        versionCode = DeviceUtils.getVersionCode(this);
        //backGroundVersion:请求接口后返回的版本信息(按理这里是请求服务器后进行的设置,我只是为了自测方便写了假值)
        if (Integer.parseInt(versionCode) < Integer.parseInt(backGroundVersion)) {
            mVersion.setText("最新版本为 V1.2 ,请更新");
            mRlVersion.setEnabled(true);
        } else {
            mVersion.setText("您当前已是最新版本");
            mRlVersion.setEnabled(false);
        }
    }

	//成功与失败状态下的回调 ,看自己的需求
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                /** 弹出提示更新的dialog   */
                case SHOW_UPDATE_DIALOG:
                   // showUpdateDialog();
                    break;

                /** 提示错误    */
                case SHOW_ERROR:
                    Toast.makeText(SettingActivity.this, msg.obj+"", Toast.LENGTH_LONG).show();
                    //load2Login();
                    break;
                default:
                    break;
            }
        }
    };

    @OnClick({R.id.rl_update_password, R.id.tv_quit_account, R.id.rl_msg_inform, R.id.rl_version})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.rl_version:
                showUpdateDialog();
                break;
            default:
                break;
        }
    }

    /**
     * 弹出提示更新的dialog
     */
    private void showUpdateDialog() {
        CustomDialog customDialog = new CustomDialog(SettingActivity.this);
        customDialog.setMsg("检查到有最新版本,是否更新?");
        customDialog.setTitle("版本更新提示");
        customDialog.setCancelText("暂不更新");
        customDialog.setConfirmText("立刻更新");
        customDialog.setOnSureClickListener(new CustomDialog.OnSureClickListener() {
            @Override
            public void sureClick() {
                //下载APK
                downloadApk();
            }
        });
        customDialog.show();
    }

    /**
     * 从服务器端下载最新apk
     */
    private void downloadApk() {
        //显示下载进度
        ProgressDialog dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.setProgressNumberFormat("");
        dialog.setCancelable(false);
        dialog.show();

        //访问网络下载apk
        new Thread(new DownloadApk(dialog)).start();
    }

    /**
     * 访问网络下载apk
     */
    private class DownloadApk implements Runnable {
        private ProgressDialog dialog;
        InputStream is;
        FileOutputStream fos;

        public DownloadApk(ProgressDialog dialog) {
            this.dialog = dialog;
        }

        @Override
        public void run() {
            OkHttpClient client = new OkHttpClient();
            String url = "https://raw.githubusercontent.com/WVector/AppUpdateDemo/master/apk/app-debug.apk";
            Request request = new Request.Builder().get().url(url).build();
            try {
                okhttp3.Response response = client.newCall(request).execute();
                if (response.isSuccessful()) {
                    Log.d("tag", "开始下载apk");
                    //获取内容总长度
                    long contentLength = response.body().contentLength();
                    //设置最大值
                    dialog.setMax((int) contentLength);
                    //保存到sd卡
                    File apkFile = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis() + ".apk");
                    fos = new FileOutputStream(apkFile);
                    //获得输入流
                    is = response.body().byteStream();
                    //定义缓冲区大小
                    byte[] bys = new byte[1024];
                    int progress = 0;
                    int len = -1;
                    while ((len = is.read(bys)) != -1) {
                        try {
                            Thread.sleep(1);
                            fos.write(bys, 0, len);
                            fos.flush();
                            progress += len;
                            //设置进度
                            dialog.setProgress(progress);
                        } catch (InterruptedException e) {
                            Message msg = Message.obtain();
                            msg.what = SHOW_ERROR;
                            msg.obj = "ERROR:10002";
                            handler.sendMessage(msg);
                            //                            load2Login();
                        }
                    }
                    //下载完成,提示用户安装
                    installApk(apkFile);
                }
            } catch (IOException e) {
                Message msg = Message.obtain();
                msg.what = SHOW_ERROR;
                msg.obj = "ERROR:10003";
                handler.sendMessage(msg);
                //                load2Login();
            } finally {
                //关闭io流
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    is = null;
                }
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    fos = null;
                }
            }
            dialog.dismiss();
        }
    }

    /**
     * 下载完成,提示用户安装
     */
    private void installApk(File file) {
        //调用系统安装程序
        Intent intent = new Intent();
        intent.setAction("android.intent.action.VIEW");
        intent.addCategory("android.intent.category.DEFAULT");
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        startActivityForResult(intent, 119);
    }
新版(兼容7.0、8.0、9.0)

新版的版本升级,我直接抽了一个Present出来,90%的代码可以直接使用

package com.nk.machine.present;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.Settings;
import android.support.v4.content.FileProvider;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;

import com.alibaba.fastjson.JSON;
import com.nk.machine.R;
import com.nk.machine.base.HttpUrl;
import com.nk.machine.base.VersionConstant;
import com.nk.machine.model.APKModel;
import com.nk.machine.tools.PackageUtils;
import com.nk.machine.tools.ToastTool;
import com.nk.machine.tools.ToolUtil;
import com.zhy.http.okhttp.OkHttpUtils;
import com.zhy.http.okhttp.callback.StringCallback;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * @author MrLiu
 * @date 2021/5/14
 * desc 版本升级
 */
public class VersionPresent {
    private Activity context;

    public VersionPresent(Activity context) {
        this.context = context;
    }

	//网络请求后台获取最新版本
    public void updateVersion() {
        OkHttpUtils
                .get()
                .url("接口url")
                //有没有参数要传,取决于后台要求
                .addParams("参数1", "值")
                .build()
                .execute(new StringCallback() {
                    @Override
                    public void onError(Call call, Exception e, int id) {
                    }

                    @Override
                    public void onResponse(String response, int id) {
                    	//部分解析,不可抄袭,因为都不一样
                        APKModel apkModel = JSON.parseObject(response, APKModel.class);
                        if (apkModel.isSuccess()) {
                            APKModel.ExtInfoBean extInfo = apkModel.getExtInfo();
                            APKModel.ExtInfoBean.ApkManagerBean apkManager = extInfo.getApkManager();
                            if (apkManager != null) {
                                //    ---版本更新,此处的逻辑可以通用---
                                int currentCode = PackageUtils.getVersionCode(context);
                                String versionCode = apkManager.getVersionCode();
                                if (currentCode == Integer.parseInt(versionCode)) {
                                    hintDialog();
                                } else if (currentCode < Integer.parseInt(versionCode)) {
                                    upgradeDialog(apkManager.getUrl(), apkManager.getDescription());
                                } else {
                                    ToastTool.centerShow("版本更新功能出现异常,请联系管理员");
                                }
                            } else {
                                ToastTool.shortShow("版本更新后台无数据,请联系管理员");
                            }
                        } else {
                            ToastTool.shortShow(apkModel.getMsg());
                        }
                    }
                });
    }

    /**
     * 提示对话框
     */
    public void hintDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.BasicDialogStyle);
        AlertDialog alertDialog = builder.setMessage("当前为最新版本")
                .setPositiveButton("确定", null)
                .create();
        alertDialog.show();
        setLocation(alertDialog);
    }

    /**
     * 更新对话框
     */
    public void upgradeDialog(String downUrl, String description) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.BasicDialogStyle);
        AlertDialog alertDialog = builder.setMessage(ToolUtil.isStringNull(description) ? "发现新版本" : "发现新版本,更新内容:" + description)
                .setPositiveButton("立即更新", (dialog, which) -> downloadApk(downUrl))
                .setNeutralButton("以后再说", null)
                .create();
        alertDialog.show();
        setLocation(alertDialog);
    }

    /**
     * 设置Dialog于屏幕正中间
     */
    private void setLocation(Dialog dialog) {
        //放在show()之后,不然有些属性是没有效果的,比如height和width
        Window dialogWindow = dialog.getWindow();
        WindowManager m = context.getWindowManager();
        // 获取屏幕宽、高
        Display d = m.getDefaultDisplay();
        // 获取对话框当前的参数值
        WindowManager.LayoutParams p = dialogWindow.getAttributes();
        // 设置宽度 - 宽度设置为屏幕的0.95
        p.width = (int) (d.getWidth() * 0.95);
        //设置显示位置
        p.gravity = Gravity.CENTER;
        //p.alpha = 0.8f;//设置透明度
        dialogWindow.setAttributes(p);
    }

    /**
     * 从服务器端下载最新apk
     */
    private void downloadApk(String url) {
        //显示下载进度
        ProgressDialog dialog = new ProgressDialog(context);
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.setMessage("正在下载,请稍后...");
        dialog.setProgressNumberFormat("");
        dialog.setCancelable(false);
        dialog.show();
        setLocation(dialog);

        //访问网络下载apk
        new Thread(new DownloadApk(url, dialog)).start();
    }

    /**
     * 访问网络下载apk
     */
    private class DownloadApk implements Runnable {
        private ProgressDialog dialog;
        private String url;
        InputStream is;
        FileOutputStream fos;

        public DownloadApk(String url, ProgressDialog dialog) {
            this.url = url;
            this.dialog = dialog;
        }

        @Override
        public void run() {
            OkHttpClient client = new OkHttpClient.Builder()
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .build();
            Request request = new Request
                    .Builder()
                    .get()
                    .url(url)
                    .build();
            try {
                Call call = client.newCall(request);
                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        if (dialog != null) {
                            dialog.dismiss();
                        }
                        ToastTool.centerShow("下载失败,请重新下载");
                        Log.e("tag", "请求错误");
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        if (call.isCanceled()) {
                            IOException exception = new IOException("Request Canceled");
                            Log.e("tag", "请求取消:" + exception.getMessage());
                            return;
                        }
                        if (response.isSuccessful()) {
                            Log.e("tag", "开始下载apk");
                            //获取内容总长度
                            long contentLength = response.body().contentLength();
                            //设置最大值
                            dialog.setMax((int) contentLength);
                            //保存到sd卡
                            File apkFile = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis() + ".apk");
                            fos = new FileOutputStream(apkFile);
                            //获得输入流
                            is = response.body().byteStream();
                            //定义缓冲区大小
                            byte[] bys = new byte[1024];
                            int progress = 0;
                            int len = -1;
                            while ((len = is.read(bys)) != -1) {
                                try {
                                    Thread.sleep(1);
                                    fos.write(bys, 0, len);
                                    fos.flush();
                                    progress += len;
                                    //设置进度
                                    dialog.setProgress(progress);
                                } catch (InterruptedException e) {
                                    Log.e("tag", "InterruptedException:" + e.getMessage());
                                }
                            }

                            //关闭io流
                            if (is != null) {
                                try {
                                    is.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                is = null;
                            }
                            if (fos != null) {
                                try {
                                    fos.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                fos = null;
                            }
                            dialog.dismiss();
                            //下载完成,提示用户安装
                            checkIsAndroidO(apkFile);
                        }
                    }
                });
            } catch (Exception e) {
                Log.e("tag", "Exception:" + e.getMessage());
            }
        }
    }

    /**
     * 兼容8.0
     */
    private void checkIsAndroidO(File file) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (!context.getPackageManager().canRequestPackageInstalls()) {
                //打开权限
                Uri packageURI = Uri.parse("package:" + context.getPackageName());
                //注意这个是8.0新API
                Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
                context.startActivity(intent);
                installApk(file);
            } else {
                //有权限,直接安装
                installApk(file);
            }
        } else {
            //安卓8.0以下,直接安装
            installApk(file);
        }
    }

    /**
     * 下载完成,提示用户安装
     */
    private void installApk(File file) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(new File(file.getPath())), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        context.startActivity(intent);
    }
}

所遇问题解析

仅记录我自己在使用中遇到的问题

Apk安装阶段报错:FileUriExposedException

通过Android7.0中的共享文件进行治理即可 ~

涉及:新版installApk方法

/**
     * 下载完成,提示用户安装
     */
    private void installApk(File file) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(new File(file.getPath())), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        context.startActivity(intent);
    }
下载成功,无法调起新版apk
  1. 首先看上是否适配7.0
  2. 查看是否加入应用安装的android.permission.REQUEST_INSTALL_PACKAGES权限
  3. 是否在代码中打开权限,涉及方法如下
/**
     * 兼容8.0
     */
    private void checkIsAndroidO(File file) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (!context.getPackageManager().canRequestPackageInstalls()) {
                //打开权限
                Uri packageURI = Uri.parse("package:" + context.getPackageName());
                //注意这个是8.0新API
                Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
                context.startActivity(intent);
                installApk(file);
            } else {
                //有权限,直接安装
                installApk(file);
            }
        } else {
            //安卓8.0以下,直接安装
            installApk(file);
        }
    }
安装完成,重新启动app

注意 :Android 部分手机通过下面这个方法无法实现跳转,可能是6.0,7.0这种系统原因,可能是机型的问题

步骤:

  1. 安装完成之后startActivityForResult,如:
startActivityForResult(intent, 119);
  1. 重写onActivityReenter
@Override
    public void onActivityReenter(int resultCode, Intent data) {
        super.onActivityReenter(resultCode, data);
        if (resultCode == 119) {
            startAPP("我们的包名");
        }
    }
  1. app唤醒方法
public void startAPP(String appPackageName) {
        try {
            Intent intent = this.getPackageManager().getLaunchIntentForPackage(appPackageName);
            startActivity(intent);
        } catch (Exception e) {
            Toast.makeText(this, "没有安装", Toast.LENGTH_LONG).show();
        }
    }