安卓应用开发版本更新

安卓开发实战之app之版本更新升级(DownloadManager和http下载)完整实现

 转载

博主文章分类:14 其他随笔 ©著作权
wx610a246613cb02021-08-05 17:02:56

文章标签安卓实战开发androidide服务器版本更新文章分类Android移动开发阅读数1605

前言

本文将介绍关于App升级与更新的内容。通常情况下,用户可以通过以下两种方式获取升级提醒:

  • 一种是通过应用市场 获取
  • 一种是打开应用之后提醒用户更新升级

用户点击升级按钮后,更新操作通常开始执行。升级操作可以分为两种形式:

  • 一般升级
  • 强制升级

如何进行app升级操作:

  • 应用市场的app升级

在App Store中进行升级需要上传新版的App。一旦我们完成了新版本的开发,就会将其上传至App Store。经过审核后,即可正式发布于应用市场并上线。此时,用户若已安装该应用市场,则会收到有关我们App新版本升级的提醒通知。

  • 应用内升级

除了可以在应用市场升级,我们还可以在应用内升级,在应用内升级主要是通过调用服务器端接口获取应用的升级信息,然后通过获取的服务器升级应用信息与本地的App版本比对,若服务器下发的最新的App版本高于本地的版本号,则说明有新版本发布,那么我们就可以执行更新操作了,否则忽略掉即可。

很明显,我们并不把应用市场提醒的升级作为重点。本文主要关注于实现不同角度的app升级场景,以便在未来的开发过程中直接使用。

服务器端:

  • 服务端提供一个接口,或者网址,这里提供一个网址如下:
