如何防止 PendingIntent 在 Android 中创建新的 activity

How to prevent PendingIntent from creating a new activity in Android

我正在制作一个 APK 文件下载屏幕,我在屏幕上显示进度,即文件下载的百分比,而且我还收到使用前台服务的通知,因为文件下载必须从服务下载.下载过程中,用户可以离开应用,也可以通过通知返回我的应用。但问题是,为了实现这个逻辑,我使用 PendingIntent 并且当用户单击我的应用程序的通知时,它会重新创建应用程序而不是返回到之前的屏幕。我不知道为什么。请检查下面我的代码。

文件下载片段

 class FileDownloadFragment(private val uri: String) : DialogFragment() {

 ...

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     super.onViewCreated(view, savedInstanceState)
     startDownloadService()
 }

 private fun startDownloadService() {
     val downloadService = AppUpdateAPKDownloadService()
     val startIntent = Intent(context, downloadService::class.java)
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
         context.startForegroundService(startIntent)
     } else {
         context.startService(startIntent)
     }
 }

 ...

}

前台服务

  class AppUpdateAPKDownloadService: LifecycleService() {

  ...

  /** Dispatcher */
  private val ioDispatcher = Dispatchers.IO

  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
      super.onStartCommand(intent, flags, startId)
      if (intent != null) {
          createNotificationChannel()
          val notification = getNotification()
          startForeground(NOTIFICATION_ID, notification)

          lifecycleScope.launch{
              downloadAPK(intent.getStringExtra(UPDATE_APK_URI).toString())
          }
      }

      return START_NOT_STICKY
  }

  private fun createNotificationChannel() {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
          val channel = NotificationChannel(
              CHANNEL_ID,
               CHANNEL_NAME,
              NotificationManager.IMPORTANCE_DEFAULT
          ).apply {
              enableVibration(true)
              enableLights(true)
          }
          val manager = this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        manager.createNotificationChannel(channel)
      }
  }

  private fun getNotification(): Notification {
      val intent = Intent(applicationContext, SplashActivity::class.java)
      intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
      val pendingIntent = TaskStackBuilder.create(applicationContext).run {
          addNextIntentWithParentStack(intent)
          getPendingIntent(APP_UPDATE_PENDING_INTENT_REQUEST_CODE, PendingIntent.FLAG_IMMUTABLE)
      }

      // val pendingIntent = PendingIntent.getActivity(
      //     applicationContext,
      //     APP_UPDATE_PENDING_INTENT_REQUEST_CODE,
      //     intent,
      //     PendingIntent.FLAG_IMMUTABLE
      //     )

      val notification = NotificationCompat.Builder(this, CHANNEL_ID).apply {
          setContentTitle("DownloadApp")
          setContentText("Downloading...")
          setSmallIcon(R.drawable.icon)
       setLargeIcon(BitmapFactory.decodeResource(this@AppUpdateAPKDownloadService.applicationContext.resources, R.mipmap.icon))
          priority = NotificationCompat.PRIORITY_HIGH
          setContentIntent(pendingIntent)
          setOngoing(true)
          setAutoCancel(true)
       }.build()

      val notificationManager = NotificationManagerCompat.from(this)
      notificationManager.notify(NOTIFICATION_ID, notification)

      return notification
  }

  private suspend fun downloadAPK(uri: String) = withContext(ioDispatcher) {
      kotlin.runCatching {
          URL(uri)
      }.onSuccess { url ->
          val connection: URLConnection = url.openConnection()
          connection.connect()

          val fileFullSize = connection.contentLength

          val directory = applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
          directory?.let {
              if (!directory.exists()) directory.mkdirs()
          }

          val inputDataStream = DataInputStream(BufferedInputStream(url.openStream(), fileFullSize))
          val file = File(directory, "my_app.apk")
          val outputDataStream = DataOutputStream(BufferedOutputStream(FileOutputStream(file), fileFullSize))
          processDownload(
              inputStream = inputDataStream,
              outputStream = outputDataStream,
              fileFullSize = fileFullSize
          )

          val bundle = Bundle().apply {
              putInt(UPDATE_APK_RECEIVER_MODE, UPDATE_APK_DOWNLOAD_COMPLETE)
          }
          uiUpdateReceiver?.send(Activity.RESULT_OK, bundle)
      }.onFailure {
         
      }
  }

  private fun processDownload(
      inputStream: DataInputStream,
      outputStream: DataOutputStream,
      fileFullSize: Int
  ) {
      val data = ByteArray(fileFullSize)
      var downloadSize: Long = 0
      var count: Int

      while (inputStream.read(data).also { count = it } != -1) {
          downloadSize += count
          val percent = (downloadSize.toFloat() / fileFullSize) * 100
          Log.d("TEST", "writing...$percent% in Service")
          val bundle = Bundle().apply {
              putInt(UPDATE_APK_RECEIVER_MODE, UPDATE_APK_UI_UPDATE)
              putFloat(UPDATE_API_UI_PERCENT, percent)
          }
          uiUpdateReceiver?.send(Activity.RESULT_OK, bundle)
          outputStream.write(data, 0, count)
      }

      outputStream.flush()
      outputStream.close()
      inputStream.close()
  }

  ...
}



我还在 AndroidManifest.xml 中将 android:launchMode="singleTop" 添加到 SplashActivity 但它仍然无法正常工作...
我犯了什么错误?

好的,我解决了这个问题。这是3个步骤。

  1. 使用 PendingIntent.getActivity() 而不是 TaskStackBuilder.create(applicationContext)

  2. 如果您的目标是 Android M(API23) 或更高版本,则使用 PendingIntent.FLAG_IMMUTABLE,如果您的目标是 Android Lollipop(API21) 或更高版本,则使用 PendingIntent.FLAG_UPDATE_CURRENT下面。

  3. 将这些标志 Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP 添加到 Intent。

TaskStackBuilder 将始终重新创建活动。这就是它的设计方式。如果您想 return 到现有任务,您不想使用它。

不要使用您的代码来创建 PendingIntent,而是使用:

val intent = PackageManager.getLaunchIntentForPackage("your.package.name")
val pendingIntent = PendingIntent.getActivity(applicationContext,
            APP_UPDATE_PENDING_INTENT_REQUEST_CODE,
            intent,
            PendingIntent.FLAG_IMMUTABLE)

这只会导致您现有的任务以其上次处于的任何状态被带到前台。如果您的应用不是 运行,这将启动您的应用,就像您点击主屏幕上的应用图标一样。