Android APP 在线更新功能

497 阅读3分钟

我正在参加「掘金·启航计划」

1.最近公司有项目需要用上在线更新功能 然后我在网上借鉴了很多相关在线更新的案例 发现轮子哥的安卓中台APP的在线更新可借鉴性和实现方式都很简单指的学习一下 后面话不多说先看样子 图片描述 先给一个弹窗的工具类

class UpdateDialog {

    class Builder(context: Context) : BaseDialog.Builder<Builder>(context) {

        private val nameView: TextView? by lazy { findViewById(R.id.tv_update_name) }
        private val detailsView: TextView? by lazy { findViewById(R.id.tv_update_details) }
        private val progressView: ProgressBar? by lazy { findViewById(R.id.pb_update_progress) }
        private val updateView: TextView? by lazy { findViewById(R.id.tv_update_update) }
        private val closeView: TextView? by lazy { findViewById(R.id.tv_update_close) }

        /** Apk 文件 */
        private var apkFile: File? = null

        /** 下载地址 */
        private var downloadUrl: String? = null

        /** 文件 MD5 */
        private var fileMd5: String? = null

        /** 是否强制更新 */
        private var forceUpdate = false

        /** 当前是否下载中 */
        private var downloading = false

        /** 当前是否下载完毕 */
        private var downloadComplete = false

        init {
            setContentView(R.layout.update_dialog)
            setAnimStyle(AnimAction.ANIM_BOTTOM)
            setCancelable(false)
            setOnClickListener(updateView, closeView)

            // 让 TextView 支持滚动
            detailsView?.movementMethod = ScrollingMovementMethod()
        }

        /**
         * 设置版本名
         */
        fun setVersionName(name: CharSequence?): Builder = apply {
            nameView?.text = name
        }

        /**
         * 设置更新日志
         */
        fun setUpdateLog(text: CharSequence?): Builder = apply {
            detailsView?.text = text
            detailsView?.visibility = if (text == null) View.GONE else View.VISIBLE
        }

        /**
         * 设置强制更新
         */
        fun setForceUpdate(force: Boolean): Builder = apply {
            forceUpdate = force
            closeView?.visibility = if (force) View.GONE else View.VISIBLE
            setCancelable(!force)
        }

        /**
         * 设置下载 url
         */
        fun setDownloadUrl(url: String?): Builder = apply {
            downloadUrl = url
        }

        /**
         * 设置文件 md5
         */
        fun setFileMd5(md5: String?): Builder = apply {
            fileMd5 = md5
        }

        @SingleClick
        override fun onClick(view: View) {
            if (view === closeView) {
                dismiss()
            } else if (view === updateView) {
                // 判断下载状态
                if (downloadComplete) {
                    if (apkFile!!.isFile) {
                        // 下载完毕,安装 Apk
                        installApk()
                    } else {
                        // 下载失败,重新下载
                        downloadApk()
                    }
                } else if (!downloading) {
                    // 没有下载,开启下载
                    downloadApk()
                }
            }
        }

        /**
         * 下载 Apk
         */
        @CheckNet
        @Permissions(Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE, Permission.REQUEST_INSTALL_PACKAGES)
        private fun downloadApk() {
            // 设置对话框不能被取消
            setCancelable(false)
            val notificationManager = getSystemService(NotificationManager::class.java)
            val notificationId = getContext().applicationInfo.uid
            var channelId = ""
            // 适配 Android 8.0 通知渠道新特性
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val channel = NotificationChannel(getString(R.string.update_notification_channel_id),
                    getString(R.string.update_notification_channel_name), NotificationManager.IMPORTANCE_LOW)
                channel.enableLights(false)
                channel.enableVibration(false)
                channel.vibrationPattern = longArrayOf(0)
                channel.setSound(null, null)
                notificationManager.createNotificationChannel(channel)
                channelId = channel.id
            }
            val notificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(getContext(), channelId)
                // 设置通知时间
                .setWhen(System.currentTimeMillis())
                // 设置通知标题
                .setContentTitle(getString(R.string.app_name))
                // 设置通知小图标
                .setSmallIcon(R.mipmap.logo)
                // 设置通知大图标
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.logo))
                // 设置通知静音
                .setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE)
                // 设置震动频率
                .setVibrate(longArrayOf(0))
                // 设置声音文件
                .setSound(null)
                // 设置通知的优先级
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)

            // 创建要下载的文件对象
            apkFile = File(
                context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
                getString(R.string.app_name) + "_v" + nameView?.text.toString() + ".apk")

            EasyHttp.download(getDialog())
                .method(HttpMethod.GET)
                .file(apkFile)
                .url(downloadUrl)
                .md5(fileMd5)
                .listener(object : OnDownloadListener {
                    override fun onStart(file: File?) {
                        // 标记为下载中
                        downloading = true
                        // 标记成未下载完成
                        downloadComplete = false
                        // 后台更新
                        closeView?.visibility = View.GONE
                        // 显示进度条
                        progressView?.visibility = View.VISIBLE
                        updateView?.setText(R.string.update_status_start)
                    }

                    override fun onProgress(file: File, progress: Int) {
                        updateView?.text = String.format(getString(R.string.update_status_running)!!, progress)
                        progressView?.progress = progress
                        // 更新下载通知
                        notificationManager.notify(
                            notificationId, notificationBuilder
                                // 设置通知的文本
                                .setContentText(String.format(getString(R.string.update_status_running)!!, progress))
                                // 设置下载的进度
                                .setProgress(100, progress, false)
                                // 设置点击通知后是否自动消失
                                .setAutoCancel(false)
                                // 是否正在交互中
                                .setOngoing(true)
                                // 重新创建新的通知对象
                                .build()
                        )
                    }

                    override fun onComplete(file: File) {
                        // 显示下载成功通知
                        notificationManager.notify(
                            notificationId, notificationBuilder
                                // 设置通知的文本
                                .setContentText(String.format(getString(R.string.update_status_successful)!!, 100))
                                // 设置下载的进度
                                .setProgress(100, 100, false)
                                // 设置通知点击之后的意图
                                .setContentIntent(PendingIntent.getActivity(getContext(), 1, getInstallIntent(), Intent.FILL_IN_ACTION))
                                // 设置点击通知后是否自动消失
                                .setAutoCancel(true)
                                // 是否正在交互中
                                .setOngoing(false)
                                .build()
                        )
                        updateView?.setText(R.string.update_status_successful)
                        // 标记成下载完成
                        downloadComplete = true
                        // 安装 Apk
                        installApk()
                    }

                    override fun onError(file: File, e: Exception) {
                        // 清除通知
                        notificationManager.cancel(notificationId)
                        updateView?.setText(R.string.update_status_failed)
                        // 删除下载的文件
                        file.delete()
                    }

                    override fun onEnd(file: File) {
                        // 更新进度条
                        progressView?.progress = 0
                        progressView?.visibility = View.GONE
                        // 标记当前不是下载中
                        downloading = false
                        // 如果当前不是强制更新,对话框就恢复成可取消状态
                        if (!forceUpdate) {
                            setCancelable(true)
                        }
                    }
                }).start()
        }

        /**
         * 安装 Apk
         */
        @Permissions(Permission.REQUEST_INSTALL_PACKAGES)
        private fun installApk() {
            getContext().startActivity(getInstallIntent())
        }

        /**
         * 获取安装意图
         */
        private fun getInstallIntent(): Intent {
            val intent = Intent()
            intent.action = Intent.ACTION_VIEW
            val uri: Uri?
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                uri = FileProvider.getUriForFile(getContext(), AppConfig.getPackageName() + ".provider", apkFile!!)
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
            } else {
                uri = Uri.fromFile(apkFile)
            }
            intent.setDataAndType(uri, "application/vnd.android.package-archive")
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            return intent
        }
    }
}

页面XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="260dp"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    app:cardBackgroundColor="@color/white"
    app:cardCornerRadius="5dp"
    tools:context=".ui.windows.dialog.UpdateDialog">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <androidx.appcompat.widget.AppCompatImageView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginHorizontal="@dimen/dp_m_1"
                android:adjustViewBounds="true"
                app:srcCompat="@mipmap/update_app_top_bg"
                android:layout_marginTop="-15dp"
                />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginHorizontal="@dimen/dp_30"
                android:layout_marginBottom="@dimen/dp_5"
                android:orientation="vertical">


                <androidx.appcompat.widget.AppCompatTextView
                    android:id="@+id/tv_update_name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="@dimen/dp_10"
                    android:layout_marginTop="@dimen/dp_5"
                    android:textColor="@color/white"
                    android:textSize="@dimen/sp_20"
                    tools:text="3.2.1" />

            </LinearLayout>

        </FrameLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <androidx.appcompat.widget.AppCompatTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginHorizontal="@dimen/dp_20"
                android:text="@string/update_content"
                android:textColor="@color/black"
                android:textSize="@dimen/sp_17"
                android:textStyle="bold" />

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_update_details"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginHorizontal="@dimen/dp_20"
                android:layout_marginVertical="@dimen/dp_10"
                android:lineSpacingExtra="@dimen/dp_5"
                android:maxLines="4"
                android:minLines="3"
                android:scrollbars="vertical"
                android:textColor="@color/black60"
                android:textSize="@dimen/sp_15"
                tools:text="6\n66\n666\n6666\n66666" />

            <ProgressBar
                android:id="@+id/pb_update_progress"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginHorizontal="@dimen/dp_20"
                android:layout_marginTop="@dimen/dp_3"
                android:indeterminate="false"
                android:visibility="gone"
                tools:progress="50"
                tools:visibility="visible" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <androidx.appcompat.widget.AppCompatTextView
                    android:id="@+id/tv_update_close"
                    android:layout_width="0px"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:background="@drawable/transparent_selector"
                    android:focusable="true"
                    android:gravity="center"
                    android:paddingVertical="@dimen/dp_15"
                    android:text="@string/update_no"
                    android:textColor="@color/black40"
                    android:textSize="@dimen/sp_14" />

                <androidx.appcompat.widget.AppCompatTextView
                    android:id="@+id/tv_update_update"
                    android:layout_width="0px"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:background="@drawable/transparent_selector"
                    android:focusable="true"
                    android:gravity="center"
                    android:paddingVertical="@dimen/dp_15"
                    android:text="@string/update_yes"
                    android:textColor="@color/black60"
                    android:textSize="@dimen/sp_15" />
            </LinearLayout>

        </LinearLayout>

    </LinearLayout>

</androidx.cardview.widget.CardView>
有一些是资源文件需要自己替换 自己的照片之类的 还有 很多设置 dp 都是抽出来的 也需要自己主要一下

1.重点讲解 1.这个网络请求用的EasyHttp 需要自己 去找引入依赖

    implementation 'com.github.getActivity:EasyHttp:11.2'

2.EasyHttp 使用的时候需要初始化

3.下载完成后 获取安装意图

/**
         * 获取安装意图
         */
        private fun getInstallIntent(): Intent {
            val intent = Intent()
            intent.action = Intent.ACTION_VIEW
            val uri: Uri?
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
			/** 这里 AppConfig.getPackageName()  是当前APP 的 包名 如果不正确 会导致安装失败 应用闪退

*/
                uri = FileProvider.getUriForFile(getContext(), AppConfig.getPackageName() + ".provider", apkFile!!)
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
            } else {
                uri = Uri.fromFile(apkFile)
            }
            intent.setDataAndType(uri, "application/vnd.android.package-archive")
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            return intent
        }

应用还有一些代码没有提供完整 如果需要的话可以查看github 连接获取完整项目代码 github.com/getActivity…