蓝牙连接状态改变时防止onDestroy
Prevent onDestroy when Bluetooth connection state changes
目标
- 如果已连接的蓝牙设备断开连接,并且 Activity 已经 运行ning,请关闭 Activity
问题
- 当蓝牙设备连接状态通过 BluetoothAdapterProperties 改变时:CONNECTION_STATE_CHANGE,似乎创建了一个新的 Activity 或当前的重新启动。
侦听 and/or 的代码中没有任何内容应该对蓝牙连接状态变化做出反应。
问题体现在 BroadcastReceivers 的使用中,而 BroadcastReceivers 又启动了 Activity using intents。出于某种原因,Activity 在其生命周期中保持 运行ning,产生新的 windows,即使蓝牙连接的唯一变化是 BluetoothAdapterProperties:CONNECTION_STATE_CHANGE
我仅在带有 Android N 的 Nexus 6P 上对此进行了测试。我还不知道此实现对任何其他设备意味着什么。但我至少需要在 一个 设备上运行它。
更新
我做了一些实验,发现如果我不在 AndroidManifest 中注册 BroadcastReceiver,调用 onDestroy 的问题就会消失。但是,我希望能够对蓝牙连接设备做出反应,以便我可以启动 activity 然后处理输入。如果每次新设备 connects/disconnects 时 activity 都被销毁,这将根本不起作用。如果 BroadcastReceiver 已经 运行ning,那么让 BroadcastReceiver 完成 activity 的原因是什么?我可以控制这种行为吗?
更新 2
我还可以得出结论,使用此方法 禁用静态声明的 BroadcastReceiver 不会改善情况。一旦 Manifest-BroadcastReceiver 从 Android 捕捉到 ACL_CONNECTED 意图并启动我的自定义 activity,它就会在连接状态改变时无情地调用 onDestroy(通常就在之前一个 ACL_DISCONNECTED)。我是否在清单中声明了 ACL_DISCONNECTED 并不重要。只要我让我的接收器侦听 ACL_CONNECTED 意图并基于此启动我的 Activity,当连接状态更改时将调用 onDestroy。好郁闷
清单
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".BtActivity"
android:launchMode="singleTop" />
<receiver android:name=".BtConnectionBroadcastReceiver" android:priority="100000">
<intent-filter>
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
<action android:name="android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED" />
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.VOLUME_CHANGED_ACTION" />
</intent-filter>
</receiver>
</application>
BtConnectionBroadcastReceiver
public class BtConnectionBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "BT";
public static final String BROADCAST_ACTION_CONNECTED = "CONNECTED";
public static final String BROADCAST_ACTION_DISCONNECTED = "DISCONNECTED";
SharedPreferences mSharedPreferences;
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
// Get the BluetoothDevice object from the Intent
Log.d(TAG, "DEVICE CONNECTED");
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d("DEVICE NAME", device.getName());
Log.d("DEVICE ADDRESS", device.getAddress());
Intent i = new Intent(context, BtActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(i);
} else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
Log.d(TAG, "DEVICE DISCONNECTED");
intent = new Intent();
intent.setAction(BtConnectionBroadcastReceiver.BROADCAST_ACTION_DISCONNECTED);
context.sendBroadcast(intent);
}
}
BtActivity
public class BtActivity extends AppCompatActivity {
private static final String TAG = "BT";
Window mWindow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bt);
Log.d(TAG, "onCreate");
IntentFilter filter = new IntentFilter(BtConnectionBroadcastReceiver.INTENT_FILTER);
filter.addAction(BtConnectionBroadcastReceiver.BROADCAST_ACTION_CONNECTED);
filter.addAction(BtConnectionBroadcastReceiver.BROADCAST_ACTION_DISCONNECTED);
//registerReceiver(mReceiver, filter);
mWindow = getWindow();
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
//params.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;
params.screenBrightness = 0.2f;
mWindow.setAttributes(params);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
mWindow.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE);
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "BROADCAST RECEIVED IN ACTIVITY");
String mac;
if(intent.getAction().equals(BtConnectionBroadcastReceiver.BROADCAST_DEVICE_CONNECTED)) {
Log.d(TAG, "CONNECT BROADCAST RECEIVED");
mac = intent.getStringExtra("mac");
checkConnectedDevice(mac, true); // This adds a device to an internal list
Log.d(TAG, "Activity nr of devices:" +mNrOfDevices);
}
if(intent.getAction().equals(BtConnectionBroadcastReceiver.BROADCAST_DEVICE_DISCONNECTED)) {
Log.d(TAG, "DISCONNECT BROADCAST RECEIVED");
mac = intent.getStringExtra("mac");
checkConnectedDevice(mac, false); // This removes a device from an internal list
Log.d(TAG, "Activity nr of devices:" +mNrOfDevices);
if(mNrOfDevices < 1) {
Log.d(TAG, "No more connected devices");
finish();
}
}
abortBroadcast();
}
};
}
当我运行这段代码时,我得到以下链:
- Start MainActivity(不包含,它只包含一个 activity 和默认的主布局,以便注册应用程序接收器)
- 打开蓝牙设备(之前已经配对,所以 android 知道)
- 等到它连接并得到这个:
- 设备已连接
- onCreate
- 恢复
- 关闭蓝牙设备,然后我得到这个:
- 设备已断开连接
- onDestroy
- onCreate
- 恢复
我不明白为什么 activity 会在此时重新启动时被破坏。 activity 已经运行ning,BroadcastReceiver 只发送一个广播给已经运行ning 的activity。我不明白为什么 Activity 会自行终止并 然后 再次重启。这让我处于 Activity 仍然 运行ning 的状态,但它不是最初启动的 Activity。
不过,我确实在 logcat 中看到了一些似乎与此有关的内容,并且在这个顺序中;
06-02 15:45:09.156 26431 26431 D BT:设备已断开连接
06-02 15:45:09.213 19547 19547 D BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_CONNECTION_STATE_CHANGED
06-02 15:45:09.213 26431 26431 D BT : onDestroy
06-02 15:45:09.214 19547 19547 D BluetoothAdapterProperties: CONNECTION_STATE_CHANGE: FF:FF:20:00:C1:47: 2 -> 0
06-02 15:45:09.216 3502 3805 D CachedBluetoothDevice: onProfileStateChanged: 配置文件 HID newProfileState 0
06-02 15:45:09.237 414 414 W SurfaceFlinger:无法记录到二进制事件日志:溢出。
06-02 15:45:09.239 26431 26431 D BT: onCreate
06-02 15:45:09.243 26431 26431 D BT : onResume
读完这个 https://developer.android.com/guide/components/broadcasts.html#effects_on_process_state 我可以有把握地得出结论,调用 onDestroy 的原因是因为接收方影响它所在的进程 运行,实际上意味着当接收方有 运行它的onReceive方法,它会销毁自己并带走Activity。
我当然希望它能以不同的方式工作,但我相信这是有效的,需要采取另一种方法。
在 AndroidManifest.xml 中为 Activity 添加以下内容,它对我有用。
android:configChanges="keyboard|keyboardHidden"
我知道这个答案已经很晚了,但我的 activity 标签遇到了这个问题。在清单文件中,我为 configChange 添加了以下行。
android:configChanges="keyboard|orientation|screenSize|keyboardHidden|navigation|screenLayout"
现在我的应用程序不会自行终止。
目标
- 如果已连接的蓝牙设备断开连接,并且 Activity 已经 运行ning,请关闭 Activity
问题
- 当蓝牙设备连接状态通过 BluetoothAdapterProperties 改变时:CONNECTION_STATE_CHANGE,似乎创建了一个新的 Activity 或当前的重新启动。
侦听 and/or 的代码中没有任何内容应该对蓝牙连接状态变化做出反应。
问题体现在 BroadcastReceivers 的使用中,而 BroadcastReceivers 又启动了 Activity using intents。出于某种原因,Activity 在其生命周期中保持 运行ning,产生新的 windows,即使蓝牙连接的唯一变化是 BluetoothAdapterProperties:CONNECTION_STATE_CHANGE
我仅在带有 Android N 的 Nexus 6P 上对此进行了测试。我还不知道此实现对任何其他设备意味着什么。但我至少需要在 一个 设备上运行它。
更新
我做了一些实验,发现如果我不在 AndroidManifest 中注册 BroadcastReceiver,调用 onDestroy 的问题就会消失。但是,我希望能够对蓝牙连接设备做出反应,以便我可以启动 activity 然后处理输入。如果每次新设备 connects/disconnects 时 activity 都被销毁,这将根本不起作用。如果 BroadcastReceiver 已经 运行ning,那么让 BroadcastReceiver 完成 activity 的原因是什么?我可以控制这种行为吗?
更新 2
我还可以得出结论,使用此方法 禁用静态声明的 BroadcastReceiver 不会改善情况。一旦 Manifest-BroadcastReceiver 从 Android 捕捉到 ACL_CONNECTED 意图并启动我的自定义 activity,它就会在连接状态改变时无情地调用 onDestroy(通常就在之前一个 ACL_DISCONNECTED)。我是否在清单中声明了 ACL_DISCONNECTED 并不重要。只要我让我的接收器侦听 ACL_CONNECTED 意图并基于此启动我的 Activity,当连接状态更改时将调用 onDestroy。好郁闷
清单
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".BtActivity"
android:launchMode="singleTop" />
<receiver android:name=".BtConnectionBroadcastReceiver" android:priority="100000">
<intent-filter>
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
<action android:name="android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED" />
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.VOLUME_CHANGED_ACTION" />
</intent-filter>
</receiver>
</application>
BtConnectionBroadcastReceiver
public class BtConnectionBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "BT";
public static final String BROADCAST_ACTION_CONNECTED = "CONNECTED";
public static final String BROADCAST_ACTION_DISCONNECTED = "DISCONNECTED";
SharedPreferences mSharedPreferences;
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
// Get the BluetoothDevice object from the Intent
Log.d(TAG, "DEVICE CONNECTED");
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d("DEVICE NAME", device.getName());
Log.d("DEVICE ADDRESS", device.getAddress());
Intent i = new Intent(context, BtActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(i);
} else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
Log.d(TAG, "DEVICE DISCONNECTED");
intent = new Intent();
intent.setAction(BtConnectionBroadcastReceiver.BROADCAST_ACTION_DISCONNECTED);
context.sendBroadcast(intent);
}
}
BtActivity
public class BtActivity extends AppCompatActivity {
private static final String TAG = "BT";
Window mWindow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bt);
Log.d(TAG, "onCreate");
IntentFilter filter = new IntentFilter(BtConnectionBroadcastReceiver.INTENT_FILTER);
filter.addAction(BtConnectionBroadcastReceiver.BROADCAST_ACTION_CONNECTED);
filter.addAction(BtConnectionBroadcastReceiver.BROADCAST_ACTION_DISCONNECTED);
//registerReceiver(mReceiver, filter);
mWindow = getWindow();
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
//params.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;
params.screenBrightness = 0.2f;
mWindow.setAttributes(params);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
mWindow.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE);
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "BROADCAST RECEIVED IN ACTIVITY");
String mac;
if(intent.getAction().equals(BtConnectionBroadcastReceiver.BROADCAST_DEVICE_CONNECTED)) {
Log.d(TAG, "CONNECT BROADCAST RECEIVED");
mac = intent.getStringExtra("mac");
checkConnectedDevice(mac, true); // This adds a device to an internal list
Log.d(TAG, "Activity nr of devices:" +mNrOfDevices);
}
if(intent.getAction().equals(BtConnectionBroadcastReceiver.BROADCAST_DEVICE_DISCONNECTED)) {
Log.d(TAG, "DISCONNECT BROADCAST RECEIVED");
mac = intent.getStringExtra("mac");
checkConnectedDevice(mac, false); // This removes a device from an internal list
Log.d(TAG, "Activity nr of devices:" +mNrOfDevices);
if(mNrOfDevices < 1) {
Log.d(TAG, "No more connected devices");
finish();
}
}
abortBroadcast();
}
};
}
当我运行这段代码时,我得到以下链:
- Start MainActivity(不包含,它只包含一个 activity 和默认的主布局,以便注册应用程序接收器)
- 打开蓝牙设备(之前已经配对,所以 android 知道)
- 等到它连接并得到这个:
- 设备已连接
- onCreate
- 恢复
- 关闭蓝牙设备,然后我得到这个:
- 设备已断开连接
- onDestroy
- onCreate
- 恢复
我不明白为什么 activity 会在此时重新启动时被破坏。 activity 已经运行ning,BroadcastReceiver 只发送一个广播给已经运行ning 的activity。我不明白为什么 Activity 会自行终止并 然后 再次重启。这让我处于 Activity 仍然 运行ning 的状态,但它不是最初启动的 Activity。
不过,我确实在 logcat 中看到了一些似乎与此有关的内容,并且在这个顺序中;
06-02 15:45:09.156 26431 26431 D BT:设备已断开连接
06-02 15:45:09.213 19547 19547 D BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_CONNECTION_STATE_CHANGED
06-02 15:45:09.213 26431 26431 D BT : onDestroy
06-02 15:45:09.214 19547 19547 D BluetoothAdapterProperties: CONNECTION_STATE_CHANGE: FF:FF:20:00:C1:47: 2 -> 0
06-02 15:45:09.216 3502 3805 D CachedBluetoothDevice: onProfileStateChanged: 配置文件 HID newProfileState 0
06-02 15:45:09.237 414 414 W SurfaceFlinger:无法记录到二进制事件日志:溢出。
06-02 15:45:09.239 26431 26431 D BT: onCreate
06-02 15:45:09.243 26431 26431 D BT : onResume
读完这个 https://developer.android.com/guide/components/broadcasts.html#effects_on_process_state 我可以有把握地得出结论,调用 onDestroy 的原因是因为接收方影响它所在的进程 运行,实际上意味着当接收方有 运行它的onReceive方法,它会销毁自己并带走Activity。
我当然希望它能以不同的方式工作,但我相信这是有效的,需要采取另一种方法。
在 AndroidManifest.xml 中为 Activity 添加以下内容,它对我有用。
android:configChanges="keyboard|keyboardHidden"
我知道这个答案已经很晚了,但我的 activity 标签遇到了这个问题。在清单文件中,我为 configChange 添加了以下行。
android:configChanges="keyboard|orientation|screenSize|keyboardHidden|navigation|screenLayout"
现在我的应用程序不会自行终止。