安卓开发app版本更新
安卓开发实战之app之版本更新升级(DownloadManager和http下载)完整实现
转载
wx610a246613cb02021-08-05 17:02:56博主文章分类:14 其他随笔©著作权
文章标签安卓实战开发androidide服务器版本更新文章分类Android移动开发阅读数1605
前言
本文将讲解app的升级与更新。一般而言用户使用App的时候升级提醒有两种方式获得:
- 一种是通过应用市场 获取
- 一种是打开应用之后提醒用户更新升级
而更新操作一般是在用户点击了升级按钮之后开始执行的,这里的升级操作也分为两种形式:
- 一般升级
- 强制升级
app升级操作:
- 应用市场的app升级
在App Store中升级需要为App Store上传新版App,我们在新版本完成之后都会上传到App Store中,在审核完成之后就相当于完成了这个应用市场的发布了,也就是发布上线了。这时候如果用户安装了这个应用市场,那么就能看到我们的App有新版本的升级提醒了。
- 应用内升级
除了可以在应用市场升级,我们还可以在应用内升级,在应用内升级主要是通过调用服务器端接口获取应用的升级信息,然后通过获取的服务器升级应用信息与本地的App版本比对,若服务器下发的最新的App版本高于本地的版本号,则说明有新版本发布,那么我们就可以执行更新操作了,否则忽略掉即可。
显然应用市场提醒的升级不是我们的重点,本篇主要是对于app升级的场景来进行不同角度的实现,便于以后开发过程中直接拿去用就ok了。
服务器端:
- 服务端提供一个接口,或者网址,这里提供一个网址如下:
http://192.168.191.1:8081/update
- 1.
- 1
一般作为一个安卓程序员要测试还得写一个服务端(醉了),这里我就使用nodejs来搞一个本地的服务器来测试下app的版本更新检验。
- 根据请求的结果,我这里就写一个简单的json
{"data":{"appname": "hoolay.apk","serverVersion": "1.0.2","serverFlag": "1","lastForce" : "1","updateurl": "http://releases.b0.upaiyun.com/hoolay.apk","upgradeinfo": "V1.0.2版本更新,你想不想要试一下哈!!!"
},"error_code":"200","error_msg" :"蛋疼的认识"}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
然后我电脑上是装了webstrom的,没有装也没有关系但是必须有nodejs,现在都自带了express,表示并没有学过,所以简单的写个express_demo.js:
var express = require('express');
var app = express();
var fs = require("fs");
//此处设置为get请求,app里面直接写 (本机ip:8081/update)
app.get('/update', function (req, res) {//http://127.0.0.1:8081/updatefs.readFile( __dirname + "/" + "version.json", 'utf8', function (err, data) {//读取相同目录下的version.json文件console.log( data );//打印json数据res.end( data );//把json数据response回去});
})
var server = app.listen(8081, function () {//端口我这里写的是8081var host = server.address().addressvar port = server.address().portconsole.log("应用实例,访问地址为 http://%s:%s", host, port)})
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
有webstrom的直接选中文件run就ok了,没有直接 node express_demo.js,可以直接浏览器打开: http://127.0.0.1:8081/update
- 效果如下:
上图为打开浏览器后的显示结果。
上图为webstrom的终端显示结果。
客户端需要实现:
我们知道不同的需求有不同的操作方法和界面显示:
从是否为app内部下载还是通知栏更新:
- app内下载更新
这时我们必须等下载安装完全后才能进行操作,效果是这样的:
- 通知栏下载更新
这种情况是不在应用内更新,放在通知栏并不会影响当前app的使用,效果是这样的:
app更新分3种:强制更新,推荐更新,无需更新
强制更新
推荐更新
无需更新
具体思路:
- 实现bean用于对接后端接口实现app的更新(不写网络请求模拟本地数据也需要这个模型)
- 使用retrofit来请求版本更新接口
- 下载apk我们分别使用DownloadManager和普通的httpurlconnection
- 通过BroadcastReceiver来监听是否下载完成
准备bean
首先我们要去解析服务端给的json,那么我们就要来创建一个bean类了,这里是严格根据json文件的格式来的:
package com.losileeya.appupdate.bean;
/*** User: Losileeya (847457332@qq.com)* 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最新版本地址public String updateurl;//升级信息public String upgradeinfo;get...set...}get...set...
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
网络接口的实现
这里使用retrofit和rxjava来练笔
先加入 依赖
compile 'io.reactivex:rxandroid:1.1.0' // RxAndroidcompile 'io.reactivex:rxjava:1.1.0' // 推荐同时加载RxJavacompile 'com.squareup.retrofit:retrofit:2.0.0-beta2' // Retrofit网络处理compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' // Retrofit的rx解析库compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' // Retrofit的gson库
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
接下来网络接口的定制:
public interface ApiService {//实际开发过程可能的接口方式@GET("update")Observable<UpdateAppInfo> getUpdateInfo(@Query("appname") String appname, @Query("serverVersion") String appVersion);//以下方便版本更新接口测试@GET("update")Observable<UpdateAppInfo> getUpdateInfo();
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
通过工厂模式来创建ApiService :
public class ServiceFactory {private static final String BASEURL="http://192.168.191.1:8081/";public static <T> T createServiceFrom(final Class<T> serviceClass) {Retrofit adapter = new Retrofit.Builder().baseUrl(BASEURL).addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx适配器.addConverterFactory(GsonConverterFactory.create()) // 添加Gson转换器.build();return adapter.create(serviceClass);}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
版本检测接口的使用:
/*** 检查更新*/@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<UpdateAppInfo>() {@Overridepublic void onCompleted() {}@Overridepublic void onError(Throwable e) {}@Overridepublic void onNext(UpdateAppInfo updateAppInfo) {if (updateAppInfo.error_code == 0 || updateAppInfo.data == null ||updateAppInfo.data.updateurl == null) {updateCallback.onError(); // 失败} else {updateCallback.onSuccess(updateAppInfo);}}});}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
以上就是版本更新接口的调用,具体的rxjava+retrofit请自行学习 你真的会用Retrofit2吗?Retrofit2完全教程
附上结果回调监听:
public interface CheckCallBack{//检测成功或者失败的相关接口void onSuccess(UpdateAppInfo updateInfo);void onError();}
- 1.
- 2.
- 3.
- 4.
具体使用接口的处理:
//网络检查版本是否需要更新CheckUpdateUtils.checkUpdate("apk", "1.0.0", new CheckUpdateUtils.CheckCallBack() {@Overridepublic 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)){//强制更新forceUpdate(MainActivity.this,appName,downUrl,updateinfo);}else{//非强制更新//正常升级normalUpdate(MainActivity.this,appName,downUrl,updateinfo);}}@Overridepublic void onError() {noneUpdate(MainActivity.this);}});
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
实在不想写网络也好,直接使用假想数据做相关操作如下:
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(MainActivity.this,info.getAppname(),info.getUpdateurl(),info.getUpgradeinfo());}else{//非强制更新//正常升级 normalUpdate(MainActivity.this,info.getAppname(),info.getUpdateurl(),info.getUpgradeinfo());}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
更新dialog的使用注意:
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() {@Overridepublic void onClick(DialogInterface dialog, int which) {if (!canDownloadState()) {showDownloadSetting();return;}// DownLoadApk.download(MainActivity.this,downUrl,updateinfo,appName);AppInnerDownLoder.downLoadApk(MainActivity.this,downUrl,appName);}}).setCancelable(false).create().show();}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
上面以强制更新举个例子,因为AlertDialog在不同的版本下面表现的美观度不一致,所以我们需要
import android.support.v7.app.AlertDialog;
- 1.
- 1
然后显然是不能按返回键取消的,我们需要
.setCancelable(false)
- 1.
使用谷歌推荐的DownloadManager实现下载
Android自带的DownloadManager模块来下载,在api level 9之后,我们通过通知栏知道, 该模块属于系统自带, 它已经帮我们处理了下载失败、重新下载等功能。整个下载 过程全部交给系统负责,不需要我们过多的处理。
DownLoadManager.Query:主要用于查询下载信息。
DownLoadManager.Request:主要用于发起一个下载请求。
先看下简单的实现:
创建Request对象的代码如下:
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkurl));//设置在什么网络情况下进行下载request.setAllowedNetworkTypes(Request.NETWORK_WIFI);//设置通知栏标题request.setNotificationVisibility(Request.VISIBILITY_VISIBLE);request.setTitle("下载");request.setDescription("apk正在下载");request.setAllowedOverRoaming(false);//设置文件存放目录request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "mydown");
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
取得系统服务后,调用downloadmanager对象的enqueue方法进行下载,此方法返回一个编号用于标示此下载任务:
downManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
id= downManager.enqueue(request);
- 1.
- 2.
这里我们可以看下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):设置Notification的title信息
setDescription(CharSequence):设置Notification的message信息
setDestinationInExternalFilesDir、setDestinationInExternalPublicDir、 setDestinationUri等方法用于设置下载文件的存放路径,注意如果将下载文件存放在默认路径,那么在空间不足的情况下系统会将文件删除,所 以使用上述方法设置文件存放目录是十分必要的。
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
具体实现思路:
我们通过downloaderManager来下载apk,并且本地保存downManager.enqueue(request)返回的id值,并且通过这个id获取apk的下载文件路径和下载的状态,并且通过状态来更新通知栏的显示。
第一次下载成功,弹出安装界面
如果用户没有点击安装,而是按了返回键,在某个时候,又再次使用了我们的APP
如果下载成功,则判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本,如果都满足则直接启动安装程序。
具体代码实现:
文件下载管理的实现,包括创建request和加入队列下载,通过返回的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(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);//设置文件的保存的位置[三种方式]//第一种//file:///storage/emulated/0/Android/data/your-package/files/Download/update.apkreq.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, appName+".apk");//第二种//file:///storage/emulated/0/Download/update.apk//req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "update.apk");//第三种 自定义文件路径//req.setDestinationUri()// 设置一些基本显示信息req.setTitle(title);req.setDescription(description);//req.setMimeType("application/vnd.android.package-archive");return downloadManager.enqueue(req);//异步//dm.openDownloadedFile()}/*** 获取文件保存的路径** @param downloadId an ID for the download, unique across the system.* This ID is used to make future calls related to this download.* @return file path* @see FileDownloadManager#getDownloadUri(long)*/public String getDownloadPath(long downloadId) {DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);Cursor c = downloadManager.query(query);if (c != null) {try {if (c.moveToFirst()) {return c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));}} finally {c.close();}}return null;}/*** 获取保存文件的地址** @param downloadId an ID for the download, unique across the system.* This ID is used to make future calls related to this download.* @see FileDownloadManager#getDownloadPath(long)*/public Uri getDownloadUri(long downloadId) {return downloadManager.getUriForDownloadedFile(downloadId);}public DownloadManager getDownloadManager() {return downloadManager;}/*** 获取下载状态** @param downloadId an ID for the download, unique across the system.* This ID is used to make future calls related to this download.* @return int* @see DownloadManager#STATUS_PENDING* @see DownloadManager#STATUS_PAUSED* @see DownloadManager#STATUS_RUNNING* @see DownloadManager#STATUS_SUCCESSFUL* @see DownloadManager#STATUS_FAILED*/public int getDownloadStatus(long downloadId) {DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);Cursor c = downloadManager.query(query);if (c != null) {try {if (c.moveToFirst()) {return c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));}} finally {c.close();}}return -1;}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
app的检测安装的实现:
public class DownLoadApk {public static final String TAG = DownLoadApk.class.getSimpleName();public static void download(Context context, String url, String title,final String appName) {// 获取存储IDSharedPreferences 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(DownloadManager.EXTRA_DOWNLOAD_ID,id).commit();Log.d(TAG, "apk start download " + id);}public static void startInstall(Context context, Uri uri) {Intent install = new Intent(Intent.ACTION_VIEW);install.setDataAndType(uri, "application/vnd.android.package-archive");install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(install);}/*** 获取apk程序信息[packageName,versionName...]** @param context Context* @param path apk path*/private static PackageInfo getApkInfo(Context context, String path) {PackageManager pm = context.getPackageManager();PackageInfo info = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);if (info != null) {return info;}return null;}/*** 下载的apk和当前程序版本比较** @param apkInfo apk file's packageInfo* @param context Context* @return 如果当前应用版本小于apk的版本则返回true*/private static boolean compare(PackageInfo apkInfo, Context context) {if (apkInfo == null) {return false;}String localPackage = context.getPackageName();if (apkInfo.packageName.equals(localPackage)) {try {PackageInfo packageInfo = context.getPackageManager().getPackageInfo(localPackage, 0);if (apkInfo.versionCode > packageInfo.versionCode) {return true;}} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}}return false;}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
上面的代码可知:我们通过获取当前app的信息来比较是否需要下载和是否立即安装。第一次下载把downloadId保存到本地,用户下次进来的时候,取出保存的downloadId,然后通过downloadId来获取下载的状态信息。如果下载失败,则重新下载并且把downloadId存起来。如果下载成功,则判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本
,如果都满足则直接启动安装程序。
监听app是否安装完成
public class ApkInstallReceiver extends BroadcastReceiver {@Overridepublic 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) {// 获取存储IDSharedPreferences 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);if (downloadFileUri != null) {Intent install= new Intent(Intent.ACTION_VIEW);install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(install);}else{Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();}}}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
DownloadManager下载完成后会发出一个广播 android.intent.action.DOWNLOAD_COMPLETE
新建一个广播接收者即可:
清单配置:
先添加网络下载的权限:
<uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- 1.
- 2.
再添加静态广播:
<receiver android:name=".ApkInstallReceiver"><intent-filter><action android:name="android.intent.action.DOWNLOAD_COMPLETE" /></intent-filter></receiver>
- 1.
- 2.
- 3.
- 4.
- 5.
使用HttpUrlConnection下载
这种情况下载的话我们就不需要考虑id的问题,因为是直接在项目中下载,所以我们就是一个网络下载的过程,并且使用ProgressDialog显示下载信息及进度更新就ok了。
public class AppInnerDownLoder {public final static String SD_FOLDER = Environment.getExternalStorageDirectory()+ "/VersionChecker/";private static final String TAG = AppInnerDownLoder.class.getSimpleName();/*** 从服务器中下载APK*/@SuppressWarnings("unused")public static void downLoadApk(final Context mContext,final String downURL,final String appName ) {final ProgressDialog pd; // 进度条对话框pd = new ProgressDialog(mContext);pd.setCancelable(false);// 必须一直下载完,不可取消pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);pd.setMessage("正在下载安装包,请稍后");pd.setTitle("版本升级");pd.show();new Thread() {@Overridepublic void run() {try {File file = downloadFile(downURL,appName, pd);sleep(3000);installApk(mContext, file);// 结束掉进度条对话框pd.dismiss();} catch (Exception e) {pd.dismiss();}}}.start();}/*** 从服务器下载最新更新文件* * @param path* 下载路径* @param pd* 进度条* @return* @throws Exception*/private static File downloadFile(String path,String appName ,ProgressDialog pd) throws Exception {// 如果相等的话表示当前的sdcard挂载在手机上并且是可用的if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);// 获取到文件的大小pd.setMax(conn.getContentLength());InputStream is = conn.getInputStream();String fileName = SD_FOLDER+ appName+".apk";File file = new File(fileName);// 目录不存在创建目录if (!file.getParentFile().exists())file.getParentFile().mkdirs();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 {throw new IOException("未发现有SD卡");}}/*** 安装apk*/private static void installApk(Context mContext, File file) {Uri fileUri = Uri.fromFile(file);Intent it = new Intent();it.setAction(Intent.ACTION_VIEW);it.setDataAndType(fileUri, "application/vnd.android.package-archive");it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 防止打不开应用mContext.startActivity(it);}/*** 获取应用程序版本(versionName)* * @return 当前应用的版本号*/private static double getLocalVersion(Context context) {PackageManager manager = context.getPackageManager();PackageInfo info = null;try {info = manager.getPackageInfo(context.getPackageName(), 0);} catch (NameNotFoundException e) {Log.e(TAG, "获取应用程序版本失败,原因:" + e.getMessage());return 0.0;}return Double.valueOf(info.versionName);}/** * byte(字节)根据长度转成kb(千字节)和mb(兆字节) * * @param bytes * @return */ public static String bytes2kb(long bytes) { BigDecimal filesize = new BigDecimal(bytes); BigDecimal megabyte = new BigDecimal(1024 * 1024); float returnValue = filesize.divide(megabyte, 2, BigDecimal.ROUND_UP) .floatValue(); if (returnValue > 1) return (returnValue + "MB"); BigDecimal kilobyte = new BigDecimal(1024); returnValue = filesize.divide(kilobyte, 2, BigDecimal.ROUND_UP) .floatValue(); return (returnValue + "KB"); }
}
基本上具体的代码就写完了,但是说如果停止了下载管理程序
调用dm.enqueue(req);就会上面的错误,从而程序闪退.
所以在使用该组件的时候,需要判断该组件是否可用:
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.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {return false;}} catch (Exception e) {e.printStackTrace();return false;}return true;}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
可以通过如下代码进入 启用/禁用 下载管理 界面:
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);
- 1.
- 2.
- 3.
- 4.
总结
本文意在讲解app的更新逻辑以及不同的表现形式的处理附带的介绍了使用nodejs写一个简单的api接口,重点是如何使用DownloadManager来实现apk的下载更新安装,顺带讲一下retrofit+rxjava的使用以及如何监听app是否下载完成。
DownloadManager的使用概括:
构建下载请求:
new DownloadManager.Request(url)
- 1.
- 1
设置请求属性
request.setXXX()
- 1.
- 1
调用downloadmanager对象的enqueue方法进行下载,此方法返回一个编号用于标示此下载任务:
downManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);id= downManager.enqueue(request);
- 1.
- 2.
- 3.
- 1
- 2
- 3
DownManager会对所有的现在任务进行保存管理,那么我们如何获取这些信息呢?这个时候就要用到DownManager.Query对象,通过此对象,我们可以查询所有下载任务信息。
setFilterById(long… ids):根据任务编号查询下载任务信息
setFilterByStatus(int flags):根据下载状态查询下载任务
如果想取消下载,则可以调用remove方法完成,此方法可以将下载任务和已经下载的文件同时删除:
downManager.remove(id);
- 1.
- 1
好了具体的都讲的差不多了,本文以同步到我的 github
demo 传送门:AppUpdate.rar
后记
鉴于版本更新没有对6.0的权限和7.0的FileProvider做适配,导致6.0和7.0的安装失败或者7.0直接crash,本文将不再解释,自行处理这里提供一个已经适配的demo下载
安卓开发app版本更新相关推荐
- reac native 开发app版本更新
1.app开发不可缺少的功能:app升级更新: 安卓:应用商城鱼龙混杂,下载的方式也多种多样,硬伤就是app不会自动更新,即使更新了应用商城,所以发版前一定要做好app更新的功能,以防后期app更新, ...
- 原生安卓开发app的框架frida常用关键代码定位
点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 多情却似总无情,唯觉樽前笑不成. ...
- 安卓开发APP,我要ping通路由器
玉律传佳节,青阳伴此辰.--(唐)冷朝阳<立春> 今天是2月3日,二十四节气中的第一个节气--立春.天气开始转暖,万物开始复苏.在这个充满希望的日子里,祝愿新冠疫情能够早日散去,藁城能够早 ...
- 手把手教你使用更多的原生安卓开发app的框架frida API
点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 蜡烛有心还惜别,替人垂泪到天明. ...
- python能开发手机程序吗_python能否开发安卓应用app?当然可以,python助你轻松搞定...
python是非常简单方便的编程语言,你可以用python很简洁的实现很多功能,今天就来说说如何用python开发安卓应用app. app的开发有两种方式: 第一种,混合开发.主要是通过一个容器来进行 ...
- 安卓app开发工具_怎么开发app软件需要多少钱?主流app开发工具盘点
现在智能手机的快速普及让手机app在生活中越来越重要,很多企业及创业者也意识到了app的重要性,但是怎么开发app软件?有哪些主流app开发工具呢?这里就为大家分享一下如何快速开发app软件. 一.编 ...
- 安卓开发由一个APP拉起另一个APP的方法总结
安卓开发由一个APP拉起另一个APP的方法总结 最近公司在对接第三方应用的时候有两个需求:1.要由我们的客户端拉起第三方的客户端,并且传值.2.要让第三方客户端能够拉起我们的客户端,并可以根据传递过来 ...
- 【安卓开发系列 -- APP 】APP 性能优化 -- 崩溃分析
[安卓开发系列 -- APP ]APP 性能优化 -- 崩溃分析 [1]Native Crash 分析示例 [1.1]Linux 编译 breadpad 下载 breadpad 源码 git clon ...
- 安卓app开发工具_四川智慧社区安卓手机app开发多少钱
四川智慧社区安卓手机app开发多少钱 注册登录应用公园后,有两种APP制作模式: 1.主题模式: 应用公园平台提供了上百个配置好的APP模板,可以直接使用,把图片文字替换就可以直接使用.如下图所示: ...
最新文章
- 神经网络和深度学习简史(三)
- linux命令行ps1变量_Linux下SHELL的PS1变量简介
- android布局之线性布局
- oracle8 as sysdba,Oracle 8i 密码验证
- python画五角星_Python第25课:海龟绘图_自定义函数的应用
- 解密阿里线上问题诊断工具Arthas和jvm-sandbox
- Golang interface 全面介绍
- matlab fft能量守恒吗,功能关系 能量守恒定律
- java duration 设置值,Java中的Duration toHours()方法
- 思科交换机IOS备份和升级
- OpenFoam | 全面解析sprayFoam | 一、对象parcels属于哪个类
- QT多线程之:moveToThread
- js计算时间差,包括计算,天,时,分,秒
- VS2017 新建项目没有MFC项目选项
- 深度学习实现安全帽佩戴的检测
- 【机器人定位引导中的机器视觉技术】
- 后缀是lnk是什么文件_后缀是lnk文件怎么打开,lnk什么格式
- 天空为什么刚才灰得就像哭过呢??
- 怎样实现一个二维码同时支持微信支付和支付宝支付
- 通过Mapi写Outlook日历中的约会项目(Recurrence , remaind,重复)