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

   2016-09-29 0
核心提示:本文将讲解app的升级与更新。一般而言用户使用App的时候升级提醒有两种方式获得: 一种是通过应用市场 获取 一种是打开应用之后提醒用户更新升级而更新操作一般是在用户点击了升级按钮之后开始执行的,这里的升级操作也分为两种形式: 一般升级 强制升级app升

本文将讲解app的升级与更新。一般而言用户使用App的时候升级提醒有两种方式获得:

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

而更新操作一般是在用户点击了升级按钮之后开始执行的,这里的升级操作也分为两种形式:

  • 一般升级
  • 强制升级

app升级操作:

  • 应用市场的app升级

在App Store中升级需要为App Store上传新版App,我们在新版本完成之后都会上传到App Store中,在审核完成之后就相当于完成了这个应用市场的发布了,也就是发布上线了。这时候如果用户安装了这个应用市场,那么就能看到我们的App有新版本的升级提醒了。

  • 应用内升级

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

显然应用市场提醒的升级不是我们的重点,本篇主要是对于app升级的场景来进行不同角度的实现,便于以后开发过程中直接拿去用就ok了。

服务器端:

  • 服务端提供一个接口,或者网址,这里提供一个网址如下:
http://192.168.191.1:8081/update

一般作为一个安卓程序员要测试还得写一个服务端(醉了),这里我就使用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":"蛋疼的认识"}

然后我电脑上是装了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/update
 fs.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 () {//端口我这里写的是8081
 var host = server.address().address
 var port = server.address().port
 console.log("应用实例,访问地址为 http://%s:%s", host, port)

})

有webstrom的直接选中文件run就ok了,没有直接 node express_demo.js,可以直接浏览器打开: http://127.0.0.1:8081/update

  • 效果如下:

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

上图为打开浏览器后的显示结果。

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

上图为webstrom的终端显示结果。

客户端需要实现:

我们知道不同的需求有不同的操作方法和界面显示:

  1. 从是否为app内部下载还是通知栏更新:
  • app内下载更新

    这时我们必须等下载安装完全后才能进行操作,效果是这样的:

    ?

  • 通知栏下载更新

    这种情况是不在应用内更新,放在通知栏并不会影响当前app的使用,效果是这样的:

    ?

  1. app更新分3种:强制更新,推荐更新,无需更新

    • 强制更新

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

    • 推荐更新

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

    • 无需更新

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

      ?

具体思路:

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

准备bean

首先我们要去解析服务端给的json,那么我们就要来创建一个bean类了,这里是严格根据json文件的格式来的:

packagecom.losileeya.appupdate.bean;
/**
 * User: Losileeya (847457332@qq.com)
 * Date: 2016-09-27
 * Time: 11:20
 * 类描述:版本更新的实体与你服务器的字段相匹配
 * @version:
 */
publicclassUpdateAppInfo{
publicUpdateInfo data;// 信息
publicInteger error_code;// 错误代码
publicString error_msg;// 错误信息
publicstaticclassUpdateInfo{
// app名字
publicString appname;
//服务器版本
publicString serverVersion;
//服务器标志
publicString serverFlag;
//强制升级
publicString lastForce;
//app最新版本地址
publicString updateurl;
//升级信息
publicString upgradeinfo;
 get...
 set...
 }
 get...
 set...
}

网络接口的实现

这里使用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.retrofit:adapter-rxjava:2.0.0-beta2'// Retrofit的rx解析库
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'// Retrofit的gson库

接下来网络接口的定制:

publicinterfaceApiService{
//实际开发过程可能的接口方式
@GET("update")
Observable<UpdateAppInfo> getUpdateInfo(@Query("appname")String appname, @Query("serverVersion")String appVersion);
//以下方便版本更新接口测试
@GET("update")
Observable<UpdateAppInfo> getUpdateInfo();
}

通过工厂模式来创建ApiService :

publicclassServiceFactory{
privatestaticfinalString BASEURL="http://192.168.191.1:8081/";
publicstatic<T>TcreateServiceFrom(finalClass<T> serviceClass){
 Retrofit adapter = newRetrofit.Builder()
 .baseUrl(BASEURL)
 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx适配器
 .addConverterFactory(GsonConverterFactory.create()) // 添加Gson转换器
 .build();
returnadapter.create(serviceClass);
 }
}

版本检测接口的使用:

/**
 * 检查更新
 */
@SuppressWarnings("unused")
publicstaticvoidcheckUpdate(String appCode, String curVersion,finalCheckCallBack updateCallback){
 ApiService apiService= ServiceFactory.createServiceFrom(ApiService.class);
 apiService.getUpdateInfo()//测试使用
// .apiService.getUpdateInfo(appCode, curVersion)//开发过程中可能使用的
 .subscribeOn(Schedulers.newThread())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(newSubscriber<UpdateAppInfo>() {
@Override
publicvoidonCompleted(){
 }
@Override
publicvoidon
Error(Throwable e){
 }
@Override
publicvoidonNext(UpdateAppInfo updateAppInfo){
if(updateAppInfo.error_code ==0|| updateAppInfo.data ==null||
 updateAppInfo.data.updateurl == null) {
 updateCallback.on
Error(); // 失败
 } else{
 updateCallback.onSuccess(updateAppInfo);
 }
 }
 });
 }

以上就是版本更新接口的调用,具体的rxjava+retrofit请自行学习 你真的会用Retrofit2吗?Retrofit2完全教程

附上结果回调监听:

publicinterfaceCheckCallBack{//检测成功或者失败的相关接口
voidonSuccess(UpdateAppInfo updateInfo);
voidon
Error();
 }

具体使用接口的处理:

//网络检查版本是否需要更新
 CheckUpdateUtils.checkUpdate("apk","1.0.0",newCheckUpdateUtils.CheckCallBack() {
@Override
publicvoidonSuccess(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);
 }
 }
@Override
publicvoidon
Error(){
 noneUpdate(MainActivity.this);
 }
 });

实在不想写网络也好,直接使用假想数据做相关操作如下:

UpdateAppInfo.UpdateInfo info =newUpdateAppInfo.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());
 }

更新dialog的使用注意:

privatevoidforceUpdate(finalContext context,finalString appName,finalString downUrl,finalString updateinfo){
 mDialog = newAlertDialog.Builder(context);
 mDialog.setTitle(appName+"又更新咯!");
 mDialog.setMessage(updateinfo);
 mDialog.setPositiveButton("立即更新",newDialogInterface.OnClickListener() {
@Override
publicvoidonClick(DialogInterface dialog,intwhich){
if(!canDownloadState()) {
 showDownloadSetting();
return;
 }
// DownLoadApk.download(MainActivity.this,downUrl,updateinfo,appName);
 AppInnerDownLoder.downLoadApk(MainActivity.this,downUrl,appName);
 }
 }).setCancelable(false).create().show();
 }

上面以强制更新举个例子,因为AlertDialog在不同的版本下面表现的美观度不一致,所以我们需要

> importandroid.support.v7.app.AlertDialog;
>

然后显然是不能按返回键取消的,我们需要

> .setCancelable(false)
>

使用谷歌推荐的DownloadManager实现下载

Android自带的DownloadManager模块来下载,在api level 9之后,我们通过通知栏知道, 该模块属于系统自带, 它已经帮我们处理了下载失败、重新下载等功能。整个下载 过程全部交给系统负责,不需要我们过多的处理。

DownLoadManager.Query:主要用于查询下载信息。

DownLoadManager.Request:主要用于发起一个下载请求。

先看下简单的实现:

创建Request对象的代码如下:

DownloadManager.Request request = newDownloadManager.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");

取得系统服务后,调用downloadmanager对象的enqueue方法进行下载,此方法返回一个编号用于标示此下载任务:

downManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
id= downManager.enqueue(request);

这里我们可以看下request的一些属性:

addRequestHeader(String header,String value):添加网络下载请求的http头信息
allowScanningByMediaScanner():用于设置是否允许本MediaScanner扫描。
setAllowedNetworkTypes(intflags):设置用于下载时的网络类型,默认任何网络都可以下载,提供的网络常量有:NETWORK_BLUETOOTH、NETWORK_MOBILE、NETWORK_WIFI。
setAllowedOverRoaming(Boolean allowed):用于设置漫游状态下是否可以下载
setNotificationVisibility(intvisibility):用于设置下载时时候在状态栏显示通知信息
setTitle(CharSequence):设置Notification的title信息
setDescription(CharSequence):设置Notification的message信息
setDestinationInExternalFilesDir、setDestinationInExternalPublicDir、 setDestinationUri等方法用于设置下载文件的存放路径,注意如果将下载文件存放在默认路径,那么在空间不足的情况下系统会将文件删除,所 以使用上述方法设置文件存放目录是十分必要的。

具体实现思路:

  1. 我们通过downloaderManager来下载apk,并且本地保存downManager.enqueue(request)返回的id值,并且通过这个id获取apk的下载文件路径和下载的状态,并且通过状态来更新通知栏的显示。

  2. 第一次下载成功,弹出安装界面

    如果用户没有点击安装,而是按了返回键,在某个时候,又再次使用了我们的APP

    如果下载成功,则判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本,如果都满足则直接启动安装程序。

具体代码实现:

文件下载管理的实现,包括创建request和加入队列下载,通过返回的id来获取下载路径和下载状态。

publicclassFileDownloadManager{
privateDownloadManager downloadManager;
privateContext context;
privatestaticFileDownloadManager instance;

privateFileDownloadManager(Context context){
 downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
this.context = context.getApplicationContext();
 }

publicstaticFileDownloadManagergetInstance(Context context){
if(instance ==null) {
 instance = newFileDownloadManager(context);
 }
returninstance;
 }
/**
 * @paramuri
 * @paramtitle
 * @paramdescription
 * @returndownload id
 */
publiclongstartDownload(String uri, String title, String description,String appName){
 DownloadManager.Request req = newDownloadManager.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.apk
 req.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");
returndownloadManager.enqueue(req);//异步
//dm.openDownloadedFile()
 }
/**
 * 获取文件保存的路径
 *
 * @paramdownloadId an ID for the download, unique across the system.
 * This ID is used to make future calls related to this download.
 * @returnfile path
 * @seeFileDownloadManager#getDownloadUri(long)
 */
publicStringgetDownloadPath(longdownloadId){
 DownloadManager.Query query = newDownloadManager.Query().setFilterById(downloadId);
 Cursor c = downloadManager.query(query);
if(c !=null) {
try{
if(c.moveToFirst()) {
returnc.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));
 }
 } finally{
 c.close();
 }
 }
returnnull;
 }

/**
 * 获取保存文件的地址
 *
 * @paramdownloadId an ID for the download, unique across the system.
 * This ID is used to make future calls related to this download.
 * @seeFileDownloadManager#getDownloadPath(long)
 */
publicUrigetDownloadUri(longdownloadId){
returndownloadManager.getUriForDownloadedFile(downloadId);
 }

publicDownloadManagergetDownloadManager(){
returndownloadManager;
 }

/**
 * 获取下载状态
 *
 * @paramdownloadId an ID for the download, unique across the system.
 * This ID is used to make future calls related to this download.
 * @returnint
 * @seeDownloadManager#STATUS_PENDING
 * @seeDownloadManager#STATUS_PAUSED
 * @seeDownloadManager#STATUS_RUNNING
 * @seeDownloadManager#STATUS_SUCCESSFUL
 * @seeDownloadManager#STATUS_FAILED
 */
publicintgetDownloadStatus(longdownloadId){
 DownloadManager.Query query = newDownloadManager.Query().setFilterById(downloadId);
 Cursor c = downloadManager.query(query);
if(c !=null) {
try{
if(c.moveToFirst()) {
returnc.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
 }
 } finally{
 c.close();
 }
 }
return-1;
 }
}

app的检测安装的实现:

publicclassDownLoadApk{
publicstaticfinalString TAG = DownLoadApk.class.getSimpleName();

publicstaticvoiddownload(Context context, String url, String title,finalString appName){
// 获取存储ID
 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
longdownloadId =sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L);
if(downloadId != -1L) {
 FileDownloadManager fdm = FileDownloadManager.getInstance(context);
intstatus = 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);
 } elseif(status == DownloadManager.STATUS_FAILED) {
 start(context, url, title,appName);
 } else{
 Log.d(TAG, "apk is already downloading");
 }
 } else{
 start(context, url, title,appName);
 }
 }

