├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── qubin │ │ └── downloadmanager │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── qubin │ │ │ └── downloadmanager │ │ │ ├── CommonDialog.java │ │ │ ├── DownLoadBuilder.java │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── dialog.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── provider_paths.xml │ └── test │ └── java │ └── com │ └── qubin │ └── downloadmanager │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DownloadManagerDemo 2 | 一行代码处理更新那些事儿。用DwonLoadManage封装一个app的更新组件。 3 | 4 | 5 | 6 | ### 前言 7 | 8 | android app的更新是我们在平时开发的时候常常需要遇到的问题。通常的情况是我们用第三方的网络加载库去进行地址的下载,然后进行更新。例如okHttp、volley等,都具备了下载的功能。 9 | 10 | 但是我们在用这些第三方库进行下载的时候可能需要做很多之外的处理,比如更新的时候处理进度。写一个notification去提示下载显示,这无疑让我们在编写代码的时候增加了很多不必要的麻烦。其实Android系统他已经自带了一个下载的库,DownloadManage,并且在里面已经帮我们处理了很多事情,我们只需知道他的用法,再做一些封装便可以处理我们日常中绝大多数下载的问题。 11 | 12 | ### 使用: 13 | 14 | 首先在app的gradle中引入相应的包 15 | ``` 16 | implementation 'com.qubin.download:download:1.0.1' 17 | ``` 18 | 19 | 然后是权限问题: 20 | 21 | ``` 22 | 23 | 24 | 25 | 26 | ``` 27 | 28 | 之后再在项目的buid中的repositories引入如下: 29 | 30 | ``` 31 | maven { url 'https://jitpack.io' } 32 | ``` 33 | 34 | 由于需要安装,所以需要进行广播的注册,我们先进行广播注册。 35 | 36 | 在oncreate中注册广播,其中downloadCompleteBroadcast为注册的广播 37 | 38 | ``` 39 | //实例化广播 40 | downloadCompleteBroadcast = new DownloadCompleteBroadcast(); 41 | //广播过滤器 42 | IntentFilter intentFilter = new IntentFilter(); 43 | intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 44 | registerReceiver(downloadCompleteBroadcast,intentFilter); 45 | 46 | ``` 47 | 48 | 然后在onDestroy()中注销广播 49 | ``` 50 | unregisterReceiver(downloadCompleteBroadcast); 51 | ``` 52 | 53 | 最后写一个广播来监听是否下载完成,启动安装 54 | 55 | ``` 56 | class DownloadCompleteBroadcast extends BroadcastReceiver { 57 | 58 | @Override 59 | public void onReceive(Context context, Intent intent) { 60 | if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){ 61 | //安装 62 | DownLoadBuilder.intallApk(MainActivity.this,apkName); 63 | } 64 | } 65 | } 66 | 67 | ``` 68 | 69 | 然后就可以开始下载了 70 | 71 | ``` 72 | new DownLoadBuilder.Builder(this) 73 | .addUrl(下载地址) 74 | .isWiFi(true) 75 | .addDownLoadName(apkName) 76 | .addDscription("开始下载") 77 | .builder(); 78 | 79 | ``` 80 | 81 | 那么我们先来讲解一些常见的api用法。 82 | 83 | ### DownloadManager 84 | 85 | 首先,下载嘛,当然需要网络权限和文件读取写入权限啦,不然没网络如何下载?下载之后的apk放哪里?于是我们首先在清单文件中添加权限。 86 | 87 | ```JAVA 88 | 89 | 90 | 91 | 92 | ``` 93 | 之后是实例化这个DownloadManager类,并且传入下载的地址。 94 | 95 | ``` 96 | DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); 97 | ``` 98 | 99 | 在DownloadManager内部会判断手机所处在的环境是什么,也就是说,我们可以设置是在wifi情况下进行下载还是在移动网络情况下进行下载。 100 | 101 | ``` 102 | request.setAllowedNetworkTypes() 103 | ``` 104 | 105 | - **DownloadManager.Request.NETWORK_WIFI:** 代表在wifi情况下下载 106 | 107 | - **DownloadManager.Request.NETWORK_MOBILE:** 代表在移动网络下进行下载 108 | 109 | 如果设置的是wifi情况下下载,但是切换到了4g网络,那么程序会自动停止,如果这时候再次切换回来,那么又会自动下载,并且还是会自动断点续传。 110 | 111 | 定制化notification: 112 | 113 | 在点击进行下载的时候,一般在手机下拉框中会出现一个notification来显示下载进行,DownloadManager在这方面做得很智能,几行代码就可以直接搞定这个复杂的功能。 114 | 115 | ```JAVA 116 | //下载时显示notification 117 | request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 118 | //添加描述信息 119 | request.setDescription(description); 120 | ``` 121 | 122 | - VISIBILTY_HIDDEN: Notification:将不会显示,如果设置该属性的话,必须要添加权限。Android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 123 | VISIBILITY_VISIBLE: Notification显示,但是只是在下载任务执行的过程中显示,下载完成自动消失。(默认值) 124 | 125 | - VISIBILITY_VISIBLE_NOTIFY_COMPLETED : Notification显示,下载进行时,和完成之后都会显示。 126 | 127 | - VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION :只有当任务完成时,Notification才会显示。 128 | 129 | 之后便可以设置存储地址 130 | 131 | ```JAVA 132 | //file:///storage/emulated/0/Download/downloadName.apk 133 | request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, downloadName +".apk"); 134 | ``` 135 | 136 | 最后将请求加入队列,便可以开始进行下载了。 137 | 138 | ```JAVA 139 | request.setMimeType("application/vnd.android.package-archive"); 140 | DownloadManager systemService = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 141 | systemService.enqueue(request); 142 | ``` 143 | 144 | 这时,就可以看到开始进行了下载: 145 | 146 | ![](https://note.youdao.com/yws/public/resource/59bb093c694f261c2964721b8dbf7c8b/xmlnote/WEBRESOURCE74cc60920f9fcfe9310c84814e2dfe9f/14255) 147 | 148 | 那么如何才知道下载完成,来进行安装呢?在DownloadManager内部在下载完成之后会发送一个广播告诉下载完成。**DownloadManager.ACTION_DOWNLOAD_COMPLETE** 149 | 150 | 于是我们便可以写一个broadcast来进行接收广播,同时处理安装事件。 151 | 152 | ```JAVA 153 | class DownloadCompleteBroadcast extends BroadcastReceiver{ 154 | 155 | @Override 156 | public void onReceive(Context context, Intent intent) { 157 | if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){ 158 | 159 | //TODO... 160 | } 161 | } 162 | } 163 | ``` 164 | 165 | ### 兼容处理: 166 | 167 | 在安装的时候就开始体现了版本的差异了,需要开始做兼容。我们在6.0以下版本,可以直接使用以下代码进行安装即可。 168 | 169 | ```java 170 | Intent intent = new Intent(Intent.ACTION_VIEW); 171 | intent.setDataAndType(Uri.parse("file:///storage/emulated/0/Download/" + downloadName +".apk"), "application/vnd.android.package-archive"); 172 | //为这个新apk开启一个新的activity栈 173 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 174 | //开始安装 175 | startActivity(intent); 176 | ``` 177 | 178 | ### 6.0兼容: 179 | 180 | 在6.0时候引入的动态权限问题。也就是说,我们在清单文件设置了权限问题,但是在需要一些比较私密的权限的时候,必须由用户去进行选择,如果不在处理这些权限的时候让用户去选择,那么程序必定会奔溃。这里推荐一个好用的动态权限库,RxPersmissions。这个库运用了RxJava的链式思想,来处理动态权限问题。github地址:[RxPermissions](https://github.com/tbruyelle/RxPermissions)。使用起来也非常简单,直接在进行下载的时候给出权限授权提示即可。 181 | 182 | ```JAVA 183 | RxPermissions rxPermissions = new RxPermissions(this); 184 | rxPermissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE) 185 | .subscribe(new Consumer() { 186 | @Override 187 | public void accept(Permission permission) throws Exception { 188 | if (permission.granted){ 189 | //TODO... 190 | 191 | }else { 192 | Toast.makeText(context, "权限未开启", Toast.LENGTH_SHORT).show(); 193 | } 194 | } 195 | }); 196 | ``` 197 | 198 | ### 7.0兼容: 199 | 200 | 从文档里知道,Android 7 开始增加安全性,文件私有化,而需要共享文件给其他程序,例如APK安装程序,需要通过FileProvider配置共享文件,配置表是基于XML文件实现,然后通过Content URI携带配置文件xml来共享文件. 201 | 202 | 实现配置FileProvider 需要两步: 203 | 第一步: 需要配置AndroidManifest.xml清单. 204 | 205 | ``` 206 | 211 | 214 | 215 | ``` 216 | 217 | 第二步:建立文件 res/xml/file_paths.xml. 218 | 219 | ``` 220 |   221 |   222 |       223 |           237 |           238 |       239 | 240 | 241 | ``` 242 | 243 | 而其中的 path=""是代表根目录,也就是向共享的应用程序共享根目录以及其子目录的任何一个文件.理论上说假如共享程序是恶意程序,那它便可以获取你的应用的所有共享文件信息. 244 | 245 | 最后准备好上面两步便可以安装文件 246 | 247 | ```java 248 | 249 | File file= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "/" + downloadName +".apk"); 250 | //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件 251 | Uri apkUri = FileProvider.getUriForFile(context, "com.qubin.downloadmanager", file); 252 | 253 | Intent intent = new Intent(Intent.ACTION_VIEW); 254 | // 由于没有在Activity环境下启动Activity,设置下面的标签 255 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 256 | //添加这一句表示对目标应用临时授权该Uri所代表的文件 257 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 258 | intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); 259 | startActivity(intent); 260 | ``` 261 | 262 | ### 8.0兼容: 263 | 264 | Android 8到时有了什么改变以致安装apk的方法有很大改变呢? 265 | 266 | 在2017年8月29号的谷歌开发者博客中写道 <<在 Android O 中更安全地获取应用>>新的安装未知应用的,Android O 禁用了总是安装未知应用的选择,改为安装未知应用时提出设置的提示,减少恶意应用通过虚假的安装界面欺骗用户行为. 267 | 所以开发者需要调整AndroidManifest文件里的权限,增加 REQUEST_INSTALL_PACKAGES权限. 268 | 269 | ``` 270 | 271 | ``` 272 | 273 | 274 | 谷歌建议是通过PackageManager canRequestPackageInstalls() 的API,查询此权限的状态,然后使用使用 ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent 操作。 275 | 276 | ```java 277 | Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); 278 | 279 | startActivityForResult(intent, RESULT_CODE); 280 | 281 | ``` 282 | 283 | 但是我不建议这样使用,因为使用 ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent 操作后会跳到所有应用列表,然后从众多的应用里选择对应的APP的选择进入再打开权限,这样的用户体验不好。可以直接等到安装的时候点击跳转开发这个权限即可。 284 | 285 | ### 封装 286 | 287 | 好了,有了以上的一些操作,之后我利用了builder模式直接进行了一层封装操作,便可以方便我们使用这个下载的方法了。具体的builder写法不难,这里不做过多的说明,直接看代码就能看懂。 288 | 289 | 另外,我们在使用更新的时候一般来说,会先进行网络请求接口,拿到更新提示文案,弹出一个dialog弹窗,点击下载之后便可以开始下载。这里我也写了一个通用的dialog,通过这个便可以进行操作了。也是利用了builder设计模式。如果对这块不懂,可以参考一下我写的另一篇文章。[动手造轮子——用Builder模式撸一个通用版本的Dialog](https://juejin.im/post/5be41dfaf265da6151143dbd)。 290 | 291 | 292 | ![](https://note.youdao.com/yws/public/resource/59bb093c694f261c2964721b8dbf7c8b/xmlnote/WEBRESOURCEfbc38eefe17e58097f8b5cc2102ec29e/14303) 293 | 294 | 这里只是写了一个大概的界面,具体的界面操作,可以自己去根据这个demo进行改造。 295 | 296 | 在我们使用这个dialog: 297 | 298 | ```java 299 | commonDialog = new CommonDialog.Builder(MainActivity.this) 300 | .view(R.layout.dialog) //布局文件 301 | .style(R.style.Dialog) //样式透明 302 | .setMessage(R.id.txt_sure,"开始更新") //更新按钮文字 303 | .setMessage(R.id.txt_cancel,"取消更新") //取消按钮文字 304 | .addViewOnClick(R.id.txt_sure, new View.OnClickListener() { //点击开始更新按钮点击事件 305 | @Override 306 | public void onClick(View v) { 307 | 308 | Toast.makeText(MainActivity.this, "开始下载", Toast.LENGTH_SHORT).show(); 309 | commonDialog.dismiss(); 310 | } 311 | }) 312 | .addViewOnClick(R.id.txt_cancel, new View.OnClickListener() { //取消按钮点击事件 313 | @Override 314 | public void onClick(View v) { 315 | commonDialog.dismiss(); 316 | } 317 | }) 318 | .build(); 319 | 320 | commonDialog.show(); 321 | ``` 322 | 323 | 在进行更新时,写下一下一行代码便可以开始进行更新了 324 | 325 | ```java 326 | new DownLoadBuilder.Builder(MainActivity.this) 327 | .addUrl(url) 328 | .isWiFi(true) 329 | .addDownLoadName(apkName) 330 | .addDscription("开始下载") 331 | .builder(); 332 | ``` 333 | 334 | 是不是觉得很方便?不要忘记下完写一个广播来接收下载完成事件。 335 | 336 | 所有代码都放到了github上,如果需要使用以上两个方法,只需要将 **DownLoadBuilder** 和 **CommonDialog** 这两个类引入到自己项目中即可操作。 337 | 338 | 如果觉得可以,欢迎start。 339 | 340 | [代码dmeo的github地址](https://github.com/hamuamu0/DownloadManagerDemo) 341 | 342 | 有兴趣可以关注我的小专栏,学习更多职场产品思考知识:[小专栏](https://xiaozhuanlan.com/goodjob) 343 | 344 | ![](https://user-gold-cdn.xitu.io/2018/10/31/166c8a201aa27a0d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 345 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.qubin.downloadmanager" 7 | minSdkVersion 15 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:28.0.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | implementation 'com.github.tbruyelle:rxpermissions:0.10.2' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/qubin/downloadmanager/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.qubin.downloadmanager; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.qubin.downloadmanager", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/qubin/downloadmanager/CommonDialog.java: -------------------------------------------------------------------------------- 1 | package com.qubin.downloadmanager; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.widget.TextView; 9 | 10 | /** 11 | * 类或接口的描述信息 12 | * 13 | * @Author:qubin 14 | * @Theme: 通用Dialog 15 | * @Data:2018/12/17 16 | * @Describe: 17 | */ 18 | public class CommonDialog extends Dialog { 19 | 20 | 21 | private boolean cancelTouchout; 22 | private View view; 23 | 24 | public CommonDialog(Builder builder) { 25 | super(builder.context); 26 | 27 | cancelTouchout = builder.cancelTouchout; 28 | view = builder.view; 29 | } 30 | 31 | public CommonDialog(Builder builder, int resStyle) { 32 | super(builder.context, resStyle); 33 | 34 | cancelTouchout = builder.cancelTouchout; 35 | view = builder.view; 36 | } 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(view); 42 | setCancelable(cancelTouchout); 43 | setCanceledOnTouchOutside(cancelTouchout); 44 | } 45 | 46 | public static final class Builder{ 47 | private Context context; 48 | private boolean cancelTouchout; 49 | private View view; 50 | private int resStyle = -1; 51 | 52 | public Builder(Context context){ 53 | this.context = context; 54 | } 55 | 56 | public Builder view(int resView){ 57 | view = LayoutInflater.from(context).inflate(resView,null); 58 | return this; 59 | } 60 | 61 | public Builder style(int resStyle){ 62 | this.resStyle = resStyle; 63 | return this; 64 | } 65 | 66 | public Builder cancelTouchout(boolean val){ 67 | cancelTouchout = val; 68 | return this; 69 | } 70 | 71 | public Builder addViewOnClick(int viewRes,View.OnClickListener listener){ 72 | view.findViewById(viewRes).setOnClickListener(listener); 73 | return this; 74 | } 75 | 76 | public Builder setTitle(int viewRes,String title){ 77 | TextView textTitle = (TextView)view.findViewById(viewRes); 78 | textTitle.setText(title); 79 | return this; 80 | } 81 | 82 | public Builder setMessage(int viewRes,String message){ 83 | TextView txtMessage = (TextView)view.findViewById(viewRes); 84 | txtMessage.setText(message); 85 | return this; 86 | } 87 | 88 | public Builder setVersion(int viewVersion,String newVersion){ 89 | TextView txtMessage = (TextView)view.findViewById(viewVersion); 90 | txtMessage.setText("发现新版本" + newVersion); 91 | return this; 92 | } 93 | 94 | public CommonDialog build(){ 95 | if (resStyle != -1){ 96 | return new CommonDialog(this,resStyle); 97 | }else { 98 | return new CommonDialog(this); 99 | } 100 | } 101 | 102 | 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/qubin/downloadmanager/DownLoadBuilder.java: -------------------------------------------------------------------------------- 1 | package com.qubin.downloadmanager; 2 | 3 | import android.Manifest; 4 | import android.app.DownloadManager; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.os.Build; 9 | import android.os.Environment; 10 | import android.support.v4.app.FragmentActivity; 11 | import android.support.v4.content.FileProvider; 12 | import android.text.TextUtils; 13 | import android.widget.Toast; 14 | 15 | import com.tbruyelle.rxpermissions2.Permission; 16 | import com.tbruyelle.rxpermissions2.RxPermissions; 17 | 18 | import java.io.File; 19 | 20 | import io.reactivex.functions.Consumer; 21 | 22 | /** 23 | * 类或接口的描述信息 24 | * 25 | * @Author:qubin 26 | * @Theme: 更新 27 | * @Data:2018/12/17 28 | * @Describe: 29 | */ 30 | public class DownLoadBuilder { 31 | 32 | private Context context; 33 | private String url; 34 | private Boolean isWiFi; 35 | private String description; 36 | private String downloadName; 37 | 38 | public DownLoadBuilder(Builder builder) { 39 | super(); 40 | context = builder.context; 41 | url = builder.url; 42 | isWiFi = builder.isWiFi; 43 | description = builder.description; 44 | downloadName = builder.downloadName; 45 | if (isWiFi == null){ 46 | isWiFi = true; 47 | } 48 | download(context,url,isWiFi,description,downloadName); 49 | } 50 | 51 | 52 | 53 | static final class Builder{ 54 | 55 | private Context context; 56 | private String url; 57 | private Boolean isWiFi; 58 | private String description; 59 | private String downloadName; 60 | 61 | 62 | public Builder(Context context){ 63 | this.context = context; 64 | 65 | } 66 | 67 | public Builder addUrl(String url){ 68 | this.url = url; 69 | return this; 70 | } 71 | 72 | public Builder isWiFi(Boolean isWiFi){ 73 | this.isWiFi = isWiFi; 74 | return this; 75 | } 76 | 77 | public Builder addDscription(String description){ 78 | this.description =description; 79 | return this; 80 | } 81 | 82 | public Builder addDownLoadName(String downloadName){ 83 | this.downloadName = downloadName; 84 | return this; 85 | } 86 | 87 | public DownLoadBuilder builder(){ 88 | return new DownLoadBuilder(this); 89 | } 90 | 91 | } 92 | 93 | public static void download(final Context context, final String url, final boolean isWIFI, final String description, final String downloadName){ 94 | RxPermissions rxPermissions = new RxPermissions((FragmentActivity) context); 95 | if (TextUtils.isEmpty(url)){ 96 | Toast.makeText(context, "下载地址不能为空", Toast.LENGTH_SHORT).show(); 97 | return; 98 | } 99 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M){ 100 | downloadApk(context,url,isWIFI,description,downloadName); 101 | }else { 102 | rxPermissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE) 103 | .subscribe(new Consumer() { 104 | @Override 105 | public void accept(Permission permission) throws Exception { 106 | if (permission.granted){ 107 | downloadApk(context,url,isWIFI,description,downloadName); 108 | }else { 109 | Toast.makeText(context, "权限未开启", Toast.LENGTH_SHORT).show(); 110 | } 111 | } 112 | }); 113 | 114 | 115 | } 116 | 117 | 118 | } 119 | 120 | private static void downloadApk(Context context, String url, boolean isWIFI, String description, String downloadName){ 121 | DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); 122 | 123 | //判断是在wifi还是在移动网络下进行下载 124 | if (isWIFI){ 125 | request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); 126 | }else { 127 | request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE); 128 | } 129 | //下载时显示notification 130 | request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 131 | //添加描述信息 132 | request.setDescription(description); 133 | //file:///storage/emulated/0/Download/downloadName.apk 134 | request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, downloadName +".apk"); 135 | 136 | request.setMimeType("application/vnd.android.package-archive"); 137 | DownloadManager systemService = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 138 | systemService.enqueue(request); } 139 | 140 | 141 | public static void intallApk(Context context,String downloadName){ 142 | 143 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N){ 144 | //6.0及以下安装 145 | Intent intent = new Intent(Intent.ACTION_VIEW); 146 | intent.setDataAndType(Uri.parse("file:///storage/emulated/0/Download/" + downloadName +".apk"), "application/vnd.android.package-archive"); 147 | //为这个新apk开启一个新的activity栈 148 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 149 | //开始安装 150 | context.startActivity(intent); 151 | 152 | }else { 153 | //7.0及以上 154 | File file= new File( 155 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) 156 | , "/" + downloadName +".apk"); 157 | //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件 158 | Uri apkUri = 159 | FileProvider.getUriForFile(context, "com.qubin.downloadmanager", file); 160 | 161 | Intent intent = new Intent(Intent.ACTION_VIEW); 162 | // 由于没有在Activity环境下启动Activity,设置下面的标签 163 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 164 | //添加这一句表示对目标应用临时授权该Uri所代表的文件 165 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 166 | intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); 167 | context.startActivity(intent); 168 | 169 | 170 | } 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /app/src/main/java/com/qubin/downloadmanager/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.qubin.downloadmanager; 2 | 3 | import android.Manifest; 4 | import android.app.DownloadManager; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.os.Bundle; 11 | import android.view.View; 12 | import android.widget.Button; 13 | import android.widget.Toast; 14 | 15 | 16 | 17 | public class MainActivity extends AppCompatActivity { 18 | 19 | Button btnDownload; 20 | String url = "https://downpack.baidu.com/appsearch_AndroidPhone_v8.0.3(1.0.65.172)_1012271b.apk"; 21 | private DownloadCompleteBroadcast downloadCompleteBroadcast; 22 | private CommonDialog commonDialog; 23 | private String apkName = "测试"; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_main); 29 | btnDownload = (Button)findViewById(R.id.btn_down); 30 | downloadCompleteBroadcast = new DownloadCompleteBroadcast(); 31 | IntentFilter intentFilter = new IntentFilter(); 32 | intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 33 | registerReceiver(downloadCompleteBroadcast,intentFilter); 34 | btnDownload.setOnClickListener(new View.OnClickListener() { 35 | @Override 36 | public void onClick(View v) { 37 | 38 | commonDialog = new CommonDialog.Builder(MainActivity.this) 39 | .view(R.layout.dialog) 40 | .style(R.style.Dialog) 41 | .setMessage(R.id.txt_sure,"开始更新") 42 | .setMessage(R.id.txt_cancel,"取消更新") 43 | .addViewOnClick(R.id.txt_sure, new View.OnClickListener() { 44 | @Override 45 | public void onClick(View v) { 46 | 47 | new DownLoadBuilder.Builder(MainActivity.this) 48 | .addUrl(url) 49 | .isWiFi(true) 50 | .addDownLoadName(apkName) 51 | .addDscription("开始下载") 52 | .builder(); 53 | Toast.makeText(MainActivity.this, "开始下载", Toast.LENGTH_SHORT).show(); 54 | commonDialog.dismiss(); 55 | } 56 | }) 57 | .addViewOnClick(R.id.txt_cancel, new View.OnClickListener() { 58 | @Override 59 | public void onClick(View v) { 60 | commonDialog.dismiss(); 61 | } 62 | }) 63 | .build(); 64 | 65 | commonDialog.show(); 66 | 67 | 68 | 69 | 70 | } 71 | }); 72 | } 73 | 74 | 75 | 76 | @Override 77 | protected void onDestroy() { 78 | super.onDestroy(); 79 | unregisterReceiver(downloadCompleteBroadcast); 80 | } 81 | 82 | class DownloadCompleteBroadcast extends BroadcastReceiver{ 83 | 84 | @Override 85 | public void onReceive(Context context, Intent intent) { 86 | if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){ 87 | 88 | DownLoadBuilder.intallApk(MainActivity.this,apkName); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |