<p>在Android App都会有版本更新的功能,以前我们公司是用友盟SDK更新功能,自己服务器没有这样的功能。版本检测、Apk下载都是使用友盟。最近看到友盟的版本更新SDK文档:十月份更新功能将会停止服务器,建议开发者迁移到自己的服务器中。</p><p>本文章的主要逻辑:</p><p>第一次下载成功,弹出安装界面;</p><p>如果用户没有点击安装,而是按了返回键,在某个时候,又再次使用了我们的APP</p><p>如果下载成功,则判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本,如果都满足则直接启动安装程序。</p><p>下载功能,Google官方推荐使用 DownloadManager 服务。</p><p>1. 如何使用DownloadManager</p><pre class="brush:java;toolbar:false">FileDownloadManager.java
publiclongstartDownload(Stringuri,Stringtitle,Stringdescription){
DownloadManager.Requestreq=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,"update.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");
//加入下载队列
returndm.enqueue(req);
//longdownloadId=dm.enqueue(req);
//Log.d("DownloadManager",downloadId+"");
//dm.openDownloadedFile()
}</pre><p>Android自带的DownloadManager模块来下载, 我们通过通知栏知道, 该模块属于系统自带, 它已经帮我们处理了下载失败、重新下载等功能。</p><p>2. 如何检测下载完成,然后启动安装界面</p><p>DownloadManager下载完成后会发出一个广播 android.intent.action.DOWNLOAD_COMPLETE 新建一个广播接收者即可:</p><pre class="brush:java;toolbar:false">publicclassApkInstallReceiverextendsBroadcastReceiver{
@Override
publicvoidonReceive(Contextcontext,Intentintent){
if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){
//@TODOSOMETHING
}
}
}</pre><p>3. 完善App更新逻辑</p><p>1> 第一次下载成功,弹出安装界面</p><p>这个逻辑在ApkInstallReceiver里做即可:</p><pre class="brush:java;toolbar:false">publicclassApkInstallReceiverextendsBroadcastReceiver{
@Override
publicvoidonReceive(Contextcontext,Intentintent){
if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){
installApk(context,downloadApkId);
}
}
privatestaticvoidinstallApk(Contextcontext,longdownloadApkId){
DownloadManagerdManager=(DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
Intentinstall=newIntent(Intent.ACTION_VIEW);
UridownloadFileUri=dManager.getUriForDownloadedFile(downloadApkId);
if(downloadFileUri!=null){
Log.d("DownloadManager",downloadFileUri.toString());
install.setDataAndType(downloadFileUri,"application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}else{
Log.e("DownloadManager","downloaderror");
}
}
}</pre><p>2> 如果用户没有点击安装,而是按了返回键</p><p>这个时候用户可能没有安装,而是退出了安装界面,用户退出了我们的APP,在某个时候,又再次使用了我们的APP,这个时候我们不应该去下载新版本,而是使用已经下载已经存在本地的APK。</p><p>第一次下载的 downloadManager.enqueue(req)会返回一个downloadId,把downloadId保存到本地,用户下次进来的时候,取出保存的downloadId,然后通过downloadId来获取下载的状态信息。</p><pre class="brush:java;toolbar:false">ApkUpdateUtils.java
publicstaticvoiddownload(Contextcontext,Stringurl,Stringtitle){
longdownloadId=SpUtils.getInstance(context).getLong(KEY_DOWNLOAD_ID,-1L);
if(downloadId!=-1L){
FileDownloadManagerfdm=FileDownloadManager.getInstance(context);
intstatus=fdm.getDownloadStatus(downloadId);
if(status==DownloadManager.STATUS_SUCCESSFUL){
//启动更新界面
Uriuri=fdm.getDownloadUri(downloadId);
if(uri!=null){
if(compare(getApkInfo(context,uri.getPath()),context)){
startInstall(context,uri);
return;
}else{
fdm.getDm().remove(downloadId);
}
}
start(context,url,title);
}elseif(status==DownloadManager.STATUS_FAILED){
start(context,url,title);
}else{
if(Config.DEV_MODE){
Log.d(TAG,"apkisalreadydownloading");
}
}
}else{
start(context,url,title);
}
}</pre><p>获取APK包名、版本号</p><pre class="brush:java;toolbar:false">/**
*获取apk程序信息[packageName,versionName...]
*
*@paramcontextContext
*@parampathapkpath
*/
privatestaticPackageInfogetApkInfo(Contextcontext,Stringpath){
if(Config.DEV_MODE){
Log.d(TAG,"apkdownloadpath:"+path);
}
PackageManagerpm=context.getPackageManager();
PackageInfoinfo=pm.getPackageArchiveInfo(path,PackageManager.GET_ACTIVITIES);
if(info!=null){
//StringpackageName=info.packageName;
//Stringversion=info.versionName;
//Log.d(TAG,"packageName:"+packageName+";version:"+version);
//StringappName=pm.getApplicationLabel(appInfo).toString();
//Drawableicon=pm.getApplicationIcon(appInfo);//得到图标信息
returninfo;
}
returnnull;
}</pre><p>获取下载完成的APK地址</p><pre class="brush:java;toolbar:false">/**
*获取保存文件的地址
*
*@paramdownloadIdanIDforthedownload,uniqueacrossthesystem.
*ThisIDisusedtomakefuturecallsrelatedtothisdownload.
*@seeFileDownloadManager#getDownloadPath(long)
*/
publicUrigetDownloadUri(longdownloadId){
returndm.getUriForDownloadedFile(downloadId);
}
publicDownloadManagergetDm(){
returndm;
}</pre><p>获取下载状态信息</p><pre class="brush:java;toolbar:false">publicintgetDownloadStatus(longdownloadId){
DownloadManager.Queryquery=newDownloadManager.Query().setFilterById(downloadId);
Cursorc=dm.query(query);
if(c!=null){
try{
if(c.moveToFirst()){
returnc.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
}
}finally{
c.close();
}
}
return-1;
}</pre><p>如果下载失败,则重新下载并且把downloadId存起来。</p><pre class="brush:java;toolbar:false">privatestaticvoidstart(Contextcontext,Stringurl,Stringtitle){
longid=FileDownloadManager.getInstance(context).startDownload(url,
title,"下载完成后点击打开");
SpUtils.getInstance(context).putLong(KEY_DOWNLOAD_ID,id);
if(Config.DEV_MODE){
Log.d(TAG,"apkstartdownload"+id);
}
}</pre><p>如果下载成功,则判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本,如果都满足则直接启动安装程序。</p><pre class="brush:java;toolbar:false">/**
*下载的apk和当前程序版本比较
*
*@paramapkInfoapkfile'spackageInfo
*@paramcontextContext
*@return如果当前应用版本小于apk的版本则返回true
*/
privatestaticbooleancompare(PackageInfoapkInfo,Contextcontext){
if(apkInfo==null){
returnfalse;
}
StringlocalPackage=context.getPackageName();
if(apkInfo.packageName.equals(localPackage)){
try{
PackageInfopackageInfo=context.getPackageManager().getPackageInfo(localPackage,0);
if(apkInfo.versionCode>packageInfo.versionCode){
returntrue;
}else{
if(Config.DEV_MODE){
Log.d(TAG,"apk'sversionCode<=app'sversionCode");
}
}
}catch(PackageManager.NameNotFoundExceptione){
e.printStackTrace();
}
}
if(Config.DEV_MODE){
Log.d(TAG,"apk'spackagenotmatchapp'spackage");
}
returnfalse;
}</pre><p>启动安装界面</p><pre class="brush:java;toolbar:false">publicstaticvoidstartInstall(Contextcontext,Uriuri){
Intentinstall=newIntent(Intent.ACTION_VIEW);
install.setDataAndType(uri,"application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}</pre><p>为了严谨起见,在ApkInstallReceiver里不仅要对downloadId判断,还应当把当前程序和本地apk包名和版本号对比。</p><p>如果用户禁用了下载服务[下载管理程序]</p><p>可以通过如下代码进入 启用/禁用 下载管理程序 界面:</p><pre class="brush:java;toolbar:false">StringpackageName="com.android.providers.downloads";
Intentintent=newIntent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:"+packageName));
startActivity(intent);</pre><p>我们先停止下载管理程序,然后点击demo里的Download 按钮报出如下错误:</p><pre class="brush:java;toolbar:false">Causedby:java.lang.IllegalArgumentException:UnknownURLcontent://downloads/my_downloads
atandroid.content.ContentResolver.insert(ContentResolver.java:1227)
atandroid.app.DownloadManager.enqueue(DownloadManager.java:946)
atcom.chiclam.download.FileDownloadManager.startDownload(FileDownloadManager.java:61)
atcom.chiclam.download.ApkUpdateUtils.start(ApkUpdateUtils.java:47)
atcom.chiclam.download.ApkUpdateUtils.download(ApkUpdateUtils.java:42)
atcom.chiclam.download.MainActivity.download(MainActivity.java:34)
atjava.lang.reflect.Method.invoke(NativeMethod)
atandroid.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
atandroid.view.View.performClick(View.java:5204)
atandroid.view.View$PerformClick.run(View.java:21153)
atandroid.os.Handler.handleCallback(Handler.java:739)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:148)
atandroid.app.ActivityThread.main(ActivityThread.java:5417)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)</pre><p>也就是说如果停止了下载管理程序 调用dm.enqueue(req);就会上面的错误,从而程序闪退.</p><p>所以在使用该组件的时候,需要判断该组件是否可用:</p><pre class="brush:xml;toolbar:false">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(Exceptione){
e.printStackTrace();
returnfalse;
}
returntrue;
}
---------------------</pre>