从大四开始关注阳光沙滩,一边看B站教学一边做毕业设计,第一次在这里写文章有点小激动。作为一直白嫖刚毕业工作半年的萌新,分享一下自己做的断点续传的Demo。我也懒得做动图,就上一张截图
功能要求
能够下载请求的APK文件,在下载过程中能够停止下载,再点击下载时可以接着断点继续下载。
废话不多说上代码
添加依赖
implementation 'com.squareup.okhttp3:okhttp:4.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.1.0'
添加权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
Android 10 的文件读写权限还需要在 applocation 中添加
android:requestLegacyExternalStorage="true"
Android动态获取文件读写权限
/**权限 声明*/
internal var permissions = arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE)
/**检查权限是否开启 */
private fun isPermission() : Boolean{
var ispermission = true
if (Build.VERSION.SDK_INT >= 23){
mPermissionList.clear()
for (i in permissions.indices) {
if (ContextCompat.checkSelfPermission(
this,
permissions[i]
) != PackageManager.PERMISSION_GRANTED
) {
mPermissionList.add(permissions[i])
}
}
if (mPermissionList.size > 0) {
ispermission = false
ActivityCompat.requestPermissions(this, permissions, 100)
}else{
Log.e("test","权限都打开了")
ispermission = true
}
}
return ispermission
}
网络安全配置
在xml文件夹中创建network_security_config.xml
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
并且在applocation 中使用
android:networkSecurityConfig="@xml/network_security_config"
接下来就是重头戏了
下载
fun download(url: String, saveDir : String, fileName: String) : Observable<String>{
return Observable.create{ emitter->
val request = Request.Builder().url(url).build()
val call = okHttpClient.newCall(request)
call.enqueue(object : Callback{
override fun onFailure(call: Call, e: IOException) {
Log.e("test","下载失败:" + e.message)
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful){
isSuccess = true
val buf = ByteArray(1024)
var len: Int
val dir = File(saveDir)
/**判断文件夹是否存在,不存在则创建 */
if (!dir.exists()){
dir.mkdirs()
}
/**下载的文件 */
val file = File(dir,fileName)
try {
val breakFile = RandomAccessFile(file,"rw")
inputStrem = response.body!!.byteStream()
inputStrem!!.skip(breakpoint) //跳过之前所下载的字节
breakFile.seek(breakFile.length()) //跳过以下好的字节在文件后继续添加字节
totalLength = response.body!!.contentLength()
while (((inputStrem!!.read(buf)).also { len = it } != -1) && !isStop){
breakFile.write(buf,0,len)
breakpoint += len
val progress = (breakpoint * 1.0f/ totalLength * 100).toInt()
isComplate = breakpoint == totalLength //判断是否下载完成
emitter.onNext(progress.toString())
}
emitter.onComplete()
}catch (e: Exception){
emitter.onError(e)
}finally {
try {
inputStrem!!.close()
}catch (e:Exception){
}
}
}else{
errCode = response.code
errMessage = response.message
isSuccess = false
emitter.onNext(errCode.toString() + errMessage)
}
}
})
}
}
调用下载
/**下载APK */
private fun DownLoad() {
DownLoadUtil.download(url,dir,testFile)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<String>{
override fun onComplete() {
if (DownLoadUtil.isComplate){
bt_down.isEnabled = true
DownLoadUtil.breakpoint = 0
//修改为正式的文件名
file = File(dir,testFile)
file!!.renameTo(File(dir,fileName))
var dialog = android.app.AlertDialog.Builder(this@MainActivity).create()
dialog.setButton("确认", DialogInterface.OnClickListener { dialogInterface, i ->
installAPK()
})
dialog.setMessage("下载成功")
dialog.show()
}
}
override fun onSubscribe(d: Disposable) {
Toast.makeText(this@MainActivity,"开始下载",Toast.LENGTH_LONG).show()
}
override fun onNext(t: String) {
//Log.e("test","progress == " + t)
if (DownLoadUtil.isSuccess){
pb_down.progress = Integer.parseInt(t)
}else{
var dialog = AlertDialog.Builder(this@MainActivity)
.setTitle("提示")
.setMessage("下载失败:" + t)
.setPositiveButton("确认", DialogInterface.OnClickListener { dialogInterface, i ->
bt_down.isEnabled = true
})
.create()
.show()
Log.e("test","下载失败: " + t)
}
}
override fun onError(e: Throwable) {
Log.e("test","失败: " + e.message)
var dialog = AlertDialog.Builder(this@MainActivity)
.setTitle("提示")
.setMessage("下载失败:" + e.message)
.setPositiveButton("确认", DialogInterface.OnClickListener { dialogInterface, i ->
bt_down.isEnabled = true
})
.create()
.show()
}
})
}
下载基本上就是这样子了
安装APK
为了避免安装包解析出现异常,Android SDK大于等于 7 通过fileprovider来创建Uri 在xml文件夹中创建file_paths.xml
<paths>
<external-path path="Android/data/com.earn/" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>
在AndroidManifest中添加
android:authorities="com.example.downloaddemo.fileprovider"这里注意修改为自己的包名
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.downloaddemo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
/**安装APK */
private fun installAPK() {
val intent = Intent(Intent.ACTION_VIEW)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //android N的权限问题
intent.addCategory("android.intent.category.DEFAULT")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
val contentUri = FileProvider.getUriForFile(this, "com.example.downloaddemo.fileprovider", File(
dir , fileName)
)//注意修改为自己包名
intent.setDataAndType(contentUri, "application/vnd.android.package-archive")
} else {
intent.setDataAndType(Uri.fromFile(File(dir , fileName)), "application/vnd.android.package-archive")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
}
基本上就实现了断点续传的功能,第一次写文章,文章水平可能不太好。有什么地方存在问题可以提出或者有带改进的地方和更好的方法可以一起交流。命名规则请忽略。