【问题标题】:Android 'BadTokenException window token android.os.BinderProxy@4250d6d8 is not valid' with foreground service runningAndroid 'BadTokenException 窗口令牌 android.os.BinderProxy@4250d6d8 无效',前台服务正在运行
【发布时间】:2021-06-15 18:19:54
【问题描述】:

尝试使用 kotlin 构建一个 android 应用程序,我在其中连接、发送和读取 BLE(蓝牙低功耗)设备的数据。我设计了一个活动,它将向我显示连接统计信息和从 BLE 设备接收到的数据。我有一个前台服务正在运行,以保持与 BLE 设备的连接处于活动状态并收听统计信息。 我已使用待处理意图从我的前台服务通知中打开此活动。

以下代码展示了创建通知的方法

private fun getServiceNotification(textToShowInNotification: String)
{
        val pendingIntent = Intent(<Static context from application class>,ActivityName::class.java)
        pendingIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
        val contentIntent = PendingIntent.getActivity(<static_context_from_application_class>, 0, pendingIntent, 0)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        {
            val notificationChannel = NotificationChannel("DATA_CHANNEL", <Static context from application class>.resources.getString(R.string.app_name),NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager!!.createNotificationChannel(notificationChannel)
        }

        notification = notificationBuilder!!.setOngoing(true)
            .setOnlyAlertOnce(true)
            .setContentText(textToShowInNotification)
            .setContentIntent(contentIntent)
            .build()
        notificationManager!!.notify(NOTIFICATION_ID, notification)
}

因为我有一个用例来打开显示 BLE 连接和数据接收的活动。 Activity 的多个实例被创建并且之前的一次没有被破坏,因此我无法获取正在运行的 Activity 的上下文,因此我在 Activity 中显示警报对话框时遇到了问题。

实际用例 - 当我终止应用程序并从前台服务通知打开它时,当建立与设备的连接并从服务监听数据时,必须显示的对话框说我的设备仍然连接并且正在接收的数据不显示,显示对话框时出现异常

尝试显示警报对话框时出现以下错误/异常

D/DialogExecption: Unable to add window -- token null is not valid; is your activity running?
D/DialogExecption: Unable to add window token android.os.BinderProxy@4250d6d8 is not valid; is your activity running?

我使用下面的代码来显示对话框

private fun showAlertDialog()
{
    val dialogAlertDialog = AlertDialog.Builder(<Activity Context>)
    val inflater: LayoutInflater = layoutInflater
    val dialogAlertView: View = inflater.inflate(R.layout.activity_xml_file,null)
    dialogAlertDialog.setView(dialogAlertView)
    dialogAlertDialog.setCancelable(false)

    val builderAlertDialog : AlertDialog = dialogAlertDialog.create()
    try
    {
        builderAlertDialog.show()
    }
    catch(exception: Exception)
    {
        Log.d("DialogExecption",""+exception.message)
    }
}

我也试过方法

if (!this.isFinishing)
{
    builderAlertDialogCycleCancelled.show()
}

但这也无济于事。上面的代码会抑制执行,但我不想这样做,而是想不惜一切代价显示对话框。

为了提供一个 pov,我在我的清单文件中尝试了以下内容,以保持活动的单个实例成为可能,但这并不奏效。

<activity android:name=".ActivityName"
        android:alwaysRetainTaskState="true"
        android:launchMode="singleTop"
        android:screenOrientation="portrait"
        android:theme="@style/AppThemeGeneral">

</activity>

我必须以最好的方式使用警报对话框,跳过它不是一个选择。 任何帮助都会很棒。

重要提示 - 我在我的活动中使用范围为 Dispatchers.IO 的协程从 BLE 设备获取数据。

【问题讨论】:

    标签: android kotlin android-activity android-service android-alertdialog


    【解决方案1】:

    观察

        notification = notificationBuilder!!.setOngoing(true)
            .setOnlyAlertOnce(true)
            .setContentText(textToShowInNotification)
            .setContentIntent(contentIntent)
            .build()
        notificationManager!!.notify(NOTIFICATION_ID, notification)
    

    单击此通知可能无法启动Activity,因为notification 缺少setSmallIcon 或类似的setIcon 呼叫。要修复它,请设置一些图标,如下所示:

         notification = notificationBuilder!!.setOngoing(true)
             .setOnlyAlertOnce(true)
             .setContentText(textToShowInNotification)
             .setContentIntent(contentIntent)
             .setSmallIcon(R.drawable.ic_launcher_background) 
             .build()
         notificationManager!!.notify(NOTIFICATION_ID, notification)
    

    之前的活动没有被破坏

    这是值得怀疑的,因为您在启动 Activity 时使用了标志 Intent.FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TASK,这将完成之前的所有活动。否则,请出示相关代码。

    与设备建立连接并监听数据时 当我杀死应用程序并从中打开它时,从服务中 前台服务通知,必须显示的对话框 说我的设备仍然连接并且正在接收数据是 未显示

    这听起来很混乱。听起来你是从Activity 显示它,但它不太可能失败,特别是如果你从ActivityonCreate 显示它:

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        showAlertDialog()
    }
    

    D/DialogExecption: 无法添加窗口——token null 无效;是 你的活动正在运行吗? D/DialogExecption:无法添加窗口令牌 android.os.BinderProxy@4250d6d8 无效;是你的活动 跑步吗?

    错误消息非常清楚 - 您正在尝试显示基于无效Context 此处AlertDialog.Builder(&lt;context&gt;) 的对话框。例如,如果你在那里使用applicationContextService context,它将失败并出现此类异常。

    您声称以Activity 上下文开始对话,如下所示:

    ...
    val dialogAlertDialog = AlertDialog.Builder(<Activity Context>)
    ...
    

    但是,从您的代码中并不清楚从哪里调用 showAlertDialog(),并且也没有显示上下文对象。因此,我创建了一个示例项目来测试所描述的行为。

    建议

    准备

    我尝试通过根据您在问题中提供的信息构建一个简约项目来重现该问题。 请注意,我在此示例中没有使用任何 BLE 功能,即使 蓝牙 被用作每个组件的前缀

    我创建了一个前台服务BluetoothDeviceService,负责在点击通知时启动Activity

    蓝牙设备服务.kt

    class BluetoothDeviceService: Service() {
        private val SERVICE_NOTIFICATION_ID = 123
        private val SERVICE_NOTIFICATION_CHANNEL_ID = "channel_01"
    
        override fun onCreate() {
            super.onCreate()
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val channel = NotificationChannel(
                    SERVICE_NOTIFICATION_CHANNEL_ID,
                    "Bluetooth service",
                    NotificationManager.IMPORTANCE_DEFAULT)
                val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
                notificationManager.createNotificationChannel(channel)
    
                val pendingIntent = Intent()
                pendingIntent.setClass(this,BluetoothDeviceActivity::class.java)
                pendingIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
                val contentIntent = PendingIntent.getActivity(this, 0,
                    pendingIntent, 0)
    
                val notification = NotificationCompat.Builder(this, SERVICE_NOTIFICATION_CHANNEL_ID)
                    .setOnlyAlertOnce(true)
                    .setOngoing(true)
                    .setContentText("Bluetooth service running...")
                    .setContentIntent(contentIntent)
                    .setSmallIcon(R.drawable.ic_launcher_background)
                    .build()
                startForeground(SERVICE_NOTIFICATION_ID, notification)
            }
        }
    
        override fun onBind(intent: Intent?): IBinder? {
            TODO("Not yet implemented")
        }
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            return START_STICKY
        }
    }
    

    我创建了一个必须启动前台服务的MainActivity

    MainActivity.kt

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val startServiceIntent = Intent(this, BluetoothDeviceService::class.java)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(startServiceIntent)
            } else {
                startService(startServiceIntent)
            }
            finish()
        }
    }
    

    请注意Service 也可以由BroadcastReceiver 启动,这样会更合适,但为了简单起见,我使用了Activity

    另外,我介绍了一个BluetoothDeviceActivity,它是由服务在PendingIntent的帮助下启动的:

    BluetoothDeviceActivity.kt

    class BluetoothDeviceActivity: AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            setContentView(R.layout.activity_main)
            showAlertDialog()
        }
    }
    
    private fun showAlertDialog() {
        val dialogAlertDialog = AlertDialog.Builder(this)
            .setCancelable(false)
            .setMessage("This is a test")
            .setTitle("Information")
            .create()
    
        dialogAlertDialog.show()
    }
    

    以防万一,我也放了我的清单。

    AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.stackoverflowquestion2">
    
        <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    
        <application
            android:name=".MainApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.StackOverflowQuestion2"
            android:fullBackupContent="@xml/backup_descriptor">
            <activity
                android:name=".MainActivity"
                android:label="@string/app_name"
                android:theme="@style/Theme.StackOverflowQuestion2.NoActionBar">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN"/>
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
    
            <activity android:name=".BluetoothDeviceActivity"/>
    
            <service android:name=".BluetoothDeviceService"/>
        </application>
    
    </manifest>
    

    结果

    这按预期工作,没有任何问题。

    进一步的建议

    另一个想法 - 您可以将您的 AlertDialog 转换为 Activity 并将其用作 Dialog。为此,您需要做两件事:

    1. 如下创建一个新的Àctivity

      class AlertDialogActivity: AppCompatActivity() {
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
      
              val dialogAlertDialog = AlertDialog.Builder(this)
                  .setCancelable(false)
                  .setMessage("This is a test")
                  .setTitle("Information")
                  .setPositiveButton("OK") { dialog, which -> finish() }
                  .create()
      
              dialogAlertDialog.show()
          }
      }
      
    2. 将其添加到您的清单并将其主题设置为Dialog

      <activity android:name=".AlertDialogActivity" android:theme="@style/Theme.AppCompat.Dialog"/>
      
    3. 然后,在您的 Service 中创建一个方法,并在需要时随时使用它:

      private fun showAlertDialog() {
          val intent = Intent(applicationContext, AlertDialogActivity::class.java)
          intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
          applicationContext.startActivity(intent)
      }
      

    结果

    它看起来是这样的:

    【讨论】:

      【解决方案2】:

      您可以创建扩展 BroadcastReceiver 类的类。

      你的通知会触发 BroadcastReceiver 的 onReceive 方法。

      example怎么做

      一个moremore

      onReceive(context: Context!,  intent: Intent!)
      

      根据您的意图操作,您将调用在相应上下文中显示 AlertDialog 的方法

      【讨论】:

        【解决方案3】:

        您应该使用 ViewModel。为您的活动创建一个 MainViewModel,在此 ViewModel 中您创建一个 MutableLiveData:isShowDialog。当你想显示一个对话框时,你将调用 MainViewModel 的一个方法来更新 isShowDialog 变量的值(true)。在 MainActivity 中,你会观察 isShowDialog 变量,如果它是 true,你调用该方法来显示一个对话框。 ViewModel 将确保在您的活动准备就绪时调用显示对话框的方法。

        【讨论】:

          【解决方案4】:

          是什么阻止你从 AlertDialog 转移到 Dialog 可以使用应用程序上下文而不是活动一或根本不需要上下文的 DialogFragment 调用 - 只有 fragmentManager(仍然可能会导致极端情况问题)?应用程序上下文始终相同,如果您的应用程序处于活动状态,它始终存在。唯一的缺点是您可能需要更多地自定义对话框的 UI。

          Dialog dialog = new Dialog(getApplicationContext());
          dialog.show();
          

          DialogFragment newFragment = new BleDialogFragment();
          newFragment.show(getSupportFragmentManager(), "bleStuff");
          

          【讨论】:

            猜你喜欢
            • 2014-10-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-01-03
            • 1970-01-01
            相关资源
            最近更新 更多