知识为进步之母,而进步又为富强之源泉。这篇文章主要讲述Downloadmanager实现app实现的升级下载使用相关的知识,希望能为你提供帮助。
【Downloadmanager实现app实现的升级下载使用】1、app升级下载现在不推荐使用downloadmanager下载:
原因有下面的几个方面:
(1)三星note系列部分手机需要手动打开这个权限才能用这个功能,而有些国产手机更加nb了直接个阉割了(downloadmanager),所以考虑到手机的适配性,最后自己编写app下载的后台代码
2、但是这里还是对downloadmanager下载进行一些详细的分析,很多下载的思路还是值得借鉴的
1、首先后台的apk的搭建
1、打开本地Tomcat服务器,放入一个Apk文件
文章图片
apk存放在webapps/root目录下
// 首先确保浏览器能够访问http://localhost:8080/123456.apk
下面我将内容大致分为以下几个部分:
(1)App版本检测
(2)Apk下载
(3)Apk更新安装
(4)对以上功能进行封装
基于以上4部分,我们逐一展开。
1.App版本检测:
要实现App的更新下载,我们上面介绍了,前提是服务器要保存一个App的版本号(通常的方式是保存versionCode,当然你要对比versionName也没关系)。当用户去手动检测版本,或者进入首页自动检测时,第一步是需要请求服务器的版本号,拿到版本号之后与当前App版本号(当前版本号可通过PackageInfo获取)进行对比。服务器返回的版本号大于当前App版本号,证明App已经有更新,那么进入第2步。
2.Apk下载
Apk文件是保存在服务器的。我们可以通过Http流将其下载到本地手机,然后更新安装。Android中下载的方式很多种:HttpUrlConnection,Retrofit,okHttp,以及Android原生的下载工具类DownLoadManager 等等。我们采用的方式是Google推荐的下载工具类DownLoadManager。关于DownLoadManager的使用其实很简单,简单概括如下:
(1)通过getSystemService获取DownLoadManager。
(2)初始化DownLoadManager的Request,构建下载请求。
(3)调用DownLoadManager的enqueue异步发起请求,该方法返回值为标识当前下载任务的id,即downloadId。
(4)当下载完成后,系统会发出条件为android.intent.action.DOWNLOAD_COMPLETE的广播,我们可以自定义广播接受器,然后在onReceive中处理下载完成的逻辑即可。
详细使用方式大家可以参考网上的教程,此处就不再赘述。
上面通过下载啰嗦了一堆。此时我们要想一个问题:当我们下载完成后,并没有安装。当用户再次进入App时该如何操作?
有朋友会说,那就再去下载一次,然后继续执行更新安装呀!哈哈,这种方式是没有错误的,但是如果用户恶意行为,每次下载完成都不安装,那我们岂不是每次都要去下载100次,1000次。。(然后手机boom!!!)这种方式肯定是不能采用的。那么我们该如何解决呢?
很简单,当我们在下载之前,先去指定的文件夹下查看有木有已经下载好的Apk,并且该Apk的版本是高于本App的版本,此时我们就去执行安装操作。如果上面条件不成立,此时再去执行下载操作。
3.Apk更新安装
相信大家对于如何安装一个Apk都比较熟悉吧,原理也是比较简单的。
(1)通过downloadId获取下载的Uri。
(2)将Uri设置到Itent的setDataAndType作为启动条件。
(3)调用startActivity启动对应Intent即可。
以上3步,即可完成App的更新功能。
整体的流程很清晰:
版本检测 → Apk下载 (检查是否存在未安装的Apk) → Apk安装 → 完成更新
下面,通过代码来具体分析整个流程:
关于App版本检测其实就是一个Http请求,不再多说。我们从Apk下载开始:
上面我们提到,在下载之前需要去检测是否存在已经下载的Apk。通过什么获取呢?没错,肯定是downloadId了。
1> 如果存在downloadId,那么我们通过downloadId获取当前下载的状态status。status分为成功,失败两种状态。
(1)当status为成功状态时,即已经下载完成,我们就通过downloadId获取下载文件的Uri。然后可以通过Uri获取PackageInfo,与当前App进行包名和版本号的对比,当包名相同,并且当前版本号是小于下载的Apk版本号两个条件同时成立时,直接执行安装操作。否则,执行remove,通过downloadId删除下载任务以及文件,继续执行下载。
(2)当status为失败状态时,即下载未完成,我们就直接执行重新下载即可。
2> 如果不存在downloadId,即没有下载过Apk,执行下载即可。
文章图片
下载完成后,系统会发出广播,在广播中,我们对比downloadId是否相同,相同情况下,直接通过downloadId获取Uri,然后跳转到安装界面,提示用户安装即可:
文章图片
所以,别忘了在下载之前要先将该大喇叭(广播接受器)注册。
文章图片
最后,当我们安装完成后,再次进入App,就将其已下载的Apk文件进行删除(将该方法放在onCreate生命周期中即可):
文章图片
上面通过downloadApk获取下载文件的地址。downloadApk地址是在下载完成后广播接收器中保存的。
通过上面的步骤,我们就完成了App更新下载安装的全部工作。相信大家也有了更深的认识和理解。
下面博客:
http://chuansong.me/n/1090429551927
http://chuansong.me/n/1090429551927
也是对downloadManger做了详细的解释
接下来我们看下代码
程序的框架如下所示:
文章图片
package co.huiqu.webapp; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import co.huiqu.webapp.download.DownLoadUtils; import co.huiqu.webapp.download.DownloadApk; public class MainActivity extends AppCompatActivity {@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); //1.注册下载广播接收器 DownloadApk.registerBroadcast(this); //2.apk按照成功之后再次进场到app删除已存在的Apk DownloadApk.removeFile/**/(this); fab.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View view) { /*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); *///3.如果手机已经启动下载程序,执行downloadApk。否则跳转到设置界面 if (DownLoadUtils.getInstance(getApplicationContext()).canDownload()) { //DownloadApk.downloadApk(getApplicationContext(), "http://www.huiqu.co/public/download/apk/huiqu.apk", "Hobbees更新", "Hobbees"); DownloadApk.downloadApk(getApplicationContext(), "http://10.12.8.13:8080/123456.apk", "酷狗更新", "Hobbees下载"); } else { DownLoadUtils.getInstance(getApplicationContext()).skipToDownloadManager(); } } }); }@Override protected void onDestroy() {//4.反注册广播接收器 DownloadApk.unregisterBroadcast(this); super.onDestroy(); } }
sharePrefer的工具类代码:
package co.huiqu.webapp.config; import java.util.Set; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; /** * * 保存系统信息 * @author song * @date 2015年11月4日 */ public class SystemParams {private static SystemParams instance; private static SharedPreferences sharedPrederences = null; private SystemParams() { }//在Application初始化 public static void init(Context context) { sharedPrederences = context.getSharedPreferences("hobbees", Context.MODE_PRIVATE); } public static SystemParams getInstance() {if(instance == null) { synchronized (SystemParams.class) { if(instance == null) { instance = new SystemParams(); } } } return instance; }/**get**/ public int getInt(String key){ return sharedPrederences.getInt(key, 0); }public int getInt(String key,int defValue){ return sharedPrederences.getInt(key, defValue); }public float getFloat(String key){ return sharedPrederences.getFloat(key, 0); }public float getFloat(String key,float defValue) { return sharedPrederences.getFloat(key, defValue); }public long getLong(String key){ return sharedPrederences.getLong(key, 0); }public long getLong(String key,long defValue) { return sharedPrederences.getLong(key, defValue); }public String getString(String key){ return sharedPrederences.getString(key, null); }public String getString(String key,String defValue) { return sharedPrederences.getString(key, defValue); }public boolean getBoolean(String key){ return sharedPrederences.getBoolean(key, false); }public boolean getBoolean(String key,boolean defValue) { return sharedPrederences.getBoolean(key, defValue); }/**set**/ public void setInt(String key,int value) { Editor editor = sharedPrederences.edit(); editor.putInt(key, value); editor.commit(); }public void setFloat(String key,float value) { Editor editor = sharedPrederences.edit(); editor.putFloat(key, value); editor.commit(); }public void setLong(String key,long value) { Editor editor = sharedPrederences.edit(); editor.putLong(key, value); editor.commit(); }public void setString(String key,String value) { Editor editor = sharedPrederences.edit(); editor.putString(key, value); editor.commit(); }public void setBoolean(String key,boolean value) { Editor editor = sharedPrederences.edit(); editor.putBoolean(key, value); editor.commit(); }public void setSetString(String key,Set< String> values) { Editor editor = sharedPrederences.edit(); editor.putStringSet(key, values); editor.commit(); }public void remove(String key) { Editor editor = sharedPrederences.edit(); editor.remove(key); editor.commit(); }public void clear() { Editor editor = sharedPrederences.edit(); editor.clear().commit(); } }
package co.huiqu.webapp.download; import android.app.DownloadManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.widget.Toast; import co.huiqu.webapp.config.SystemParams; /** * Created by Song on 2016/11/2. */ public class ApkInstallReceiver extends BroadcastReceiver {@Override public void onReceive(Context context, Intent intent) { long downloadApkId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { Toast.makeText(context,"收到apk下载完成的广播",Toast.LENGTH_LONG).show(); int status =DownLoadUtils.getInstance(context).checkStatus(downloadApkId); switch (status) { //下载暂停 case DownloadManager.STATUS_PAUSED: //获得下载暂停的原因等信息 Toast.makeText(context,"暂停下载"+DownLoadUtils.getInstance(context).getPausedReason(downloadApkId),Toast.LENGTH_LONG).show(); break; //下载延迟 case DownloadManager.STATUS_PENDING: //获得下载延迟的原因 break; //正在下载 case DownloadManager.STATUS_RUNNING: break; //下载完成 case DownloadManager.STATUS_SUCCESSFUL: //下载完成安装APK installApk(context, downloadApkId); break; //下载失败 case DownloadManager.STATUS_FAILED: // 获得下载失败的原因 Toast.makeText(context,"暂停下载"+DownLoadUtils.getInstance(context).getFailedReason(downloadApkId),Toast.LENGTH_LONG).show(); break; }}else if(DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(intent.getAction())){//点击通知栏取消下载 Toast.makeText(context,"通知栏被点击",Toast.LENGTH_LONG).show(); } }/** * 安装apk */ private void installApk(Context context,long downloadId) {long downId = SystemParams.getInstance().getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1L); if(downloadId == downId) { DownloadManager downManager= (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); Uri downloadUri = downManager.getUriForDownloadedFile(downloadId); SystemParams.getInstance().setString("downloadApk",downloadUri.getPath()); if (downloadUri != null) { Intent install= new Intent(Intent.ACTION_VIEW); install.setDataAndType(downloadUri, "application/vnd.android.package-archive"); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(install); } else { Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show(); } } }}
package co.huiqu.webapp.download; import android.app.DownloadManager; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Environment; import android.util.Log; import android.widget.Toast; import java.io.File; import co.huiqu.webapp.config.SystemParams; /** * Apk下载 * Created by Song on 2016/11/2. */ public class DownloadApk {private static ApkInstallReceiver apkInstallReceiver; /** * 下载APK文件 * @param context * @param url * @param title * @param appName */ public static void downloadApk(Context context, String url, String title,final String appName) {//获取存储的下载ID long downloadId = SystemParams.getInstance().getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L); if(downloadId != -1) { //存在downloadId DownLoadUtils downLoadUtils = DownLoadUtils.getInstance(context); //获取当前状态 int status = downLoadUtils.getDownloadStatus(downloadId); if(DownloadManager.STATUS_SUCCESSFUL == status) { //状态为下载成功 //获取下载路径URI Uri downloadUri = downLoadUtils.getDownloadUri(downloadId); if(null != downloadUri) { //存在下载的APK,如果两个APK相同,启动更新界面。否之则删除,重新下载。 if(compare(getApkInfo(context,downloadUri.getPath()),context)) { startInstall(context, downloadUri); return; } else { //删除下载任务以及文件 downLoadUtils.getDownloadManager().remove(downloadId); } } start(context, url, title,appName); } else if(DownloadManager.STATUS_FAILED == status) { //下载失败,重新下载 start(context, url, title,appName); }else { Log.d(context.getPackageName(), "apk is already downloading"); } } else { //不存在downloadId,没有下载过APK start(context, url, title,appName); } }/** * 开始下载 * @param context * @param url * @param title * @param appName */ private static void start(Context context, String url, String title,String appName) {if(hasSDKCard()) { long id = DownLoadUtils.getInstance(context).download(url, title, "下载完成后点击打开", appName); SystemParams.getInstance().setLong(DownloadManager.EXTRA_DOWNLOAD_ID,id); } else { Toast.makeText(context,"手机未安装SD卡,下载失败",Toast.LENGTH_LONG).show(); } }public static void registerBroadcast(Context context) { apkInstallReceiver = new ApkInstallReceiver(); context.registerReceiver(apkInstallReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); context.registerReceiver(apkInstallReceiver, new IntentFilter(DownloadManager.ACTION_NOTIFICATION_CLICKED)); }public static void unregisterBroadcast(Context context) { if(null != apkInstallReceiver) { context.unregisterReceiver(apkInstallReceiver); } }/** * 跳转到安装界面 * @param context * @param uri */ private 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程序信息 * @param context * @param path * @return */ private static PackageInfo getApkInfo(Context context, String path) {PackageManager pm = context.getPackageManager(); PackageInfo pi = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES); if(null != pi) { return pi; } return null; }/** * 比较两个APK的信息 * @param apkInfo * @param context * @return */ private static boolean compare(PackageInfo apkInfo,Context context) {if(null == apkInfo) { return false; } String localPackageName = context.getPackageName(); if(localPackageName.equals(apkInfo.packageName)) { try { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(localPackageName, 0); //比较当前APK和下载的APK版本号 if (apkInfo.versionCode > packageInfo.versionCode) { //如果下载的APK版本号大于当前安装的APK版本号,返回true return true; } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } return false; }/** * 是否存在SD卡 */ private static boolean hasSDKCard() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); }/** * 删除已下载的文件 */ public static void removeFile(Context context) { String filePath = SystemParams.getInstance().getString("downloadApk",null); if(null != filePath) { File downloadFile = new File(filePath); if(null != downloadFile & & downloadFile.exists()) { //删除之前先判断用户是否已经安装了,安装了才删除。 if(!compare(getApkInfo(context,filePath),context)) { downloadFile.delete(); Log.e("----", "已删除"); } } } } }
package co.huiqu.webapp.download; import android.app.DownloadManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Environment; import android.util.Log; import android.widget.Toast; import java.io.File; import co.huiqu.webapp.config.SystemParams; /** * 封装 DownLoadManager 下载 * Created by Song on 2016/11/2. */ public class DownLoadUtils {private Context mContext; private DownloadManager mDownloadManager; private static volatile DownLoadUtils instance; private DownLoadUtils(Context context) { this.mContext = context.getApplicationContext(); mDownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); }/** * 获取单例对象 * * @param context * @return */ public static DownLoadUtils getInstance(Context context) {if (instance == null) { synchronized (DownLoadUtils.class) { if (instance == null) { instance = new DownLoadUtils(context); return instance; } } } return instance; }/** * 下载 * * @param uri * @param title * @param description * @param appName * @return downloadId */ public long download(String uri, String title, String description, String appName) {//1.构建下载请求 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(uri)); downloadRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE); downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); /**设置漫游状态下是否可以下载*/ downloadRequest.setAllowedOverRoaming(false); /**如果我们希望下载的文件可以被系统的Downloads应用扫描到并管理, 我们需要调用Request对象的setVisibleInDownloadsUi方法,传递参数true.*/ downloadRequest.setVisibleInDownloadsUi(true); //文件保存位置 //file:///storage/emulated/0/Android/data/your-package/files/Download/appName.apk downloadRequest.setDestinationInExternalFilesDir(mContext, Environment.DIRECTORY_DOWNLOADS, appName + ".apk"); // 设置一些基本显示信息 downloadRequest.setTitle(title); downloadRequest.setDescription(description); //req.setMimeType("application/vnd.android.package-archive"); return mDownloadManager.enqueue(downloadRequest); //异步请求 }/** * 获取文件下载路径 * * @param downloadId * @return */ public String getDownloadPath(long downloadId) {DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId); Cursor c = mDownloadManager.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 * @return */ public Uri getDownloadUri(long downloadId) { return mDownloadManager.getUriForDownloadedFile(downloadId); }public DownloadManager getDownloadManager() { return mDownloadManager; }/** * 获取下载状态 * * @param downloadId * @return */ public int getDownloadStatus(long downloadId) {DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId); Cursor c = mDownloadManager.query(query); if (c != null) { try { if (c.moveToFirst()) { return c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)); } } finally { c.close(); } } return -1; }/** * 判断下载管理程序是否可用 * * @return */ public boolean canDownload() {try { int state = mContext.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; }/** * 进入 启用/禁用 下载管理程序界面 */ public void skipToDownloadManager() {String packageName = "com.android.providers.downloads"; Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:" + packageName)); mContext.startActivity(intent); }/** * 进入 启用/禁用 下载管理程序界面 */ public int checkStatus(long downloadId) { DownloadManager.Query query = new DownloadManager.Query(); //通过下载的id查找 query.setFilterById(downloadId); Cursor c = mDownloadManager.query(query); int status = -1; if (c.moveToFirst()) { status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); } return status; }/** * 查询当前下载暂停失败的原因 */推荐阅读
- android 自定义ViewSwipeBackHelper,实现左滑结束Activity
- Android Device Monitor 文件管理使用的常见问题
- 360动态加载的Android插件框架
- Android学习笔记--遇到Duplicate files copied in APK META-INF/LICENSE.txt
- Android应用Design Support Library完全使用实例
- Android笔记——Activity中的回传数据案例(装备选择)
- 兔子--android中百度地图的开发
- Android内存泄漏分析实战
- 玩转Android Camera开发:预览界面四周暗中间亮,仅仅拍摄矩形区域图片(附完整源代码)