作为一名安卓程序员,通常在测试时需要编写一个服务器端(真是麻烦),我会使用nodejs来创建一个本地服务器,以便测试应用的版本更新验证。这个本地服务器的地址是:http://192.168.191.1:8081/update
  • 根据请求的结果,我这里就写一个简单的json
{ data: { appname: hoolay.apk, serverVersion: 1.0.2, serverFlag: 1,  lastForce : 1,  updateurl: http://releases.b0.upaiyun.com/hoolay.apk,  
```javascript
const express = require('express');
const app = express();
const fs = require(fs);
app.get('/update', function (req, res) {
fs.readFile(__dirname + /version.json, 'utf8', function (err, data) {
console.log(data);
res.end(data);
});
});
var server = app.listen(8081);
```
  • 效果如下:

请参考下图,显示的是打开浏览器后的结果。

上图展示了WebStorm终端的输出结果。

客户端需要实现:

我们了解到不同的需求会有不同的操作方式和界面展示。

  1. 关于下载方式,无论是在应用内部下载还是通过通知栏更新:

    • app内下载更新

    在下载和安装完成之后,我们才能进行操作。结果如下:

    • 通知栏下载更新

    在这种情况下,应用内不会进行更新,而是将其放置在通知栏中,这样不会对当前app的使用产生影响。具体效果如下:

  2. 应用程序的更新可以分为三种类型:强制更新、推荐更新和无需更新。

    • 强制更新

    • 推荐更新

    • 无需更新

具体方案:

  1. 实现bean用于对接后端接口实现app的更新(不写网络请求模拟本地数据也需要这个模型)
  2. 使用retrofit来请求版本更新接口
  3. 下载apk我们分别使用DownloadManager和普通的httpurlconnection
  4. 通过BroadcastReceiver来监听是否下载完成

Bean preparation

首先,我们需要对服务端提供的JSON进行解析。为此,我们需要创建一个Bean类来严格按照JSON文件的格式进行定义。

```java
package com.losileeya.appupdate.bean;
/**
* User: Losileeya ([email protected])
* Date: 2016-09-27
* Time: 11:20
* 类描述:版本更新的实体与你服务器的字段相匹配
* @version :
*/
public class UpdateAppInfo {
public UpdateInfo data; // 信息
public Integer error_code; // 错误代码
public String error_msg; // 错误信息
public static class UpdateInfo{
// app名字
public String appname;
//服务器版本
public String serverVersion;
//服务器标志
public String serverFlag;
//强制升级
public String lastForce;
//app最新...
```
改为:
```java
package com.losileeya.appupdate.bean;
/**
* User: Losileeya ([email protected])
* Date: 2016-09-27
* Time: 11:20 AM
*
* Description:
*
*/
public class UpdateAppInfo {
/**
* Information about the update.
*/
public UpdateInfo data;
/**
Error code.
*/
public Integer error_code;
/**
Error message.
*/
public String error_msg;
/**
Update information for the app.
*/
public static class UpdateInfo {
/**
App name.
*/
public String appname;
/**
Server version of the app.
*/
public String serverVersion;
/**
Server flag of the app.
*/
public String serverFlag;
/**
Latest force upgrade for the app.
*/
public String lastForce;
/**
Latest version of the app.
*/
...
```   

我在这里运用retrofit和rxjava进行练笔。

先加入 依赖

compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid
compile 'io.reactivex:rxjava:1.1.0' // 推荐同时加载RxJava
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' // Retrofit网络处理
compile 'com.squareup.retro... 
public interface ApiService {
// 实际开发过程中可能的接口方式
@GET(update)
Observable getUpdateInfo(
@Query(appname) String appname,
@Query(serverVersion) String appVersion
);
} 
public class ServiceFactory {
private static final String BASEURL = http://192.168.191.1:8081/;
public static  T createServiceFrom(final Class serviceClass) {
Retrofit adapter = new Retrofit.Builder()
.baseUrl(BASEURL)
.addCallAdapterFactory(RxJavaCallAdapter
@SuppressWarnings(unused)
public static void checkUpdate(String appCode, String curVersion, final CheckCallBack updateCallback) {
ApiService apiService = ServiceFactory.createServiceFrom(ApiService.class);
apiService.getUpdateInfo()
// .apiService.getUpdateInfo(appCode, curVersion)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(UpdateAppInfo updateAppInfo) {
// TODO: Implement the logic for handling the update info
// You can use the 'updateCallback' to notify the caller about the update information.
// For example:
// if (updateCallback != null) {
//     updateCallback.onCheckComplete(updateAppInfo);
//}
}
});
} 

请提供结果回调监听。

```java
public interface CheckCallBack {
// 检测成功或失败的相关接口
void onSuccess(UpdateAppInfo updateInfo);
void onError();
}
```
具体使用接口的处理:
1.
2.
3.
4. 
// 检查网络是否需要更新版本
CheckUpdateUtils.checkUpdate(apk, 1.0.0, new CheckUpdateUtils.CheckCallBack() {
@Override
public void onSuccess(UpdateAppInfo updateInfo) {
String isForce = updateInfo.data.getLastForce(); // 是否需要强制更新
String downUrl = updateInfo.data.getUpdateurl(); // apk下载地址
String updateinfo = updateInfo.data.getUpgradeinfo(); // apk更新详情
String appName = updateInfo.data.getAppname();
if (isForce.equals(1) && !TextUtils.isEmpty(updateinfo)) { // 强制更新
UpdateAppInfo.UpdateInfo info = new UpdateAppInfo.UpdateInfo();
info.setLastForce(1);
info.setAppname(我日你);
info.setUpgradeinfo(whejjefjhrherkjreghgrjrgjjhrh);
info.setUpdateurl(http://releases.b0.upaiyun.com/hoolay.apk);
if(info.getLastForce().equals(1)){//强制更新
forceUpdate(); 
private void forceUpdate(final Context context, final String appName, final String downUrl, final String updateinfo) {
mDialog = new AlertDialog.Builder(context);
mDialog.setTitle(appName + 有新的更新!);
mDialog.setMessage(updateinfo);
mDialog.setPositiveButton(立即更新, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!canDownloadState()) {
showDownloadSettings();
}
}
});
}   
改写后的文案如下:
```java
import androidx.appcompat.app.AlertDialog;
```
然后显然是不能按返回键取消的,我们需要   
使用谷歌推荐的DownloadManager实现下载,设置为不可取消。 

在api level 9之后,我们可以使用Android自带的DownloadManager模块进行下载。通过通知栏,我们可以了解到该模块是系统自带的,并且它已经为我们处理了下载失败、重新下载等功能。因此,整个下载过程完全由系统负责,无需我们过多干预。

DownLoadManager.Query是一个用于查询下载信息的主要功能。

DownLoadManager.Request是一个用于发起下载请求的主要工具。

首先,让我们来看一下简单的实现:

以下是创建Request对象的代码:

DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkurl));
// 设置下载仅在Wi-Fi网络下进行
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
// 设置通知栏可见
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
request.setTitle(下载);
request.setDescription(apk正在下载);
request.setAllowe 
下面是关于request属性的一些信息,你可以参考:
1. 通过以下代码获取DownloadManager的实例:
```java
DownloadManager downManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
```
2. 使用以下代码将请求添加到下载队列中,并获取其ID:
```java
long id = downManager.enqueue(request);
```
希望对你有所帮助! 
addRequestHeader(String header, String value): 用于添加网络下载请求的HTTP头信息。
allowScanningByMediaScanner(): 用于设置是否允许本MediaScanner扫描。
setAllowedNetworkTypes(int flags): 设置下载时可使用的网络类型,默认为任何网络都可以下载。提供的网络常量有:NETWORK_BLUETOOTH、NETWORK_MOBILE、NETWORK_WIFI。
setAllowedOverRoaming(Boolean allowed): 设置漫游状态下是否可以下载。
setNotificationVisibility(int visibility): 设置下载时在状态栏显示通知信息的可见性。
setTitle(CharSequence title): 用于设置标题。   
  1. 我们使用downloaderManager来下载apk文件,并将downManager.enqueue(request)返回的id值保存在本地。通过这个id,我们可以获取apk文件的下载路径和下载状态,并根据状态更新通知栏显示。

  2. 当您首次成功下载时,将会弹出一个安装界面。

    如果用户在未点击安装的情况下按下返回键,然后在某个时间点再次使用我们的APP。

    当成功下载后,需验证本地apk的包名是否与当前程序相同,并且本地apk的版本号必须高于当前程序版本。若以上条件均满足,则直接启动安装程序。

代码实现的具体细节如下:

实现文件下载管理的方法包括创建请求并将其添加到下载队列中,然后通过返回的ID来获取下载路径和下载状态。

public class FileDownloadManager {
private DownloadManager downloadManager;
private Context context;
private static FileDownloadManager instance;
private FileDownloadManager(Context context) {
downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
this.context = context.getApplicationContext();
}
public static FileDownloadManager getInstance(Context context) {
if (instance == null) {
instance = new FileDownloadManager(context);
}
return instance;
}
/**
* @param uri
* @param title
* @param description
* @return download id
*/
public long startDownload(String uri, String title, String description, String appName) {
DownloadManager.Request req = new DownloadManager.Request(Uri.parse(uri));
req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
//req.setAllowedOverRoaming(false);
req.setNotificationVisibility(Downloa 
public class DownLoadApk {
public static final String TAG = DownLoadApk.class.getSimpleName();
public static void download(Context context, String url, String title, final String appName) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
long downloadId = sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L);
if (downloadId != -1L) {
FileDownloadManager fdm = FileDownloadManager.getInstance(context);
int status = fdm.getDownloadStatus(downloadId);
if (status == DownloadManager.STATUS_SUCCESSFUL) {
Uri uri = fdm.getDownloadUri(downloadId);
if (uri != null) {
if (compare(getApkInfo(context, uri.getPath()), context)) {
startInstall(context, uri);
return;
} else {
fdm.getDownloadManager().remove(downloadId);
}
}
start(context, url, title, appName);
} else if (status == DownloadManager.STATUS_FAILED) {
start(context, url, title ,appName);
} else {
Log.d(TAG,apk is already downloading);
}
} else {
start(context,url,title ,appName);
}
}
private static void start(Context context,String url,String title,String appName){
long id=FileDownloadManager.getInstance(context).startDownload(url,
title,下载完成后点击打开,appName);
SharedPreferences sp=PreferenceManager.getDefaultSharedPreferences
(context);sp.edit().putLong(DownloadManage 

监听app是否安装完成

public class ApkInstallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
long downloadApkId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
installApk(context, downloadApkId);
}
}
/**
* 安装apk
*/
private void installApk(Context context, long downloadApkId) {
// 获取存储ID
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
long downId = sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L);
if (downloadApkId == downId) {
DownloadManager downManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Uri downloadFileUri = downManager.getUriForDownloadedFile(downloadApkId);

配置清单:

请先授予网络下载的权限:

 <uses-permission android:name="android.permission.INTERNET"/>    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • 1.
  • 2.

另外,还需要添加以下静态广播权限:<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>= Build.VERSION_CODES.N) {
Uri uriForFile = FileProvider.getUriForFile(context, context.getPackageName() + .fileprovider, apkFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(uriForFile, application/vnd.android.package-archive);
} else {
Uri uriFromFile=Uri.fromFile(apkPath);//Android 7.0以下版本使用此方法获取URI
intent.setDataAndType(uriFromFile,application/vnd.android.package-archive);
}
context.startActivity(intent);
}
}

因此,在使用该组件之前,需要进行可用性判断:

private boolean canDownloadState() {
try {
int state = this.getPackageManager().getApplicationEnabledSetting(com.android.providers.downloads);
if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|| state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
|| state == PackageManager.COMPO 
String packageName = com.android.providers.downloads;
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse(package: + packageName));
startActivity(intent); 

概述DownloadManager的使用方法:

  1. 构建下载请求:

    修改后的文案如下:
    ```java
    DownloadManager.Request request = new DownloadManager.Request(url);
    request.setXXX(); // 设置请求属性
    ```
    或者
    ```java
    DownloadManager.Request request = new DownloadManager.Request(url);
    request.setXXX(); // 设置请求属性
    ```
    请注意,上述代码中的`setXXX()`应该替换为具体的方法名,以设置相应的请求属性。 
    调用`request.setXXX()`方法,然后使用downloadmanager对象的enqueue方法进行下载。该方法返回一个编号,用于标识此下载任务。 
    获取下载管理器实例,并将下载请求加入到下载队列中:
    ```java
    DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
    long downloadId = downloadManager.enqueue(request);
    ```
    为了对当前的所有任务进行保存和管理,我们可以使用 DownloadManager.Query 对象来获取这些信息。通过该对象,我们可以查询所有的下载任务信息。 

    使用setFilterById方法,可以根据任务编号来查询下载任务的信息。

    setFilterByStatus(int flags):根据下载状态查询下载任务

  2. 如果想取消下载,则可以调用remove方法完成,此方法可以将下载任务和已经下载的文件同时删除:

    下面是修改后的文案:
    ```java
    downManager.remove(id);
    ```
    好了,具体的都讲得差不多了。本文以同步到我的 GitHub。 

    示例传送门:AppUpdate.rar

    后记

    鉴于版本更新没有对6.0的权限和7.0的FileProvider做适配,导致6.0和7.0的安装失败或者7.0直接crash,本文将不再解释,自行处理这里提供一个已经适配的demo下载 

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注