2019-05-30 通知栏显示APP更新进度,兼容安卓8.0

参考样式

1.APP通知栏代码

package com.aimymusic.android.widget.update

import android.app.*
import android.content.Context
import android.content.Intent
import android.os.Build
import android.support.v4.app.NotificationCompat
import android.text.format.Formatter
import android.widget.RemoteViews
import com.aimymusic.android.R
import com.aimymusic.android.constants.IntConstants
import com.aimymusic.android.constants.StringConstants
import com.aimymusic.android.widget.file.AppFileDirManager
import com.aimymusic.android.widget.filedownload.AimyFileDownload
import com.aimymusic.browse.download.MD5Utils
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.Utils
import org.jetbrains.anko.runOnUiThread
import java.util.Locale

/**
 * Description:
 * @author: caiyoufei
 * @date: 19-5-30 下午12:06
 */
class AimyUpdateService : IntentService("UpdateService") {
  companion object {
    const val DOWNLOAD_PATH = "download_path"
    const val DOWNLOAD_VERSION_NAME = "download_version_name"
    const val DOWNLOAD_SHOW_NOTIFICATION = "download_show_notification"
    fun startIntent(
      path: String,
      version_name: String = "",
      showNotification: Boolean = false
    ) {
      val intent = Intent(Utils.getApp(), AimyUpdateService::class.java)
      intent.putExtra(DOWNLOAD_PATH, path)
      intent.putExtra(DOWNLOAD_VERSION_NAME, version_name)
      intent.putExtra(DOWNLOAD_SHOW_NOTIFICATION, showNotification)
      Utils.getApp()
          .startService(intent)
    }
  }

  //下载的百分比
  private var mPercent = 0f
  //通知管理
  private var mNotificationManager: NotificationManager? = null
  //通知创建类
  private var mBuilder: NotificationCompat.Builder? = null
  //上次的下载量(方便计算下载速度)
  private var mLastBytes: Long = 0
  //上次的时间
  private var mLastTime: Long = 0
  //下载速度
  private var mSpeed: Long = 0
  //通知栏数据设置
  private var mRemoteViews: RemoteViews? = null
  //显示下载更新的版本名称
  private var mVerName: String? = null
  //文件下载保存的文件夹
  private val mFileDir = AppFileDirManager.getDownloadFileDir(Utils.getApp())
  //是否显示通知栏
  private var needShowNotification = false
  //总大小
  private var mTtotalSize = 0L
  //apk下载地址
  private var mApkUrl = ""
  //apk下载的版本
  private var mApkVersion = ""
  //app名称
  private val appName = AppUtils.getAppName()
  //是否正在下载
  private var isDownloading = false
  //渠道id 安卓8.0 https://blog.csdn.net/MakerCloud/article/details/82079498
  private val UPDATE_CHANNEL_ID = AppUtils.getAppPackageName() + ".update.channel.id"
  private val UPDATE_CHANNEL_NAME = AppUtils.getAppPackageName() + ".update.channel.name"

