Android 'BadTokenException window token android.os.BinderProxy@4250d6d8 is not valid' 有前台服务 运行

Android 'BadTokenException window token android.os.BinderProxy@4250d6d8 is not valid' with foreground service running

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

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

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 的上下文,因此我遇到了一个问题在 activity.

中显示警报对话框

实际用例 - 当我终止应用程序并从前台服务通知打开它时,当与设备建立连接并从服务中侦听数据时,必须显示的对话框说我的设备仍然connected and received data is not showed and exceptions 当要显示对话框时

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

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 的单个实例,但这没有用。

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

</activity>

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

重要说明 - 我在 activity 中使用范围为 Dispatchers.IO 的协程从 BLE 设备获取数据。

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

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

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

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

您可以创建 class 扩展 BroadcastReceiver class。

您的通知将触发 BroadcastReceiver 的 onReceive 方法。

查看example操作方法

一个more and more

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

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

观察结果

    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)

previous activity is not destroyed

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

When the connection with device is established and data is listened from service at that time when I kill the app and open it from foreground service notification, the dialog which has to show up saying that my device is still connected and data is being received is not shown

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

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

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?

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

您声称使用如下 Activity 上下文开始您的对话:

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

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

建议

准备

我尝试根据您在问题中提供的信息构建一个简约项目来重现该问题。 请注意,尽管 Bluetooth 用作每个组件的前缀 .[=56,但我并未在此示例中使用任何 BLE 功能=]

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

BluetoothDeviceService.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.Whosebugquestion2">

    <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.WhosebugQuestion2"
        android:fullBackupContent="@xml/backup_descriptor">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/Theme.WhosebugQuestion2.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)
    }
    

结果

看起来是这样的: