写项目版本升级这种功能用到的地方太多了,常见的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
实现效果
基础配置
以下俩点均为必须进行适配的版本,如不适配很难正常使用 ~
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 :
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
涉及:新版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
- 首先看上是否适配7.0
- 查看是否加入应用安装的
android.permission.REQUEST_INSTALL_PACKAGES
权限 - 是否在代码中打开权限,涉及方法如下
/**
* 兼容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这种系统原因,可能是机型的问题
步骤:
- 安装完成之后startActivityForResult,如:
startActivityForResult(intent, 119);
- 重写onActivityReenter
@Override
public void onActivityReenter(int resultCode, Intent data) {
super.onActivityReenter(resultCode, data);
if (resultCode == 119) {
startAPP("我们的包名");
}
}
- 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();
}
}