  override fun onHandleIntent(intent: Intent?) {
    if (mNotificationManager == null) {
      mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(
            UPDATE_CHANNEL_ID, UPDATE_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW//等级太高会一直响
        )
        channel.setSound(null, null)
        mNotificationManager?.createNotificationChannel(channel)
      }
    }
    if (isDownloading) return
    intent?.let {
      isDownloading = true
      mApkUrl = it.getStringExtra(DOWNLOAD_PATH)
      val downloadUrl: String = mApkUrl
      mApkVersion = it.getStringExtra(DOWNLOAD_VERSION_NAME)
      val versionName = mApkVersion
      needShowNotification = it.getBooleanExtra(DOWNLOAD_SHOW_NOTIFICATION, false)
      val downLoadName = MD5Utils.MD5(downloadUrl)
      mVerName = if (versionName.isEmpty() || versionName == "最新版本") "最新版本" else versionName
      AimyFileDownload.downloadFile(
          url = downloadUrl,
          path = mFileDir,
          isReDownloadIfCompleted = false,
          fileName = downLoadName,
          start = { showNotification() },
          progress = { _, offsetSize, totalSize -> updateProgress(offsetSize, totalSize) },
          success = { _, file ->
            isDownloading = false
            file?.let { f ->
              showSuccess(f.path)
              AppUtils.installApp(f.path)
            }
          },
          error = { url, _ ->
            isDownloading = false
            showFail(url)
          }
      )
    }
  }

  //显示下载通知
  private fun showNotification() {
    //发送进度
    runOnUiThread { UpdateLiveData.setProgress(0f) }
    if (needShowNotification) {
      initRemoteViews()
      mRemoteViews?.let {
        it.setTextViewText(R.id.notice_update_title, "正在下载$appName$mVerName")
        it.setTextViewText(R.id.notice_update_percent, "0%")
        it.setProgressBar(R.id.notice_update_progress, 100, 0, false)
        it.setTextViewText(R.id.notice_update_speed, "")
        it.setTextViewText(R.id.notice_update_size, "")
        initBuilder(it)
        mNotificationManager?.notify(IntConstants.NOTIFICATION.NOTICE_UPDATE_ID, mBuilder?.build())
      }
    }
  }

  //每次刷新的时间间隔
  private val interval = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 500 else 250

  //更新下载进度
  private fun updateProgress(
    offsetSize: Long,
    totalSize: Long
  ) {
    mTtotalSize = totalSize
    mPercent = offsetSize * 100f / totalSize
    //发送进度
    runOnUiThread { UpdateLiveData.setProgress(Math.min(99.9f, mPercent)) }
    if (mLastTime == 0L || mLastBytes == 0L) {
      mSpeed = offsetSize / 1000
      mLastTime = System.currentTimeMillis()
      mLastBytes = offsetSize
    } else if (System.currentTimeMillis() - mLastTime >= interval || offsetSize > totalSize) {
      mSpeed = (offsetSize - mLastBytes) / (System.currentTimeMillis() - mLastTime) * 1000
      mLastTime = System.currentTimeMillis()
      mLastBytes = offsetSize
    } else {
      return
    }
    if (needShowNotification) {
      mRemoteViews?.let {
        it.setProgressBar(R.id.notice_update_progress, 100, mPercent.toInt(), false)
        val progress = String.format(Locale.getDefault(), "%.1f", mPercent) + "%"
        it.setTextViewText(R.id.notice_update_percent, progress)
        val speedStr = Formatter.formatFileSize(Utils.getApp(), mSpeed) + "/s"
        it.setTextViewText(R.id.notice_update_speed, speedStr)
        val curSizeStr = Formatter.formatFileSize(Utils.getApp(), offsetSize)
        val totalSizeStr = Formatter.formatFileSize(Utils.getApp(), totalSize)
        it.setTextViewText(R.id.notice_update_size, "$curSizeStr/$totalSizeStr")
        mNotificationManager?.notify(IntConstants.NOTIFICATION.NOTICE_UPDATE_ID, mBuilder?.build())
      }
    }
  }

  //下载成功
  private fun showSuccess(filePath: String) {
    //发送进度
    runOnUiThread { UpdateLiveData.setProgress(100f) }
    if (needShowNotification) {
      initRemoteViews()
      mRemoteViews?.let {
        if (mTtotalSize == 0L) mTtotalSize = File(filePath).length()
        it.setTextViewText(R.id.notice_update_title, "$appName${mVerName}下载完成,点击安装")
        it.setProgressBar(R.id.notice_update_progress, 100, 100, false)
        it.setTextViewText(R.id.notice_update_percent, "100%")
        val totalSizeStr = Formatter.formatFileSize(Utils.getApp(), mTtotalSize)
        it.setTextViewText(R.id.notice_update_size, "$totalSizeStr/$totalSizeStr")
        val intentInstall = Intent(Utils.getApp(), NotificationBroadcastReceiver::class.java)
        intentInstall.action = StringConstants.Update.INTENT_KEY_INSTALL_APP
        intentInstall.putExtra(StringConstants.Update.INTENT_KEY_INSTALL_PATH, filePath)
        it.setOnClickPendingIntent(
            R.id.notice_update_layout,
            PendingIntent.getBroadcast(
                Utils.getApp(), 0, intentInstall,
                PendingIntent.FLAG_CANCEL_CURRENT
            )
        )
        initBuilder(it)
        mBuilder?.setOngoing(false)
        mNotificationManager?.notify(IntConstants.NOTIFICATION.NOTICE_UPDATE_ID, mBuilder?.build())
      }
    }
  }

  //更新失败
  private fun showFail(path: String) {
    //发送进度
    runOnUiThread { UpdateLiveData.setProgress(-1f) }
    if (needShowNotification) {
      initRemoteViews()
      mRemoteViews?.let {
        it.setTextViewText(R.id.notice_update_title, "$appName${mVerName}下载失败,点击重试")
        val intentInstall = Intent(Utils.getApp(), NotificationBroadcastReceiver::class.java)
        intentInstall.action = StringConstants.Update.INTENT_KEY_APK_DOWNLOAD_ERROR
        intentInstall.putExtra(StringConstants.Update.INTENT_KEY_RETRY_PATH, mApkUrl)
        intentInstall.putExtra(StringConstants.Update.INTENT_KEY_RETRY_NAME, mApkVersion)
        it.setOnClickPendingIntent(
            R.id.notice_update_layout,
            PendingIntent.getBroadcast(
                Utils.getApp(), 0, intentInstall,
                PendingIntent.FLAG_CANCEL_CURRENT
            )
        )
        initBuilder(it)
        mBuilder?.setOngoing(false)
        mNotificationManager?.notify(IntConstants.NOTIFICATION.NOTICE_UPDATE_ID, mBuilder?.build())
      }
    }
  }

  //初始化RemoteViews
  private fun initRemoteViews() {
    if (mRemoteViews == null) { //防止直接走失败
      mRemoteViews = RemoteViews(packageName, R.layout.notification_update)
      mRemoteViews?.setImageViewResource(R.id.notice_update_icon, R.mipmap.ic_launcher)
    }
  }

  //初始化Builder
  private fun initBuilder(remoteViews: RemoteViews) {
    if (mBuilder == null) {
      mBuilder = NotificationCompat.Builder(Utils.getApp(), UPDATE_CHANNEL_ID)
          .setWhen(System.currentTimeMillis())
          .setDefaults(Notification.FLAG_AUTO_CANCEL)
          .setSound(null)
          .setOngoing(true)
          .setAutoCancel(true)
          .setContent(remoteViews)
          .setSmallIcon(R.drawable.ic_notification)
    }
  }
}

