Android前台服务运行时出现'BadTokenException窗口令牌android.os.BinderProxy@4250d6d8无效'错误

4

尝试使用 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连接和数据接收的活动。多个实例的活动被创建,之前的活动没有被销毁,因此我无法获取正在运行的活动的上下文,由此导致我在活动中显示警报对话框时遇到问题。

实际用例- 当与设备建立连接并从服务侦听数据时,当我杀死应用程序并从前台服务通知中打开时,应该出现对话框,提示我的设备仍然连接,并正在接收数据,但对话框未显示,显示对话框时发生异常。

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

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()
}

但这也没有什么帮助。上面的代码将抑制异常,但我不想这样做,相反,我想无论如何都要显示对话框。

为了给一个观点,我尝试在清单文件中使用以下内容以保持活动的单个实例可能性,但这并没有成功。

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

</activity>

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

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

4个回答

2

观察结果

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

如果通知缺少setSmallIcon或类似的setIcon调用,则单击此通知可能无法启动Activity。要解决此问题,请按照以下方式设置一些图标:

     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: 无法添加窗口-- null 标记无效;您的活动是否正在运行?D/DialogExecption: 无法添加窗口令牌 android.os.BinderProxy@4250d6d8 无效;您的活动是否正在运行?

这个错误信息很清楚——您在此处使用无效的Context尝试显示对话框,例如,如果您在那里使用applicationContextServicecontext,就会出现此异常。

您声称使用以下代码以Activity上下文启动对话框:

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

然而,从您的代码中无法清楚地看出何时调用showAlertDialog()方法,也没有显示上下文对象。因此,我创建了一个示例项目来测试所描述的行为。
建议
准备工作
我尝试通过基于您在问题中提供的信息构建一个最小化的项目来复现此问题。请注意,即使每个组件都以蓝牙作为前缀,但本示例不使用任何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.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. Create a new Àctivity as below:

    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. Add it to your manifest and set its theme as Dialog:

    <activity android:name=".AlertDialogActivity" android:theme="@style/Theme.AppCompat.Dialog"/>
    
  3. Then, create a method in your Service, and use it any time it's needed:

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

结果

这就是它的样子:

enter image description here


0

你可以创建一个继承BroadcastReceiver类的类。

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

查看示例以了解如何实现

更多更多

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

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

-1

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

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

或者

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接