Android13 原生设置应用蓝牙配对代码分析研究
文章目录
一、前言
系统内自己封装的一个应用,在Android11 上连接蓝牙耳机和蓝牙鼠标键盘没啥问题的,
但是移植到Android13 的系统上经常遇到连接蓝牙鼠标键盘是吧的问题。
发现原生Settings 连接未出现这种问题,所有应该是代码处理需要优化。
车载项目蓝牙设置分析研究:
https://blog.csdn.net/liu362732346/article/details/81866575
Android 早期版本 Settings及BluetoothSettings流程分析:
https://blog.csdn.net/weixin_40919230/article/details/80419671
看了下类名称都不一样了,想直接用里面的关键字直接找到蓝牙调用的api好像也不太行!
这里分享一下原生Settings 中蓝牙连接的相关代码分析。
二、Android13 Settings连接蓝牙的主要代码
1、 AndroidManifest,定义Activity 的地方
packages\apps\Settings\AndroidManifest.xml
<activity android:name=".homepage.SettingsHomepageActivity"
android:label="@string/settings_label_launcher"
android:theme="@style/Theme.Settings.Home"
android:taskAffinity="com.android.settings.root"
android:launchMode="singleTask"
android:configChanges="keyboard|keyboardHidden">
<intent-filter android:priority="1">
<action android:name="android.settings.mtk.SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
<activity
android:name=".Settings$ConnectedDeviceDashboardActivity"
android:label="@string/connected_devices_dashboard_title"
android:exported="true"
android:icon="@drawable/ic_homepage_connected_device">
<intent-filter android:priority="1">
<action android:name="android.settings.BLUETOOTH_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment"/>
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
android:value="@string/menu_key_connected_devices"/>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
<!-- Alias for launcher activity only, as this belongs to each profile. -->
<activity-alias android:name="Settings"
android:label="@string/settings_label_launcher"
android:taskAffinity="com.android.settings.root"
android:launchMode="singleTask"
android:exported="true"
android:targetActivity=".homepage.SettingsHomepageActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
</activity-alias>
从上面能看到Settings 的主界面和快速入口,activity-alias 是Acitivity的别名属性,
activity-alias 一般用来设置快速入口和图标,一般app都不怎么用。
所以启动原生Settings 可以用命令:
//快速入口
am start -n com.android.settings/.Settings
//主界面
am start -n com.android.settings/.homepage.SettingsHomepageActivity
2、 Settings 一个抽象的设置界面
Settings\src\com\android\settings\Settings.java
public static class ConnectedDeviceDashboardActivity extends SettingsActivity {}
里面有上百个空实现的Activity,只复用了 SettingsActivity 的Activity!
这样写的原因是啥???暂时不清楚这个架构!
3、点击"已连接的设备"进入的界面 ConnectedDeviceDashboardFragment
这个TV平台的Settings 代码,没有NFC,这个"已连接的设备"界面就是蓝牙控制连接和显示已保存蓝牙设备的界面。
packages\apps\Settings\src\com\android\settings\connecteddevice\ConnectedDeviceDashboardFragment.java
ConnectedDeviceDashboardFragment 对应的 PreferenceScreen 布局: connected_devices.xml
对应的xml 文件 R.xml.connected_devices :
<Preference
android:fragment="com.android.settings.connecteddevice.BluetoothDashboardFragment"
android:key="bluetooth_switchbar_screen"
android:title="@string/bluetooth_settings_title"
android:icon="@*android:drawable/ic_settings_bluetooth"
android:order="-9"/>
上面的布局默认显示蓝牙界面
Settings\src\com\android\settings\connecteddevice\BluetoothDashboardFragment.java
还有相关的:AdvancedConnectedDeviceDashboardFragment
packages\apps\Settings\res\xml\connected_devices_advanced.xml
布局文件:
//字符串"与新设备配对"的布局
Settings\res\xml\bluetooth_screen.xml
//字符串"连接偏好设置"的布局
Settings\res\xml\connected_devices.xml
上面很多相关跳转还是有点混乱的,可以多加log打印查看。
4、BluetoothPairingDetail 配对列表界面
追一下里面的相关逻辑就能看到调用的具体api了。其实关键就是这个类之后的调用。
//点击"与新设备配对"条目后的配对列表界面
Settings\src\com\android\settings\bluetooth\BluetoothPairingDetail.java
对应布局:
Settings\res\xml\bluetooth_pairing_detail.xml
从上面Java代码和xml 文件中是看不出列表数据的。
(1)BluetoothPairingDetail.java
void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
LogUtil.debug("btPreference =" + btPreference);//点击每个条目时,是有打印的!自己加的
disableScanning();
super.onDevicePreferenceClick(btPreference);//所以重点是看父类的点击事件
}
BluetoothPairingDetail 的父类是 DeviceListPreferenceFragment。
Settings\src\com\android\settings\bluetooth\DeviceListPreferenceFragment.java
//扫描到的可以连接的蓝牙集合列表
final HashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap = new HashMap<>();
//父类中的点击事件
void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
btPreference.onClicked();//继续追踪 onClicked 方法
}
(2)BluetoothDevicePreference
Settings\src\com\android\settings\bluetooth\BluetoothDevicePreference.java
protected final CachedBluetoothDevice mCachedDevice;
//这里就是原生Settings 中根据蓝牙条目的状态,调用蓝牙的具体api实现。
void onClicked() {
Context context = getContext();
int bondState = mCachedDevice.getBondState();
LogUtil.debug("bondState = " + bondState);
final MetricsFeatureProvider metricsFeatureProvider =
FeatureFactory.getFactory(context).getMetricsFeatureProvider();
if (mCachedDevice.isConnected()) { //条目已连接,弹框提示是否断开连接
metricsFeatureProvider.action(context,
SettingsEnums.ACTION_SETTINGS_BLUETOOTH_DISCONNECT);
askDisconnect();
} else if (bondState == BluetoothDevice.BOND_BONDED) { //已保存的情况,直接进行连接操作
metricsFeatureProvider.action(context,
SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT);
mCachedDevice.connect();
} else if (bondState == BluetoothDevice.BOND_NONE) {
metricsFeatureProvider.action(context,
SettingsEnums.ACTION_SETTINGS_BLUETOOTH_PAIR);
if (!mCachedDevice.hasHumanReadableName()) {
metricsFeatureProvider.action(context,
SettingsEnums.ACTION_SETTINGS_BLUETOOTH_PAIR_DEVICES_WITHOUT_NAMES);
}
pair();
}
}
//未连接的设备,配对
private void pair() {
if (!mCachedDevice.startPairing()) { //这里看到只是调用了 startPairing 方法!
Utils.showError(getContext(), mCachedDevice.getName(),
R.string.bluetooth_pairing_error_message);
}
}
5、BluetoothDeviceDetailsFragment 取消保存和断开连接界面
packages\apps\Settings\src\com\android\settings\bluetooth\BluetoothDeviceDetailsFragment.java
//点击已连接的蓝牙条目,跳转显示"取消保存"和"断开连接"界面
//点击"断开连接"–》变成"连接",进入可连接状态
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
ArrayList<AbstractPreferenceController> controllers = new ArrayList<>();
if (mCachedDevice != null) {
Lifecycle lifecycle = getSettingsLifecycle();
controllers.add(new BluetoothDetailsHeaderController(context, this, mCachedDevice,
lifecycle, mManager));
//"取消保存","断开连接","连接" 事件都 BluetoothDetailsButtonsController 在里面
controllers.add(new BluetoothDetailsButtonsController(context, this, mCachedDevice,
lifecycle));
controllers.add(new BluetoothDetailsCompanionAppsController(context, this,
mCachedDevice, lifecycle));
controllers.add(new BluetoothDetailsSpatialAudioController(context, this, mCachedDevice,
lifecycle));
controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
mCachedDevice, lifecycle));
controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,
lifecycle));
controllers.add(new BluetoothDetailsRelatedToolsController(context, this, mCachedDevice,
lifecycle));
controllers.add(new BluetoothDetailsPairOtherController(context, this, mCachedDevice,
lifecycle));
}
return controllers;
}
(2)BluetoothDetailsButtonsController 事件监听
//点击"取消保存"触发
private void onForgetButtonPressed() {
LogUtil.debug("");
ForgetDeviceDialogFragment fragment =
ForgetDeviceDialogFragment.newInstance(mCachedDevice.getAddress());
fragment.show(mFragment.getFragmentManager(), ForgetDeviceDialogFragment.TAG);
}
//界面创建的时候,"取消保存"按钮
@Override
protected void init(PreferenceScreen screen) {
mActionButtons = ((ActionButtonsPreference) screen.findPreference(
getPreferenceKey()))
.setButton1Text(R.string.forget)
.setButton1Icon(R.drawable.ic_settings_delete)
.setButton1OnClickListener((view) -> onForgetButtonPressed())
.setButton1Enabled(true);
}
//界面resume的时候化,更新蓝牙的状态显示:"断开连接"/"连接"按钮,并添加监听事件
@Override
protected void refresh() {
mActionButtons.setButton2Enabled(!mCachedDevice.isBusy());
boolean previouslyConnected = mIsConnected;
mIsConnected = mCachedDevice.isConnected();
if (mIsConnected) {
if (!mConnectButtonInitialized || !previouslyConnected) {
mActionButtons
.setButton2Text(R.string.bluetooth_device_context_disconnect)
.setButton2Icon(R.drawable.ic_settings_close)
.setButton2OnClickListener(view -> {
mMetricsFeatureProvider.action(mContext,
SettingsEnums.ACTION_SETTINGS_BLUETOOTH_DISCONNECT);
//断开蓝牙的api
mCachedDevice.disconnect();
});
mConnectButtonInitialized = true;
}
} else {
if (!mConnectButtonInitialized || previouslyConnected) {
mActionButtons
.setButton2Text(R.string.bluetooth_device_context_connect)
.setButton2Icon(R.drawable.ic_add_24dp)
.setButton2OnClickListener(
view -> {
mMetricsFeatureProvider.action(mContext,
SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT);
//连接断开过的蓝牙的api
mCachedDevice.connect();
});
mConnectButtonInitialized = true;
}
}
}
(3)确认忘记设备对话框界面 ForgetDeviceDialogFragment
Settings\src\com\android\settings\bluetooth\ForgetDeviceDialogFragment.java
private CachedBluetoothDevice mDevice;
@Override
public Dialog onCreateDialog(Bundle inState) {
DialogInterface.OnClickListener onConfirm = (dialog, which) -> {
//确认忘记蓝牙的api
mDevice.unpair();
Activity activity = getActivity();
if (activity != null) {
activity.finish();
}
};
Context context = getContext();
mDevice = getDevice(context);
AlertDialog dialog = new AlertDialog.Builder(context)
.setPositiveButton(R.string.bluetooth_unpair_dialog_forget_confirm_button,
onConfirm)
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.setTitle(R.string.bluetooth_unpair_dialog_title);
dialog.setMessage(context.getString(R.string.bluetooth_unpair_dialog_body,
mDevice.getName()));
return dialog;
}
//似乎只执行了这个:
mDevice.unpair();
三、总结原生Settings中对蓝牙连接断开代码调用:
//获取蓝牙列表
LocalBluetoothManager mLocalManager = LocalBluetoothManager.getInstance(context, mOnInitCallback);;
mLocalManager.getCachedDeviceManager().clearNonBondedDevices();//清除扫描列表
Collection<CachedBluetoothDevice> cachedDevices = mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();//获取扫描列表
//蓝牙单个对象 CachedBluetoothDevice
protected final CachedBluetoothDevice mCachedDevice;
//1、未连接的情况,配对+连接
boolean isParing = mCachedDevice.startPairing();
//2、已连接的情况,断开连接,进入保存状态
mCachedDevice.disconnect();
//3、断开连接,进入保存状态的情况,重新连接
mCachedDevice.connect();
//4、连接/保存状态,忘记设备
mCachedDevice.unpair();
LocalBluetoothManager 和 CachedBluetoothDevice 都是系统SettingsLib包中封装的类
LocalBluetoothManager 里面其实是封装了LocalBluetoothAdapter ,最后是调用了原生的 BluetoothAdapter;
CachedBluetoothDevice 其实是对蓝牙基本数据 BluetoothAdapter 的进一步封装。
Android 9 好像是看过TvSettings 的蓝牙连接过程,跟原生Settings 写法好像并不完全相同!
而 LocalBluetoothManager 的api的调用具体是在原生api上做了什么优化,需要大家自己探索研究了!