由于毕业设计用得到蓝牙,因此简单研究了一下蓝牙。由于本人学术知识有限,本文可能出现错误,请指正。
介绍一下
JDY-10M蓝牙模块:
手机系统为安卓9版本。
使用的工具为android studio3.5.(应该算是最新版本了),适配的安卓版本为安卓9(我手机的版本为安卓9)
买的JAY-10M附带的资料给了APP的源码,然后尝试了将这个源代码直接移植到我自己的项目中,出了问题。自带两个apk文件,一个闪退,另一个能够正常使用。源代码适配的是安卓5的。因此,这个源代码应该是没有问题的。查看报错,是权限的问题。但是已经再声明文件中给了蓝牙的权限,这是为什么呢?最后搞清楚了需要增加一个动态权限申请蓝牙权限后才能够正常使用。
这个硬件自带的直接控制硬件的删掉了,要自己焊,拆开比较麻烦(使用热缩套把贴片和底板锁在一起,就不拆了)
由于篇幅有限以及时间问题,功能上做了一些删减。
由于是使用旧版的示例代码,有些方法已经不推荐使用了,最新的方法也有,但是最官网上BLE的示例还是使用该方法实现的,后期有时间会写一下(别问为什么不用最新的,问就是不会,问就是英语不行,问就是懒)。
贴一下安卓蓝牙的手册(当然谷歌是纯英文的)
该APP有两个界面,第一个界面显示蓝牙扫描到的蓝牙设备名称、mac地址(有些蓝牙设备是没名字的,带mac地址保险点),第二个界面连接蓝牙设备进行控制。
扫描界面放一个listview。
假定名字为SCAN_VIEW.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<ListView
android:id="@+id/lv_bleList"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
创建一个layout(显示单条蓝牙信息)
用于显示蓝牙设备的名称和mac地址
假定名字为ITEM_BLE_LIST.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView android:id="@+id/device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="25dp"/>
<TextView android:id="@+id/device_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15dp"/>
</LinearLayout>
扫描界面右上角设置按钮,用来扫描设备和停止扫描。
新建一个menu
假定名字为SCAN_MAIN.xml.
三个按钮分别为刷新中,扫描,停止扫描
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_refresh"
android:checkable="false"
android:orderInCategory="1"
android:showAsAction="ifRoom"/>
<item android:id="@+id/menu_scan"
android:title="@string/menu_scan"
android:orderInCategory="100"
android:showAsAction="ifRoom|withText"/>
<item android:id="@+id/menu_stop"
android:title="@string/menu_stop"
android:orderInCategory="101"
android:showAsAction="ifRoom|withText"/>
</menu>
扫描界面代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
/**
* Activity for scanning and displaying available Bluetooth LE devices.
*/
public class DeviceScanActivity extends Activity implements OnClickListener {
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
private static final int REQUEST_ENABLE_BT = 1;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
private DeviceListAdapter mDevListAdapter;
ToggleButton tb_on_off;
TextView btn_searchDev;
Button btn_aboutUs;
ListView lv_bleList;
Timer timer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//getActionBar().setTitle(R.string.title_devices);
mHandler = new Handler();
// Use this check to determine whether BLE is supported on the device. Then you can
// selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
// Initializes a Bluetooth adapter. For API level 18 and above, get a reference to
// BluetoothAdapter through BluetoothManager.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
// Checks if Bluetooth is supported on the device.
if (mBluetoothAdapter == null) {
Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
finish();
return;
}
lv_bleList = (ListView) findViewById(R.id.lv_bleList);
mDevListAdapter = new DeviceListAdapter();
lv_bleList.setAdapter(mDevListAdapter);
lv_bleList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (mDevListAdapter.getCount() > 0) {
BluetoothDevice device1 = mDevListAdapter.getItem(position);
if (device1 == null) return;
Intent intent1 = new Intent(DeviceScanActivity.this,
DeviceControlActivity.class);;
intent1.putExtra(DeviceControlActivity.EXTRAS_DEVICE_NAME, device1.getName());
intent1.putExtra(DeviceControlActivity.EXTRAS_DEVICE_ADDRESS, device1.getAddress());
if (mScanning) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
mScanning = false;
}
startActivity(intent1);
}
}
});
}
public void onClick(View v) {
switch (v.getId()) {
case 0:
break;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
if (!mScanning) {
menu.findItem(R.id.menu_stop).setVisible(false);
menu.findItem(R.id.menu_scan).setVisible(true);
menu.findItem(R.id.menu_refresh).setActionView(null);
} else {
menu.findItem(R.id.menu_stop).setVisible(true);
menu.findItem(R.id.menu_scan).setVisible(false);
menu.findItem(R.id.menu_refresh).setActionView(
R.layout.actionbar_indeterminate_progress);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_scan:
//mLeDeviceListAdapter.clear();
scanLeDevice(true);
//mDevListAdapter.;
mDevListAdapter.clear();
mDevListAdapter.notifyDataSetChanged();
break;
case R.id.menu_stop:
scanLeDevice(false);
break;
}
return true;
}
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
invalidateOptionsMenu();
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
invalidateOptionsMenu();
}
private BluetoothAdapter.LeScanCallback mLeScanCallback = new LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mDevListAdapter.addDevice(device);
mDevListAdapter.notifyDataSetChanged();
}
});
}
};
@Override
protected void onResume() {//打开APP时扫描设备
super.onResume();
scanLeDevice(true);
}
@Override
protected void onPause() {//停止扫描
super.onPause();
scanLeDevice(false);
}
//自定义DeviceListAdapter类,用于显示数据,清除数据
class DeviceListAdapter extends BaseAdapter {
private List<BluetoothDevice> mBleArray;
private ViewHolder viewHolder;
public DeviceListAdapter() {
mBleArray = new ArrayList<BluetoothDevice>();
}
public void addDevice(BluetoothDevice device) {
if (!mBleArray.contains(device)) {
mBleArray.add(device);
}
}
public void clear(){
mBleArray.clear();
}
@Override
public int getCount() {
return mBleArray.size();
}
@Override
public BluetoothDevice getItem(int position) {
return mBleArray.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(DeviceScanActivity.this).inflate(
R.layout.listitem_device, null);
viewHolder = new ViewHolder();
viewHolder.tv_devName = (TextView) convertView
.findViewById(R.id.device_name);
viewHolder.tv_devAddress = (TextView) convertView
.findViewById(R.id.device_address);
convertView.setTag(viewHolder);
} else {
convertView.getTag();
}
// add-Parameters
//设置蓝牙名称和mac地址如果没有名称就设置为“unknow-device”
BluetoothDevice device = mBleArray.get(position);
String devName = device.getName();
if (devName != null && devName.length() > 0) {
viewHolder.tv_devName.setText(devName);
} else {
viewHolder.tv_devName.setText("unknow-device");
}
viewHolder.tv_devAddress.setText(device.getAddress());
return convertView;
}
}
//返回蓝牙名字,mac地址
class ViewHolder {
TextView tv_devName, tv_devAddress;
}
}
查看SCAN_VIEW.xml的design界面,如果是显示空白的,代表导入ITEM_BLE_LIST.xml没成功,在检查ITEM_BLE_LIST.xml。可能是有引用的资源没有,导致无法显示。下图是正常,没有导入成功就是空白的。
在代码转移的时候遇到过这个问题,没有报错,能够运行,但是不显示。
创建第二个界面
如下图所示
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_device_address"
android:textSize="14sp" />
<TextView
android:id="@+id/device_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_state"
android:textSize="14sp" />
<TextView
android:id="@+id/connection_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/disconnected"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="#00f0e0"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_data"
android:textSize="12sp" />
<TextView
android:id="@+id/data_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_data"
android:textSize="12sp" />
<CheckBox
android:id="@+id/checkBox5"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginLeft="50dp"
android:layout_marginTop="0dp"
android:text="hex收" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<EditText
android:id="@+id/rx_data_id_1"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:enabled="true"
android:focusable="false"
android:gravity="top"
android:inputType="textMultiLine"
android:maxLines="10"
android:minLines="5"
android:scrollbars="vertical"
android:textSize="10dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="25dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="0dp"
android:background="#00f0e0"
android:orientation="horizontal" >
<TextView
android:id="@+id/tx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/TXD"
android:textSize="12sp" />
<CheckBox
android:id="@+id/checkBox1"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginLeft="25dp"
android:text="hex发" />
<CheckBox
android:id="@+id/checkBox1_tc_send"
android:layout_width="wrap_content"
android:layout_height="10dp"
android:layout_marginLeft="40dp"
android:text="连续发"
android:visibility="invisible" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/tx_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:textSize="11sp" >
</EditText>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00f0e0"
android:text="以上部份为普通 APP透传功能,一般客户需要使用APP透传数据的话,请使用以上串口中功能"
android:textSize="11dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:orientation="horizontal" >
<Button
android:id="@+id/tx_button"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_marginLeft="50dp"
android:text="发送"
android:textSize="10sp" />
<Button
android:id="@+id/clear_button"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_marginLeft="50dp"
android:text="清除"
android:textSize="10sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="1dp"
android:layout_marginRight="1dp"
android:layout_marginTop="10dp"
android:background="#f00000"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="以下为MESH(串口透传、IO、PWM、LED灯、支持一对多,多对一,多对多通信"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
andr