2.notification_update.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#fff"
    >
  <FrameLayout
      android:id="@+id/notice_update_layout"
      android:layout_width="match_parent"
      android:layout_gravity="center_vertical"
      android:layout_height="64dp"
      >
    <!--APP图标-->
    <ImageView
        android:id="@+id/notice_update_icon"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:contentDescription="@null"
        android:src="@mipmap/ic_launcher"
        />

    <!--下载进度ProgressBar-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginEnd="6dp"
        android:layout_marginStart="70dp"
        android:background="@drawable/shape_notification"
        >

      <ProgressBar
          android:id="@+id/notice_update_progress"
          android:layout_width="match_parent"
          android:layout_height="5dp"
          android:progress="70"
          android:progressDrawable="@drawable/progressbar_update"
          style="@style/Widget.AppCompat.ProgressBar.Horizontal"
          />
    </LinearLayout>
    <!--下载提示-->
    <TextView
        android:id="@+id/notice_update_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="40dp"
        android:layout_marginStart="70dp"
        android:layout_marginTop="5dp"
        android:includeFontPadding="false"
        android:singleLine="true"
        android:textColor="#000"
        android:textSize="12sp"
        tools:text="正在下载咪哒音乐:4.0.0"
        />
    <!--下载百分比-->
    <TextView
        android:id="@+id/notice_update_percent"
        android:layout_width="34dp"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:layout_marginEnd="6dp"
        android:layout_marginTop="5dp"
        android:gravity="end"
        android:includeFontPadding="false"
        android:singleLine="true"
        android:textColor="#000"
        android:textSize="12sp"
        tools:text="70%"
        />
    <!--下载速度-->
    <TextView
        android:id="@+id/notice_update_speed"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_marginBottom="5dp"
        android:layout_marginEnd="120dp"
        android:layout_marginStart="70dp"
        android:gravity="start"
        android:includeFontPadding="false"
        android:minWidth="38dp"
        android:textColor="#000"
        android:textSize="12sp"
        tools:text="下载速度:876K/S"
        />
    <!--下载的大小/总大小-->
    <TextView
        android:id="@+id/notice_update_size"
        android:layout_width="114dp"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_marginBottom="5dp"
        android:layout_marginEnd="6dp"
        android:layout_marginStart="70dp"
        android:gravity="end"
        android:includeFontPadding="false"
        android:minWidth="38dp"
        android:textColor="#000"
        android:textSize="12sp"
        tools:text="999.8K/46.5M"
        />
  </FrameLayout>
