问题描述
尝试使用 kotlin 构建一个 android 应用程序,我在其中连接、发送和读取来自 BLE(低功耗蓝牙)设备的数据。我设计了一个活动,它将显示从 BLE 设备接收的连接统计信息和数据。我有一个前台服务正在运行,以保持与 BLE 设备的连接处于活动状态并收听统计信息。 我已使用待处理的 Intent 从我的前台服务通知中打开此活动。
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>,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()
}
但这也无济于事。上面的代码会抑制执行,但我不想这样做,而是想不惜一切代价显示对话框。
为了提供一个 pov,我在清单文件中尝试了以下内容,以保持活动的单个实例成为可能,但这不起作用。
<activity android:name=".ActivityName"
android:alwaysRetainTaskState="true"
android:launchMode="singletop"
android:screenorientation="portrait"
android:theme="@style/AppThemeGeneral">
</activity>
我必须以最好的方式使用警报对话框,跳过它不是一个选择。 任何帮助都会很棒。
重要说明 - 我在我的活动中使用具有范围 dispatchers.IO 的协程从 BLE 设备获取数据。
解决方法
观察
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)
之前的活动不会被破坏
这是值得怀疑的,因为您在启动 Intent.FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TASK
时使用了标志 Activity
,这将完成之前的所有活动。否则,请出示相关代码。
当与设备建立连接并监听数据时 从那时我杀死应用程序并从服务中打开它 前台服务通知,必须显示的对话框 说我的设备仍然连接并且正在接收数据是 未显示
这听起来很令人困惑。听起来您是从 Activity
显示它,但它不太可能失败,尤其是如果您从 onCreate
的 Activity
显示它:
override fun onCreate(savedInstanceState: Bundle?) {
...
showAlertDialog()
}
D/DialogExecption:无法添加窗口——令牌空无效;是 你的活动正在运行? D/DialogExecption:无法添加窗口令牌 android.os.BinderProxy@4250d6d8 无效;是你的活动 跑步?
错误消息非常清楚 - 您正在尝试显示基于无效 Context
此处 AlertDialog.Builder(<context>)
的对话框。例如,如果您在那里使用 applicationContext
或 Service
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,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
:
蓝牙设备活动.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
。为此,您需要做两件事:
-
创建一个新的
À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() } }
-
将其添加到您的清单并将其主题设置为
Dialog
:<activity android:name=".AlertDialogActivity" android:theme="@style/Theme.AppCompat.Dialog"/>
-
然后,在您的
Service
中创建一个方法,并在需要时随时使用它:private fun showAlertDialog() { val intent = Intent(applicationContext,AlertDialogActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK applicationContext.startActivity(intent) }
结果
这就是它的样子:
,您可以创建扩展 BroadcastReceiver 类的类。
您的通知将触发 BroadcastReceiver 的 onReceive 方法。
查看example如何操作
onReceive(context: Context!,intent: Intent!)
根据您意图的操作,您将调用在相应上下文中显示 AlertDialog 的方法
,您应该使用 ViewModel。为您的活动创建一个 MainViewModel,在此 ViewModel 中您创建一个 MutableLiveData:isShowDialog。当你想显示一个对话框时,你将调用 MainViewModel 的一个方法来更新 isShowDialog 变量的值 (true)。在 MainActivity 中,您将观察 isShowDialog 变量,如果为 true,则调用该方法以显示对话框。 ViewModel 将确保在您的活动准备就绪时调用显示对话框的方法。
,是什么阻止您从 AlertDialog
转移到可以使用应用程序上下文而不是使用根本不需要上下文的活动一或 Dialog
调用的 DialogFragment
-只有fragmentManager
(仍然可能导致极端情况问题)?应用程序上下文始终相同,如果您的应用程序处于活动状态,则它始终存在。唯一的缺点是您可能需要更多地自定义对话框的 UI。
Dialog dialog = new Dialog(getApplicationContext());
dialog.show();
或
DialogFragment newFragment = new BleDialogFragment();
newFragment.show(getSupportFragmentManager(),"bleStuff");