写项目,版本升级这种功能用到的地方太多了~
文章结构
俩种升级方式:
当前项目内进行版本升级,apk下载替换升级
跳转对应的app应用商店详情页,由用户自己下载
权限导入
方法归纳
/**
* 返回版本号
* 对应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;
}
/**
* 弹出提示更新的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
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
/**
* 从服务器端下载最新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();
}
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);
}
完整的Activity代码(不含XML)
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);
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);
}
所遇问题解析:
解决方案:
1.清单文件加入以下代码
file_path.xml :
3.修改 installApk 方法
/**
* 下载完成,提示用户安装
* file 为File文件 或 fileName 主要看你上一个方法有没有转文件名
*/
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_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(this, "我们的包名.fileprovider", file);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + file),
"application/vnd.android.package-archive");
}
startActivityForResult(intent, 119);
}
借鉴文章:
Android 7.0 安装Apk时报错FileUriExposedException 解决
注意 :
Android 部分手机通过下面这个方法无法实现跳转,可能是6.0,7.0这种系统原因,可能是机型的问题
步骤:
1.安装完成之后startActivityForResult,如:
startActivityForResult(intent, 119);
2.重写onActivityReenter
@Override
public void onActivityReenter(int resultCode, Intent data) {
super.onActivityReenter(resultCode, data);
if (resultCode == 119) {
startAPP("我们的包名");
}
}
3.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();
}
}
借鉴文章 :
Android 通过包名打开App的代码