安卓开发实战之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终端的输出结果。
客户端需要实现:
我们了解到不同的需求会有不同的操作方式和界面展示。
-
关于下载方式,无论是在应用内部下载还是通过通知栏更新:
- app内下载更新
在下载和安装完成之后,我们才能进行操作。结果如下:
- 通知栏下载更新
在这种情况下,应用内不会进行更新,而是将其放置在通知栏中,这样不会对当前app的使用产生影响。具体效果如下:
-
应用程序的更新可以分为三种类型:强制更新、推荐更新和无需更新。
-
强制更新
-
推荐更新
-
无需更新
-
具体方案:
- 实现bean用于对接后端接口实现app的更新(不写网络请求模拟本地数据也需要这个模型)
- 使用retrofit来请求版本更新接口
- 下载apk我们分别使用DownloadManager和普通的httpurlconnection
- 通过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): 用于设置标题。
我们使用downloaderManager来下载apk文件,并将downManager.enqueue(request)返回的id值保存在本地。通过这个id,我们可以获取apk文件的下载路径和下载状态,并根据状态更新通知栏显示。
当您首次成功下载时,将会弹出一个安装界面。
如果用户在未点击安装的情况下按下返回键,然后在某个时间点再次使用我们的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(Downloapublic 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.COMPOString 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的使用方法:
构建下载请求:
修改后的文案如下: ```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):根据下载状态查询下载任务
如果想取消下载,则可以调用remove方法完成,此方法可以将下载任务和已经下载的文件同时删除:
下面是修改后的文案: ```java downManager.remove(id); ``` 好了,具体的都讲得差不多了。本文以同步到我的 GitHub。示例传送门:AppUpdate.rar
后记
鉴于版本更新没有对6.0的权限和7.0的FileProvider做适配,导致6.0和7.0的安装失败或者7.0直接crash,本文将不再解释,自行处理这里提供一个已经适配的demo下载
还木有评论哦,快来抢沙发吧~