目录
一、 前提准备
1、获取服务器
2、使用工具操作云服务器
二、Json格式网页
三、创建file_paths.xml及修改AndroidManifest.xml
四、在java代码加入更新检测代码
效果如图:
可以强制更新和非强制更新,和浏览器下载安装包。
首先去获取云服务器,如:阿里云服务器(学生可免费领取六个月)、腾讯云服务器、华为云服务器等。具体操作参考:从零开始用阿里云服务器搭建网页_阿里云怎么装修网页打开_zstar-_的博客-CSDN博客
云服务器用于存放json格式网页和安装包,json可以对app进行版本控制更新、显示版本、显示更新内容、提供安装包下载位置等信息。
可以使用指令控制,也可以使用工具控制服务器,选用putty软件进行远程控制,winSCP软件进行文件传输。下载链接
以下是winSCP界面的服务器文件:可按下面文件找到存放安装包和网页的文件夹。
如下就是对APP进行版本更新的网页和安装包存放的文件夹:
以下是html网页代码:
UFCFans
下面是相关变量说明:
{
hasUpdate: true, //是否有更新 默认true
NoIgnorable: true, //不 可忽略更新 强制:true 非强制:false
versionCode: 51, //服务端的版本号
versionName: "2.4.1", //服务端的版本名
updateLog: "\n1、更改保存图片的存储路径。\n2、更改软件更新的提示模式。\n3、调整非强制更新控制方式。\n4、新增存储权限的申请授权位置。", //更新提示内容
apkUrl: "https://www.yuming.com/assets/a某o130.apk",//新版本APK直链下载地址
webUrl: "https://yirj.gitee.io/update111",//浏览器更新链接,随意放(直链、蓝奏、官网均可)
apkSize: "29.5MB" //新版本的大小 随意写就好
}
创建file_paths.xml
修改AndroidManifest.xml
.........
在软件的检测更新界面的Oncreate
方法下,加入检测更新的代码。
NoIgnorable变量为true则强制更新,无取消按钮,为false有取消按钮;
versionName, updateLog, apkSize, apkUrl, webUrl和upl变量为你的网页网址,如:UFCFanshttp://8.130.127.118:8080
public static JSONObject jSONObject = null;
private static boolean hasUpdate = true;
private static boolean NoIgnorable; //是否有更新。 不可忽略的更新
private static int versionCode = 0;
private static String versionName, updateLog, apkSize, apkUrl, webUrl;
private static String[] upl;
/**
* 获取当前使用的软件包的版本号
*/
public int getVersionCode() {
try {
//获取packagemanager的实例
PackageManager packageManager = getPackageManager();
//getPackageName()是你当前类的包名,0代表是获取版本信息
PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);
Log.e("TAG", "版本号" + packInfo.versionCode); //更新软件用的是版本号
return packInfo.versionCode;
} catch (Exception e) {
e.printStackTrace();
}
return 1;
}
/**
* 提示版本更新的对话框
*/
public void showDialogUpdate() {
//hasUpdate为true且程序版本号<服务端版本号,提示用户更新
if (NoIgnorable) { //NoIgnorable为true 就是强制更新,无“取消”按钮
// 这里的属性可以一直设置,因为每次设置后返回的是一个builder对象
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setCancelable(false); //开启强制更新,无法触摸外部关闭
builder.setTitle("是否升级到" + versionName + "版本?").
// 设置提示框的图标
// setIcon(R.drawable.ic_launcher).
// 设置要显示的信息
setMessage("新版本大小:" + apkSize + "\n" + updateLog).
// 设置确定按钮
setPositiveButton("更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
loadNewVersionProgress();//程序内直接下载最新的版本程序
}
}).
setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新
@Override
public void onClick(DialogInterface dialog, int which) {
Uri uri = Uri.parse(webUrl);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
finish(); //强制更新,点击后销毁应用
}
});
// 显示对话框
builder.create().show();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setCancelable(false); //非强制更新,屏蔽此行,触摸外部或退出键可关闭
builder.setTitle("是否升级到" + versionName + "版本?").
setMessage("新版本大小:" + apkSize + "\n" + updateLog).
setPositiveButton("更新", new DialogInterface.OnClickListener() {//正按钮
@Override
public void onClick(DialogInterface dialog, int which) {
loadNewVersionProgress();//下载最新的版本程序
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {//负按钮
@Override
public void onClick(DialogInterface dialog, int which) {
// finish(); //屏蔽销毁,不做任何处理
}
}).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮
@Override
public void onClick(DialogInterface dialog, int which) {
Uri uri = Uri.parse(webUrl);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//finish(); //屏蔽销毁,访问浏览器,程序不会退出
}
});
// 显示对话框
builder.create().show();
}
}
//轮询验证两个更新链接,返回有效链接
public static String checkUrl(String[] ltl) {
String resultUrl = null;
for (String url : ltl) {
resultUrl = url;
try {
//调用检查链接是否有效的方法
String result = get(url);
if (result != null && result.length() != 0) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return resultUrl;
}
//检查更新链接是否有效的方法
public static String get(String url) {
URL infoUrl = null;
InputStream inStream = null;
String line = "";
try {
infoUrl = new URL(url);
URLConnection connection = infoUrl.openConnection();
HttpURLConnection httpConnection = (HttpURLConnection) connection;
int responseCode = httpConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
inStream = httpConnection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));
StringBuilder strber = new StringBuilder();
while ((line = reader.readLine()) != null)
strber.append(line + "\n");
inStream.close();
int start = strber.indexOf("{");
int end = strber.indexOf("}");
String json = strber.substring(start, end + 1);
return json;
}
} catch (MalformedURLException e) {
} catch (IOException e) {
}
return "";
}
/**
* 使用检查过的有效链接,获取服务端json数据
*/
public static JSONObject GetServerJson() {
URL infoUrl = null;
InputStream inStream = null;
String line = "";
try {
String uurl = checkUrl(upl);
infoUrl = new URL(uurl); //json格式信息的API,使用案例。
URLConnection connection = infoUrl.openConnection();
HttpURLConnection httpConnection = (HttpURLConnection) connection;
int responseCode = httpConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
inStream = httpConnection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));
StringBuilder strber = new StringBuilder();
while ((line = reader.readLine()) != null)
strber.append(line + "\n");
inStream.close();
int start = strber.indexOf("{");
int end = strber.indexOf("}");
String json = strber.substring(start, end + 1);
if (json != null) {
try {
jSONObject = new JSONObject(json);
hasUpdate = jSONObject.getBoolean("hasUpdate");
NoIgnorable = jSONObject.getBoolean("NoIgnorable");
versionCode = jSONObject.getInt("versionCode");
versionName = jSONObject.getString("versionName");
updateLog = jSONObject.getString("updateLog");
apkSize = jSONObject.getString("apkSize");
apkUrl = jSONObject.getString("apkUrl");
webUrl = jSONObject.getString("webUrl");
return jSONObject;
} catch (JSONException e) {
e.printStackTrace();
}
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 应用内直链升级方法,下载新版本程序
*/
private void loadNewVersionProgress() {
ProgressDialog pd = new ProgressDialog(this);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
//获取手机的根目录存储位置,以及直链链接最后一个“/”后文字作为文件名展示在界面
pd.setMessage("下载最新版本安装包到:" + Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + apkUrl.substring(apkUrl.lastIndexOf("/") + 1));
pd.setCancelable(false); //开启强制更新,触摸屏幕其他位置无法关闭
pd.show();
//启动子线程下载任务
new Thread() {
@Override
public void run() {
Looper.prepare();
try {
File file = getFileFromServer(apkUrl, pd); //调用下载服务方法动态显示进度
//sleep(2000);//设置休眠两秒之后再启动安装
installApk(file); //下载完成直接安装
// pd.dismiss(); //屏蔽,结束掉进度条对话框,防止强制更新出Bug
} catch (Exception e) {
//直链下载apk异常失败提示容错。
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setCancelable(false);//开启强制更新,触摸屏幕其他位置无法关闭
builder.setTitle("下载失败:").setMessage("1.请检查存储权限是否开启。\n2.请检查网络连接是否正常。\n3.使用浏览器下载新版本。\nother:"+e.getMessage());
builder.setPositiveButton("退出", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
finish();
}
}).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新
@Override
public void onClick(DialogInterface dialog, int which) {
Uri uri = Uri.parse(webUrl);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
if (NoIgnorable) {
finish(); //强制更新,点击后销毁应用
}else{
pd.dismiss(); //不强制更新,跳转到浏览器并销毁应用内下载失败的进度条
}
}
});
builder.create().show();
}
Looper.loop();
}
}.start();
}
/**
* 安装apk
*/
protected void installApk(File file) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= 24) {
Uri apkUri = FileProvider.getUriForFile(this, "com.example.ufcfans.fileprovider", file); //这里要写你程序的包名,已实验不可使用${applicationId}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
this.startActivity(intent);
}
/**
* 从服务器获取apk文件的代码
* 传入网址uri,进度条对象即可获得一个File文件
* (要在子线程中执行哦)
*/
public static File getFileFromServer(String uri, ProgressDialog pd) throws Exception {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
URL url = new URL(uri);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
//获取到文件的大小
pd.setMax(conn.getContentLength()); //字节的方式显示下载进度
InputStream is = conn.getInputStream();
//获取直链链接最后一个“/”后文字作为文件名,下载存储到手机
File file = new File(Environment.getExternalStorageDirectory(), apkUrl.substring(apkUrl.lastIndexOf("/", apkUrl.lastIndexOf("")) + 1));
FileOutputStream fos = new FileOutputStream(file);
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buffer = new byte[1024];
int len;
int total = 0;
while ((len = bis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
total += len;
//获取当前下载量
pd.setProgress(total);//字节方式显示下载量
}
fos.close();
bis.close();
is.close();
return file;
} else {
return null;
}
}
/**
* 权限的验证及处理,相关方法
*/
private void getReadPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE}, 10001);
} else {//没有则请求获取权限,示例权限是:存储权限,需要其他权限请更改或者替换
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE}, 10001);
}
} else {//如果已经获取到了权限则直接进行下一步操作
Log.e(TAG, "全部权限已经授权成功");
}
}
}
/**
* 一个或多个权限请求结果回调
* 循环回调获取权限,除非勾选禁止后不再询问,之后提示用户引导用户去设置
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 10001:
for (int i = 0; i < grantResults.length; i++) {
// 如果拒绝获取权限
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
//判断是否勾选禁止后不再询问
boolean flag = ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i]);
if (flag) {
getReadPermissions();
return;//用户权限是一个一个的请求的,只要有拒绝,剩下的请求就可以停止,再次请求打开权限了
} else { // 勾选不再询问,并拒绝
Toast.makeText(this, "请到设置中打开权限", Toast.LENGTH_LONG).show();
return;
}
}
}
//Toast.makeText(this, "权限开启完成", Toast.LENGTH_LONG).show();
break;
default:
break;
}
}
/**
* 忽略https的证书校验 的相关方法
*/
public static void handleSSLHandshake() {
try {
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} catch (Exception ignored) {
}
}
至此,已完成对应用的版本控制更新,通过
更改html文件的versionVode控制更新,如:默认为1,改为2时,用户APP会弹出更新框。