Android 本地更新APK

作者: 神户西瓜 | 来源:发表于2021-12-18 09:29 被阅读0次

    很多APP都会有自动更新APP然后本地安装的功能 之前一直是用AsnycTask来做的 最近发现AsyncTask被标记为过时 那么就换一种方式来写吧
    我自己是做在Dialog里面 使用okhttp进行文件下载 配合自定义View的进度条进行展示的
    首先老规矩上图


    开始下载了
    下载完成后自动进入安装页面

    话不多说 我们先来定义一个用来回调的接口 分别对应成功 失败 和进度

    interface DownloadListener {
        fun onSuccess()
        fun onFailed(msg: String)
        fun onProgress(progress: Int)
    }
    

    首先就是我们对于文件的处理

    //首先是我们的下载地址 没什么好说的就使用app本地的file文件夹就可以了
     val path = getExternalFilesDir(null)!!.absolutePath+"/app.apk"
    //先生成我们的文件
     val file = File(path)
    //这里看你需求 是否需要短线点续传  需要的话就记录一下当前下载长度
     var currentLength = 0L
      if (file.exists()){
    //  currentLength=file.length()//下载长度 如果你需要断点续传就使用这个参数并去掉删除文件的方法
           file.delete()
       }
    

    接着创建一个okhttp实例并获取我们目标文件的总长度(大小)用来处理我们下载的百分比

        private val client = OkHttpClient()
        val request = Request.Builder().url(url).build()
            val response = client.newCall(request).execute()
            return if (response.isSuccessful) {
                //获取文件长度
                val length = response.body?.contentLength() as Long
                response.body!!.close()
                length
            } else {
              //如果回调失败了就返回长度为0
                0
            }
    

    根据长度来判断文件是否下载完了

     val fileLength = fileLength(url)//获取文件总长度
         if (fileLength == 0L) {//如果获取的文件长度为0则失败
             listener.onFailed("文件长度为0")
        } else if (currentLength == fileLength) {//如果获取的文件长度和下载的文件长度相等则下载完毕
               listener.onSuccess()
        }
    

    接着就要开始下载文件咯

      //使用OkHttp进行APK下载
                //请求头为RANGE 如果是断点续传就是bytes=[start,end] 
                val request = Request.Builder().addHeader("RANGE", "bytes=$currentLength-").url(url).build()
                val response = client.newCall(request).execute()
                val inputStream = response.body?.byteStream()
                val saverFile = RandomAccessFile(file, "rw")
                saverFile.seek(currentLength)
                val b = ByteArray(1024)
                var total = 0
                var len = inputStream!!.read(b)
                while (len != -1) {
                    total += len
                    saverFile.write(b, 0, len)
                    val progress = ((total + currentLength) * 100 / fileLength).toInt()
                    //下载的百分比回调
                    listener.onProgress(progress)
                    len = inputStream.read(b)
                }
                response.body?.close()
                inputStream.close()
                saverFile.close()
                //下载完毕
                listener.onSuccess()
    

    文件下载完毕就可以进行apk的安装了

    val file=File(path)
            val intent = Intent(Intent.ACTION_VIEW)
            intent.setDataAndType(Uri.parse("file://$file"), "application/vnd.android.package-archive")
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                //这边使用你自己定义的FileProvider 此处就不进行讲解了
                val contentUri = FileProvider.getUriForFile(this, "com.xxxx.provider", file)
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                intent.setDataAndType(contentUri, "application/vnd.android.package-archive")
            } else {
                intent.setDataAndType(Uri.parse("file://$file"), "application/vnd.android.package-archive")
            }
            startActivity(intent)
            //杀掉APP
            android.os.Process.killProcess(android.os.Process.myPid())
    

    另外需要使用的权限有 都不是运行时权限

    //安装本地APK的权限
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
    //访问网络的权限
     <uses-permission android:name="android.permission.INTERNET" />
    

    好了完结散花 最后送上我封装好的代码
    首先是接口

    interface DownloadListener {
        fun onSuccess()
        fun onFailed(msg: String)
        fun onProgress(progress: Int)
    }
    

    //然后是下载的工具类

    object DownloadUtil{
    
        private val client = OkHttpClient()
    
        fun download(url:String,path:String,listener:DownloadListener){
            thread {
                val file = File(path)
                var currentLength = 0L
                if (file.exists()){
    //                currentLength=file.length()//下载长度 如果你需要断点续传就使用这个参数并去掉删除的方法
                    file.delete()
                }
                val fileLength = fileLength(url)//获取文件总长度
                if (fileLength == 0L) {//如果获取的文件长度为0则失败
                    listener.onFailed("文件长度为0")
                } else if (currentLength == fileLength) {//如果获取的文件长度和下载的文件长度相等则下载完毕
                    listener.onSuccess()
                }
                //使用OkHttp进行APK下载
                //请求头为RANGE 如果是断点续传就是bytes=[start,end]
                val request = Request.Builder().addHeader("RANGE", "bytes=$currentLength-").url(url).build()
                val response = client.newCall(request).execute()
                val inputStream = response.body?.byteStream()
                val saverFile = RandomAccessFile(file, "rw")
                saverFile.seek(currentLength)
                val b = ByteArray(1024)
                var total = 0
                var len = inputStream!!.read(b)
                while (len != -1) {
                    total += len
                    saverFile.write(b, 0, len)
                    val progress = ((total + currentLength) * 100 / fileLength).toInt()
                    //下载的百分比回调
                    listener.onProgress(progress)
                    len = inputStream.read(b)
                }
                response.body?.close()
                inputStream.close()
                saverFile.close()
                //下载完毕
                listener.onSuccess()
            }
        }
    
        //获取文件长度
        private fun fileLength(url: String): Long {
            val request = Request.Builder().url(url).build()
            val response = client.newCall(request).execute()
            return if (response.isSuccessful) {
                val length = response.body?.contentLength() as Long
                response.body!!.close()
                length
            } else {
                0
            }
        }
    }
    

    接着是用来展示下载页面的Dialog

      class UpdateDialog( private val downloadUrl: String, private val path: String, private val because: String) : DialogFragment() {
        //进度展示
        private var mProgressTV: TextView? = null
        private var mBecauseTV: TextView? = null
        private var mUpdateViw: UpdateProgressView? = null
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            val view=inflater.inflate(R.layout.layout_update_dialog,container,false)
            mProgressTV = view.findViewById(R.id.mProgressTV)
            mBecauseTV = view.findViewById(R.id.mBecauseTV)
            mUpdateViw = view.findViewById(R.id.mUpdateViw)
            mBecauseTV?.text = because.handlerNull()
    
            DownloadUtil.download(downloadUrl, path, object : DownloadListener {
                override fun onSuccess() {
                    updateApp(path)
                }
    
                override fun onFailed(msg: String) {
                    myLog("下载失败了")
                }
    
                override fun onProgress(progress: Int) {
                    myLog("下载百分比:$progress %")
                    mUpdateViw?.handlerPercent(progress)
                    requireActivity().runOnUiThread {
                        val  p="正在升级中${progress}%"
                        mProgressTV?.text = p
                    }
                }
            })
            return view
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setStyle(STYLE_NORMAL, R.style.CommonDialog)
        }
    
    
        private fun updateApp(path: String) {
            MyLog.d("安装APP")
            val file = File(path)
            val intent = Intent(Intent.ACTION_VIEW)
            intent.setDataAndType(Uri.parse("file://$file"), "application/vnd.android.package-archive")
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                val contentUri = FileProvider.getUriForFile(requireContext(), "com.xxxx.provider", file)
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                intent.setDataAndType(contentUri, "application/vnd.android.package-archive")
            } else {
                intent.setDataAndType(Uri.parse("file://$file"), "application/vnd.android.package-archive")
            }
            requireActivity().startActivity(intent)
            //杀掉APP
            android.os.Process.killProcess(android.os.Process.myPid())
        }
    }
    
    

    dialog的页面 这里使用的展示进度条的自定义View可以看我另外一篇文章
    https://www.jianshu.com/p/001fc038b557

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="horizontal">
    
        <LinearLayout
            android:gravity="center_horizontal"
            android:layout_width="260dp"
            android:layout_height="300dp"
            android:background="@drawable/bg_solid_whit_radius5dp"
            android:orientation="vertical"
            tools:ignore="UselessParent">
            <TextView
                android:id="@+id/mBecauseTV"
                android:textSize="12sp"
                android:layout_marginTop="20dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <com.android.bowenporjectv2.weiget.UpdateProgressView
                android:id="@+id/mUpdateViw"
                android:layout_width="150dp"
                android:layout_height="150dp"/>
    
            <TextView
                android:layout_marginHorizontal="20dp"
                android:id="@+id/mProgressTV"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="20dp"
                android:text="正在升级中...%"
                android:textSize="12sp" />
        </LinearLayout>
    </LinearLayout>
    

    dialog的样式

     <style name="CommonDialog">
            <item name="android:windowCloseOnTouchOutside">true</item>
            <item name="android:windowFrame">@null</item>
            <item name="android:windowIsFloating">true</item>
            <item name="android:windowIsTranslucent">true</item>
            <item name="android:windowNoTitle">true</item>
            <item name="android:background">@android:color/transparent</item>
            <item name="android:windowBackground">@android:color/transparent</item>
        </style>
    

    最后就是使用了

    //下载地址
                val url = "你自己app的下载地址"
                //对应手机android/data/你的包名下/files/
                val path = getExternalFilesDir(null)!!.absolutePath+"/app.apk"
                UpdateDialog(url,path,"优化app").show(supportFragmentManager,"")
    

    相关文章

      网友评论

        本文标题:Android 本地更新APK

        本文链接:https://www.haomeiwen.com/subject/ktbifrtx.html