</FrameLayout>

3.progressbar_update.xml

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@android:id/background">
    <shape>
      <corners android:radius="5dip"/>
      <solid android:color="@color/white_30"/>
    </shape>
  </item>

  <item android:id="@android:id/secondaryProgress">
    <clip>
      <shape>
        <corners android:radius="5dip"/>
        <solid android:color="#30D18B"/>
      </shape>
    </clip>
  </item>

  <item android:id="@android:id/progress">
    <clip>
      <shape>
        <corners android:radius="5dip"/>
        <solid android:color="#30D18B"/>
      </shape>
    </clip>
  </item>

</layer-list>

4.NotificationBroadcastReceiver

package com.aimymusic.android.widget.update

import android.app.NotificationManager
import android.content.*
import android.widget.Toast
import com.aimymusic.ambase.widget.toast.AmToast
import com.aimymusic.android.constants.IntConstants
import com.aimymusic.android.constants.StringConstants
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.NetworkUtils
import java.lang.reflect.Method

/**
 * description:
 *
 * @author : case
 * @date : 2019/5/30  16:02 创建
 */
class NotificationBroadcastReceiver : BroadcastReceiver() {

  override fun onReceive(
    context: Context,
    intent: Intent
  ) {
    intent.action?.let {
      when (it) {
        //安装
        StringConstants.Update.INTENT_KEY_INSTALL_APP -> {
          collapsingNotification(context)
          (context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
              .cancel(IntConstants.NOTIFICATION.NOTICE_UPDATE_ID)
          val path = intent.getStringExtra(StringConstants.Update.INTENT_KEY_INSTALL_PATH)
          AppUtils.installApp(path)
        }
        //重试
        StringConstants.Update.INTENT_KEY_APK_DOWNLOAD_ERROR -> if (checkNetWork(context)) {
          val apkUrl = intent.getStringExtra(StringConstants.Update.INTENT_KEY_RETRY_PATH)
          val mApkVersion = intent.getStringExtra(StringConstants.Update.INTENT_KEY_RETRY_NAME)
          AimyUpdateService.startIntent(apkUrl, mApkVersion, true)
        }
      }
    }
  }

  //检查网络是否可用(点击的时候调用)
  private fun checkNetWork(context: Context): Boolean {
    val connected = NetworkUtils.isConnected()
    if (!connected) {
      AmToast.showBottomToast(context, "当前网络不可用", Toast.LENGTH_SHORT)
    }
    return connected
  }

  /**
   * 折叠通知栏
   */
 private fun collapsingNotification(context: Context) {
    try {
      val service = context.getSystemService("statusbar") ?: return
      val clazz = Class.forName("android.app.StatusBarManager")
      val sdkVersion = android.os.Build.VERSION.SDK_INT
      val collapse: Method
      if (sdkVersion <= 16) {
        collapse = clazz.getMethod("collapse")
      } else {
        collapse = clazz.getMethod("collapsePanels")
      }
      collapse.isAccessible = true
      collapse.invoke(service)
    } catch (e: Exception) {
      e.printStackTrace()
    }

  }
}

5.UpdateLiveData

package com.aimymusic.android.widget.update

import android.arch.lifecycle.*

/**
 * Description:
 * @author: caiyoufei
 * @date: 19-5-30 下午3:16
 */
object UpdateLiveData {
  //下载中1-99 下载成功100  下载失败-1
  private var updateProgress = MutableLiveData<Float>()

  fun setProgress(progress: Float) {
    updateProgress.value = progress
  }

  fun getProgress(): Float {
    return updateProgress.value ?: -1f
  }

  fun subscribeProgress(
    owner: LifecycleOwner,
    observer: Observer<Float>
  ) {
    unSubscribeProgress(owner)
    updateProgress.observe(owner, observer)
  }

  fun unSubscribeProgress(owner: LifecycleOwner) {
    updateProgress.removeObservers(owner)
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339