准备工作:
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服务器压缩包