相信大多数人都知道,要实现版本更新,首先要获取本地版本号,以及服务器apk的版本号,然后比较两者大小,如果服务器apk版本号大于本地版本,此时我们就需要app版本更新了,版本更新有很多种方法,有的直接开启一个dialog下载apk,有的开启service下载并跟踪进度,我个人比较喜欢开启service来下载apk,那么开启service跟踪下载进度,并安装apk我们要做些什么呐?
(1)跟踪下载进度:首先我们想到的是我们的网络请求是用的rxjava+retrofit,然而这个请求并没有获取下载进度的api,那么我们要怎么实现这个功能了,这里就涉及到重写了.
(2)ResponseBody,Interceptor的重写,获取下载进度
class ProgressInterceptor(var downApkListener:DownApkModel.DownApkListener) : Interceptor {
override fun intercept(chain: Interceptor.Chain?): Response {
var response = chain!!.proceed(chain.request())
return response.newBuilder().body(ProgressResponseBody(response.body()!!, downApkListener)).build()
}
}
class ProgressResponseBody(var responseBody: ResponseBody, downApkListener: DownApkModel.DownApkListener) : ResponseBody() {
var downApkListener = downApkListener
private var bufferedSource: BufferedSource? = null
override fun contentLength(): Long {
return responseBody.contentLength()
}
override fun contentType(): MediaType? {
return responseBody.contentType()
}
override fun source(): BufferedSource {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()))
}
return bufferedSource!!
}
private fun source(source: Source): Source {
return object : ForwardingSource(source) {
var totalBytesRead = 0L
override fun read(sink: Buffer?, byteCount: Long): Long {
//当前读取字节数
var bytesRead = super.read(sink, byteCount)
//增加当前读取的字节数,如果读取完成了bytesRead会返回-1
totalBytesRead += if (bytesRead != -1L) bytesRead else 0L
downApkListener.onProgress((totalBytesRead * 100 / responseBody.contentLength()).toInt())
return bytesRead
}
}
}
}
我们可以看到downApkListener.onProgress()调用了这么一个方法,他就是我们mvp中获取下载进度的方法了,后面代码中我会详解。好了现在我们获取到下载进度了,那么怎么下载,怎么开启service,并安装apk呐,这里我用mvp模式,请看我的项目结构
这个service一共有8个类和接口组成的,很多朋友就会说就一个版本更新需要这么多类和接口吗?从不利用第三方库实现更新版本,而从项目维护,代码可读性上面讲这是我目前想到的最好的办法,mvp模式他最大的特点就是逻辑,代码层次清晰,不再像以前所有的代码都放到v里面,p层负责逻辑,使m与v层完全解耦。好了回归正题,首先我讲解下mvp模式的设计思路,由于就一个service所以我就没有把m,v,p层单独分包了,
interface DownApkView {
fun onStartDownload()
fun onProgress(progress: Int)
fun onFinishDownload()
fun onFail(errorInfo: String)
}
downapkView定义了四个方法onStartDownload,onProgress,onFinishDownload,onFail,然后我们的service类要实现这个接口
class DownApkService : Service(), DownApkView {
lateinit var notificationManager: NotificationManager
lateinit var notificationCompat: NotificationCompat.Builder
private lateinit var downApkPresenterImp: DownApkPresenterImp
private var downApkModelImp = DownApkModelImp()
override fun onBind(p0: Intent?): IBinder {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
//--service启动的时候调用
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
downApkPresenterImp = DownApkPresenterImp(this, downApkModelImp)
downApkPresenterImp.DownApk()
return super.onStartCommand(intent, flags, startId)
}
//-开始下载
override fun onStartDownload() {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationCompat = NotificationCompat.Builder(this@DownApkService)
.setContentTitle("xxx")
.setContentText("正在更新...")
.setProgress(100, 0, false)
.setSmallIcon(R.mipmap.applogo)
}
//-下载进度
override fun onProgress(progress: Int) {
notificationCompat.setProgress(100, progress, false).setContentText("已下载$progress%")
//给通知栏添加一个id
notificationManager.notify(0, notificationCompat.build())
}
//--下载完成后进行安装
override fun onFinishDownload() {
//--通过id取消通知并关闭service
notificationManager.cancel(0)
stopSelf()
//---安装apk
val intent = Intent()
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
//执行动作
intent.action = Intent.ACTION_VIEW
//判读版本是否在7.0以上
if (Build.VERSION.SDK_INT >= 24) {
//provider authorities
val apkUri = FileProvider.getUriForFile(applicationContext, "xxx", downApkModelImp.file)
//Granting Temporary Permissions to a URI
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
} else {
intent.setDataAndType(Uri.fromFile(downApkModelImp.file), "application/vnd.android.package-archive")
}
startActivity(intent)
}
//--下载失败
override fun onFail(errorInfo: String) {
notificationManager.cancel(0)
stopSelf()
Utils.showToast(this, "更新失败")
}
}
然后就是我们的M层:也是一个model接口以及对应接口的实现类
interface DownApkModel {
interface DownApkListener {
fun onStartDownload()
fun onProgress(progress: Int)
fun onFinishDownload()
fun onFail(errorInfo: String)
}
fun DownApk(downApkListener: DownApkListener)
}
DownApkModel接口定义了一个下载方法,以及内部接口中又定义了v接口中几个方法,而
DownApkModelImp就是实现这个接口的类
class DownApkModelImp : DownApkModel {
lateinit var file: File
override fun DownApk(downApkListener: DownApkModel.DownApkListener) {
var progressInterceptor = ProgressInterceptor(downApkListener)
var httpClient = OkHttpClient.Builder()
.addInterceptor(progressInterceptor)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
var retrofit = Retrofit.Builder()
.baseUrl("xxx")
.client(httpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
//--开始下载
downApkListener.onStartDownload()
var service = retrofit.create(HttpApi.DownApkService::class.java)
service.DownApk()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.doOnNext {
//--切换到io线程进行文件流操作
savefiles(it)
}
//下载完成切换到主线程进行操作
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: ResponseBody) {
//-下载成功后安装apk
downApkListener.onFinishDownload()
}
override fun onError(e: Throwable) {
//--下载失败
downApkListener.onFail(e.message!!)
}
})
}
private fun savefiles(responseBody: ResponseBody) {
var inputStream: InputStream = responseBody.byteStream()
file = File(Environment.getExternalStorageDirectory(), System.currentTimeMillis().toString() + "health.apk")
//--不存在就创建file
if (!file.exists()) {
file.createNewFile()
}
var len = 0
var bytes = ByteArray(2048)
var fileOutputStream = FileOutputStream(file)
fileOutputStream.use {
while ({ len = inputStream.read(bytes);len }() != -1) {
fileOutputStream.write(bytes,0,len)
}
}
inputStream.close()
fileOutputStream.close()
}
}
interface DownApkPresenter {
fun DownApk()
}
class DownApkPresenterImp(var downApkView: DownApkView, var downApkModel: DownApkModel) : DownApkModel.DownApkListener, DownApkPresenter {
override fun onStartDownload() {
downApkView.onStartDownload()
}
override fun onProgress(progress: Int) {
downApkView.onProgress(progress)
}
override fun onFinishDownload() {
downApkView.onFinishDownload()
}
override fun onFail(errorInfo: String) {
downApkView.onFail(errorInfo)
}
override fun DownApk() {
downApkModel.DownApk(this)
}
}
可以看到p层并没有什么代码就是一些接口实现,方法调用,接口回调等,他控制v与m层。
好了到这里我们所有的代码都已经写好了,我们回顾下整体思路,开启service时候我们调用接口DownApk开始下载,然后下载过程中我们调用了onStartDownload,onProgress,onFinishDownload这三个方法来监听我们的下载过程,当我们开始下载的时候我们在service开始一个通知栏通过onProgress来更新通知栏下载进度,当service回调了onFinishDownload就关闭通知栏信息以及service当然如果我们下载失败同样关闭,最后我们通过判断sdk版本来安装apk.
mvp:p层中调用m层接口定义的方法,在m层实现类网络请求中根据实际情况来调用m层定义的接口方法,又因为p层控制着m,v层,所以我们p层又实现了这些方法,最后我们通过这些方法去调用v层接口方法,又因为service实现了v层接口,所以从开始下载-下载结束这个过程我们都可以在service里面实现