privatestaticvoidstart(Context context, String url, String title,String appName){
longid = 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);
 }

publicstaticvoidstartInstall(Context context, Uri uri){
 Intent install = newIntent(Intent.ACTION_VIEW);
 install.setDataAndType(uri, "application/vnd.android.package-archive");
 install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 context.startActivity(install);
 }


/**
 * 获取apk程序信息[packageName,versionName...]
 *
 * @paramcontext Context
 * @parampath apk path
 */
privatestaticPackageInfogetApkInfo(Context context, String path){
 PackageManager pm = context.getPackageManager();
 PackageInfo info = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
if(info !=null) {
returninfo;
 }
returnnull;
 }


/**
 * 下载的apk和当前程序版本比较
 *
 * @paramapkInfo apk file's packageInfo
 * @paramcontext Context
 * @return如果当前应用版本小于apk的版本则返回true
 */
privatestaticbooleancompare(PackageInfo apkInfo, Context context){
if(apkInfo ==null) {
returnfalse;
 }
 String localPackage = context.getPackageName();
if(apkInfo.packageName.equals(localPackage)) {
try{
 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(localPackage, 0);
if(apkInfo.versionCode > packageInfo.versionCode) {
returntrue;
 }
 } catch(PackageManager.NameNotFoundException e) {
 e.printStackTrace();
 }
 }
returnfalse;
 }
}

上面的代码可知:我们通过获取当前app的信息来比较是否需要下载和是否立即安装。第一次下载把downloadId保存到本地,用户下次进来的时候,取出保存的downloadId,然后通过downloadId来获取下载的状态信息。如果下载失败,则重新下载并且把downloadId存起来。如果下载成功,则 判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本 ,如果都满足则直接启动安装程序。

监听app是否安装完成

publicclassApkInstallReceiverextendsBroadcastReceiver{
@Override
publicvoidonReceive(Context context, Intent intent){
if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){
longdownloadApkId =intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
 installApk(context, downloadApkId);
 }
 }

/**
 * 安装apk
 */
privatevoidinstallApk(Context context,longdownloadApkId){
// 获取存储ID
 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
longdownId =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= newIntent(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();
 }
 }
 }
}

DownloadManager下载完成后会发出一个广播 android.intent.action.DOWNLOAD_COMPLETE 新建一个广播接收者即可:

清单配置:

先添加网络下载的权限:

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

再添加静态广播:

<receiver android:name=".ApkInstallReceiver">
 <intent-filter>
 <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
 </intent-filter>
 </receiver>

使用HttpUrlConnection下载

这种情况下载的话我们就不需要考虑id的问题,因为是直接在项目中下载,所以我们就是一个网络下载的过程,并且使用ProgressDialog显示下载信息及进度更新就ok了。

publicclassAppInnerDownLoder{
publicfinalstaticString SD_FOLDER = Environment.getExternalStorageDirectory()+"/VersionChecker/";
privatestaticfinalString TAG = AppInnerDownLoder.class.getSimpleName();

/**
 * 从服务器中下载APK
 */
@SuppressWarnings("unused")
publicstaticvoiddownLoadApk(finalContext mContext,finalString downURL,finalString appName ){

finalProgressDialog pd;// 进度条对话框
 pd = newProgressDialog(mContext);
 pd.setCancelable(false);// 必须一直下载完,不可取消
 pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
 pd.setMessage("正在下载安装包,请稍后");
 pd.setTitle("版本升级");
 pd.show();
newThread() {
@Override
publicvoidrun(){
try{
 File file = downloadFile(downURL,appName, pd);
 sleep(3000);
 installApk(mContext, file);
// 结束掉进度条对话框
 pd.dismiss();
 } catch(Exception e) {
 pd.dismiss();

 }
 }
 }.start();
 }

/**
 * 从服务器下载最新更新文件
 * 
 * @parampath
 * 下载路径
 * @parampd
 * 进度条
 * @return
 * @throwsException
 */
privatestaticFiledownloadFile(String path,String appName ,ProgressDialog pd)throwsException{
// 如果相等的话表示当前的sdcard挂载在手机上并且是可用的
if(Environment.MEDIA_MOUNTED.equals(Environment
 .getExternalStorageState())) {
 URL url = newURL(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 = newFile(fileName);
// 目录不存在创建目录
if(!file.getParentFile().exists())
 file.getParentFile().mkdirs();
 FileOutputStream fos = newFileOutputStream(file);
 BufferedInputStream bis = newBufferedInputStream(is);
byte[] buffer =newbyte[1024];
intlen;
inttotal =0;
while((len = bis.read(buffer)) != -1) {
 fos.write(buffer, 0, len);
 total += len;
// 获取当前下载量
 pd.setProgress(total);
 }
 fos.close();
 bis.close();
 is.close();
returnfile;
 } else{
thrownewIOException("未发现有SD卡");
 }
 }

/**
 * 安装apk
 */
privatestaticvoidinstallApk(Context mContext, File file){
 Uri fileUri = Uri.fromFile(file);
 Intent it = newIntent();
 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当前应用的版本号
 */

privatestaticdoublegetLocalVersion(Context context){
 PackageManager manager = context.getPackageManager();
 PackageInfo info = null;
try{
 info = manager.getPackageInfo(context.getPackageName(), 0);
 } catch(NameNotFoundException e) {
 Log.e(TAG, "获取应用程序版本失败,原因:"+ e.getMessage());
return0.0;
 }

returnDouble.valueOf(info.versionName);
 }
/** 
 * byte(字节)根据长度转成kb(千字节)和mb(兆字节) 
 * 
 * @parambytes
 * @return
 */ 
publicstaticStringbytes2kb(longbytes){
 BigDecimal filesize = newBigDecimal(bytes);
 BigDecimal megabyte = newBigDecimal(1024*1024);
floatreturnValue = filesize.divide(megabyte,2, BigDecimal.ROUND_UP)
 .floatValue(); 
if(returnValue >1)
return(returnValue +"MB");
 BigDecimal kilobyte = newBigDecimal(1024);
 returnValue = filesize.divide(kilobyte, 2, BigDecimal.ROUND_UP)
 .floatValue(); 
return(returnValue +"KB");
 } 
}

基本上具体的代码就写完了,但是说如果停止了 下载管理程序 调用dm.enqueue(req);就会上面的错误,从而程序闪退.

所以在使用该组件的时候,需要判断该组件是否可用:

privatebooleancanDownloadState(){
try{
intstate =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) {
returnfalse;
 }

 } catch(Exception e) {
 e.printStackTrace();
returnfalse;
 }
returntrue;
}

可以通过如下代码进入 启用/禁用 下载管理 界面:

String packageName = "com.android.providers.downloads";
 Intent intent = newIntent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
 intent.setData(Uri.parse("package:"+ packageName));
 startActivity(intent);

总结

本文意在讲解app的更新逻辑以及不同的表现形式的处理附带的介绍了使用nodejs写一个简单的api接口,重点是如何使用DownloadManager来实现apk的下载更新安装,顺带讲一下retrofit+rxjava的使用以及如何监听app是否下载完成。

DownloadManager的使用概括:

  1. 构建下载请求:

    > newDownloadManager.Request(url)
    >
    

>

  1. 设置请求属性

    > request.setXXX()
    >
    

>

  1. 调用downloadmanager对象的enqueue方法进行下载,此方法返回一个编号用于标示此下载任务:

    > downManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
    >
    > id= downManager.enqueue(request);
    >
    

>

  1. DownManager会对所有的现在任务进行保存管理,那么我们如何获取这些信息呢?这个时候就要用到DownManager.Query对象,通过此对象,我们可以查询所有下载任务信息。

    setFilterById(long… ids):根据任务编号查询下载任务信息

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

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

    > downManager.remove(id);
    >
    

好了具体的都讲的差不多了,本文以同步到我的csdn: 安卓开发实战之app之版本更新升级(DownloadManager和http下载)完整实现

demo 传送门: AppUpdate.rar

未经许可不得转载,转载请注明zilianliuxue的blog,本人保留所有版权。

 
标签: 安卓开发 HTTP
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 安卓中通知功能的具体实现
    安卓中通知功能的具体实现
    通知[Notification]是Android中比较有特色的功能,当某个应用程序希望给用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知实现。使用通知的步骤1、需要一个NotificationManager来获得NotificationManager manager = (NotificationManager
    02-05 安卓开发
  • Android view系统分析-setContentView
    Android view系统分析-setContentView
    第一天上班,列了一下今年要学习的东西。主要就是深入学习Android相关的系统源代码,夯实基础。对于学习Android系统源代码,也没什么大概,就从我们平常使用最基础的东西学起,也就是从view这个切入点开始学习Android的源码,在没分析源码之前,我们有的时候
    02-05 安卓开发
  • 如何进行网络视频截图/获取视频的缩略图
    如何进行网络视频截图/获取视频的缩略图
    小编导读:获取视频的缩略图,截图正在播放的视频某一帧,是在音视频开发中,常遇到的问题。本文是主要用于点播中截图视频,同时还可以获取点播视频的缩略图进行显示,留下一个问题,如下图所示, 如果要获取直播中节目视频缩略图,该怎么做呢?(ps:直播是直
  • Android NDK 层发起 HTTP 请求的问题及解决
    Android NDK 层发起 HTTP 请求的问题及解决
    前言新的一年,大家新年快乐~~鸡年大吉!本次给大家带来何老师的最新文章~虽然何老师还在过节,但依然放心不下广大开发者,在此佳节还未结束之际,给大家带来最新的技术分享~ 事件的起因不说了,总之是需要实现一个 NDK 层的网络请求。为了多端适用,还是选择
  • Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突
    Android插件化(六): OpenAtlasの改写aapt以防
    引言Android应用程序的编译中,负责资源打包的是aapt,如果不对打包后的资源ID进行控制,就会导致插件中的资源ID冲突。所以,我们需要改写aapt的源码,以达到通过某种方式传递资源ID的Package ID,通过aapt打包时获取到这个Package ID并且应用才插件资源的命名
    02-05 安卓开发
  • Android架构(一)MVP架构在Android中的实践
    Android架构(一)MVP架构在Android中的实践
    为什么要重视程序的架构设计 对程序进行架构设计的原因,归根结底是为了 提高生产力 。通过设计是程序模块化,做到模块内部的 高聚合 和模块之间的 低耦合 (如依赖注入就是低耦合的集中体现)。 这样做的好处是使得程序开发过程中,开发人员主需要专注于一点,
    02-05 安卓开发
  • 安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件 作者: 飞龙 这个教程中我们要分析一个锁机软件。像这种软件都比较简单,完全可以顺着入口看下去,但我这里还是用关键点来定位。首先这个软件的截图是这样,进入这个界面之后,除非退出模拟器,否则没办法回到桌面。上面那
    02-05 安卓开发
  • Android插件化(二):OpenAtlas插件安装过程分析
    Android插件化(二):OpenAtlas插件安装过程分析
    在前一篇博客 Android插件化(一):OpenAtlas架构以及实现原理概要 中,我们对应Android插件化存在的问题,实现原理,以及目前的实现方案进行了简单的叙述。从这篇开始,我们要深入到OpenAtlas的源码中进行插件安装过程的分析。 插件的安装分为3种:宿主启动时立
    02-05 安卓开发
  • [译] Android API 指南
    [译] Android API 指南
    众所周知,Android开发者有中文网站了,API 指南一眼看去最左侧的菜单都是中文,然而点进去内容还是很多是英文,并没有全部翻译,我这里整理了API 指南的目录,便于查看,如果之前还没有通读,现在可以好好看一遍。注意,如果标题带有英文,说明官方还没有翻
  • 使用FileProvider解决file:// URI引起的FileUriExposedException
    使用FileProvider解决file:// URI引起的FileUri
    问题以下是一段简单的代码,它调用系统的相机app来拍摄照片:void takePhoto(String cameraPhotoPath) {File cameraPhoto = new File(cameraPhotoPath);Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);takePhotoIntent.putExtra(Medi
    02-05 安卓开发
点击排行