一、需要了解和学习的前置知识
1、启动事件并获得返回值:
用于一般在使用相机,获取通讯录电话信息,检查权限等获取操作结果的回调的情况。
// 必须在Activity中才可以使用
// 第一个参数是我们需要处理第二个参数回调的数据的contract类
// 第二个参数是需要获取的回调值
registerForActivityResult(contract:ActivityResultContracts,callback)
i、在返回Activity返回后获取结果:
class MainActivity : AppCompatActivity() {
private final val TAG = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 触发
findViewById<Button>(R.id.btn).setOnClickListener {
val intent = Intent(this,SecondActivity::class.java)
activityLauncher.launch(intent)
}
}
private val activityLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
Log.i(TAG, "activityLauncher")
// 实际获取的data就是onCreate中传递的Intent
val data:Intent? = result?.data
Log.i(TAG, "获取的data=$data")
// 还可以获取额外的数据
val str = data?.getStringExtra("str")
Log.i(TAG, "获取的str=$str")
val action = data?.action
Log.i(TAG, "获取的action=$action")
}
}
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
findViewById<Button>(R.id.btn).setOnClickListener{
val intent = Intent()
intent.action = "hello"
intent.putExtra("str","world")
// 设置回调的结果
setResult(0,intent)
finish()
}
}
}
ii、检查权限
// 需要在配置清单配置权限
// 单权限检查
class MainActivity : AppCompatActivity() {
private final val TAG = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 检查权限
if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
permissionLauncher.launch(permission)
}
}
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()){
if (it) {
// 如果授权
Log.i(TAG,"授权成功!")
}else{
// 如果未授权
Log.i(TAG,"未授权")
}
}
// 模糊定位权限
private val permission = android.Manifest.permission.ACCESS_COARSE_LOCATION
}
// 多权限申请
class MainActivity : AppCompatActivity() {
private final val TAG = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 检查权限
// find相当于for循环
if (permissions.find {ActivityCompat.checkSelfPermission(this,it)
!= PackageManager.PERMISSION_GRANTED
} != null) {
multiplePermissionsLauncher.launch(permissions)
}
}
// 测试的权限,需要将这几个权限加到配置清单
private val permissions = arrayOf(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.BLUETOOTH,
android.Manifest.permission.BLUETOOTH_ADMIN
)
private val multiplePermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()){ permissionsMap ->
val noGrantedPermissions = ArrayList<String>()
permissionsMap.entries.forEach{
if (!it.value) {
noGrantedPermissions.add(it.key)
}
}
if (noGrantedPermissions.isEmpty()) {
Log.i(TAG,"权限全部申请通过!")
// 权限申请通过,可以执行的操作。可以不写任何代码
}else{
Log.e(TAG,"权限未全部申请通过,继续申请权限!")
noGrantedPermissions.forEach{
if (!shouldShowRequestPermissionRationale(it)) {
//用户拒绝权限并且系统不再弹出请求权限的弹窗
//这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
}
}
}
}
}
2、handler的使用
3、ListView的使用
val listData = arrayListOf("a", "b", "c", "d", "e", "f", "g", "h", "k")
val lv = findViewById<ListView>(R.id.lv)
lv.adapter =
ArrayAdapter(this, android.R.layout.simple_list_item_1, listData)
lv.onItemClickListener = AdapterView.OnItemClickListener { parent, view, postion, id ->
// 这里只需要考虑position,表示点击的是第几个item
Toast.makeText(this,"当前点击索引值:$postion,值为:${listData[postion]}" ,Toast.LENGTH_SHORT).show()
}
二、蓝牙连接的开发
1、需要了解的类
- BluetoothManager:被用来提供蓝牙适配器实例的高水平管理类
- BluetoothAdapter getAdapter():返回蓝牙适配器
官方文档表示:
- BluetoothAdapter getAdapter():返回蓝牙适配器
官方文档表示:
mBluetoothManager = mContext?.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (mBluetoothManager == null) {
Log.e(TAG, "BluetoothManager初始化失败!")
return;
}
mBluetoothAdapter = mBluetoothManager!!.adapter
- BluetoothAdapter:使蓝牙可被发现、获取配对过的设备、创建socket连接通信、通过已知的Mac地址实例化蓝牙设备、扫描附近可发现的蓝牙设备的工具类
- Constans:大量的常量,用于表示蓝牙的各种行为状态
- startDiscovery():开始扫描附近可发现的蓝牙设备
- cacelDiscovery():取消扫描,在蓝牙配对前执行该方法。
- getBondedDevices():获取已经配对过的蓝牙设备,以Set形式返回
- getState():获取当前蓝牙适配器的状态:
STATE_OFF,STATE_TURNING_ON,STATE_ON, orSTATE_TURNING_OFF - BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord (String name, UUID uuid):创建一个socket,作为Server端
- BluetoothDevice:远程连接蓝牙设备。用于创建与相应设备的连接或查询有关设备的信息,例如名称、地址、类和绑定状态。
- constans:表示蓝牙一些行为的常量。
- BluetoothSocket createRfcommSocketToServiceRecord (UUID uuid):创建socket,用于作为client端。
2、客户端开发
下方一切的findViewById()需要自己创建,能运行就行,保证基本的逻辑完好!
<!--添加蓝牙互联的权限-->
<!--允许App连接到配对的蓝牙设备,兼容低版本-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!--允许App发现和配对蓝牙设备,兼容低版本-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!--允许App扫描蓝牙设备-->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!--当前设备可被发现-->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!--App需要与蓝牙设备配对、通讯-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!--App需要获取物理位置-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
1. 初始化蓝牙相关配置
private val TAG = javaClass.simpleName
private var mBluetoothManager: BluetoothManager? = null
var mBluetoothAdapter: BluetoothAdapter? = null
private var mContext: Context? = null
/**
* 用来初始化蓝牙的相关配置!
*/
fun init(context: Context): Boolean {
mContext = context
mBluetoothManager =
mContext?.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (mBluetoothManager == null) {
Log.e(TAG, "BluetoothManager初始化失败!")
return false
}
mBluetoothAdapter = mBluetoothManager!!.adapter
if (mBluetoothAdapter == null) {
Log.e(TAG, "无法获取蓝牙适配器bluetoothAdapter!")
return false
}
if (!checkIsSupportBluetooth()) {
Log.e(TAG, "设备不支持蓝牙!")
return false
}
return true
}
/**
* 检查设备是否支持蓝牙
*/
private fun checkIsSupportBluetooth(): Boolean {
return mBluetoothAdapter!!.isEnabled
}
2.申请权限
private val permissions = arrayOf(
android.Manifest.permission.BLUETOOTH_SCAN,
android.Manifest.permission.BLUETOOTH_ADVERTISE,
android.Manifest.permission.BLUETOOTH_CONNECT,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION
)
/**
* 请求权限
*/
// onCreate()中
if (permissions.find { ActivityCompat.checkSelfPermission(this,it) != PackageManager.PERMISSION_GRANTED } != null){
// 调用下面的launcher
requestMultiplePermissionLauncher.launch(permissions)
}else{
}
private val requestMultiplePermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions: Map<String, Boolean> ->
val noGrantedPermissions = ArrayList<String>()
permissions.entries.forEach {
if (!it.value) {
noGrantedPermissions.add(it.key)
}
}
if (noGrantedPermissions.isEmpty()) {
Log.i(TAG,"权限全部申请通过!")
// 所有申请权限通过,可以执行后续操作
} else {
Log.e(TAG,"权限未通过授权!")
//未同意授权
noGrantedPermissions.forEach {
if (!shouldShowRequestPermissionRationale(it)) {
//用户拒绝权限并且系统不再弹出请求权限的弹窗
//这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
}
}
}
}
3.开启广播
蓝牙状态改变的广播、获取扫描到附近蓝牙设备的广播
private val mBluetoothDeviceList: ArrayList<BluetoothDevice> = ArrayList()
private val mBluetoothDeviceData: ArrayList<String> = ArrayList()
private lateinit var deviceListView: ListView
private lateinit var deviceViewAdapter: ArrayAdapter<String>
private lateinit var mBluetoothStateReceiver: BluetoothStateReceiver
private lateinit var mScanBluetoothReceiver: ScanBluetoothDeviceReceiver
override fun onCreate(savedInstanceState: Bundle?) {
val btStateFilter = IntentFilter().apply {
addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
}
// 开启检测蓝牙状态的监听器
mBluetoothStateReceiver = BluetoothStateReceiver()
registerReceiver(mBluetoothStateReceiver, btStateFilter)
// 初始化ListView
deviceListView = findViewById(R.id.available_device)
// 注册广播接受,监听扫描结果
val scanBtButton = findViewById<Button>(R.id.scan_bt)
scanBtButton.setOnClickListener {
mScanBluetoothReceiver = ScanBluetoothDeviceReceiver()
AlertDialog.Builder(this)
.setTitle("蓝牙搜索")
.setMessage("是否开始搜索当前可发现的所有的蓝牙设备?")
.setIcon(R.mipmap.icon)
.setNegativeButton("取消", null)
.setNeutralButton("确认") { p0, p1 ->
// 开启广播
Log.d(TAG, "开启搜索蓝牙的广播")
mBluetoothHelper.mBluetoothAdapter?.cancelDiscovery()
clearDeviceList()
// 添加需要拦截的蓝牙行为
val scanIntentFilter = IntentFilter(BluetoothDevice.ACTION_FOUND)
scanIntentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
scanIntentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
// 注册广播
registerReceiver(mScanBluetoothReceiver, scanIntentFilter)
Log.d(TAG, "蓝牙适配器是否为空:${mBluetoothHelper.mBluetoothAdapter == null}")
// 注册后,开始搜索附近的蓝牙设备
mBluetoothHelper.mBluetoothAdapter?.startDiscovery()
}.create().show()
}
}
/**
* 监测蓝牙状态的广播:监听器
*/
private inner class BluetoothStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "初始化广播,准备监听蓝牙状态...")
val action = intent!!.action
Log.d(TAG, "适配器中:actionStateChanged:$action")
val btStateView = findViewById<TextView>(R.id.bt_is_open)
if (BluetoothAdapter.ACTION_STATE_CHANGED == action) {
val state =
intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, DEFAULT_VALUE_BULETOOTH)
Log.d(TAG, "state = $state")
when (state) {
// 蓝牙关闭
BluetoothAdapter.STATE_OFF -> {
Log.d(TAG, "蓝牙处于关闭...")
btStateView.text = "未开启"
btStateView.setTextColor(Color.RED)
}
BluetoothAdapter.STATE_ON -> {
Log.d(TAG, "蓝牙处于开启...")
btStateView.text = "开启中"
btStateView.setTextColor(Color.GREEN)
}
}
}
when (action) {
// 由于蓝牙设备连接成功后,没有能够之间判断是否连接的常量,我们使用handler来操作判断。断开连接有常量字,可以直接使用
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
Log.d(TAG, "断开连接!")
btStateView.text = "未连接"
btStateView.setTextColor(Color.RED)
clearDeviceList()
findViewById<LinearLayout>(R.id.device_list).visibility = View.VISIBLE
findViewById<LinearLayout>(R.id.send_data_view).visibility = View.GONE
findViewById<Button>(R.id.scan_bt).visibility = View.VISIBLE
}
}
}
}
/**
* 蓝牙设备的监听器,即监听蓝牙扫描时的设备
*/
private inner class ScanBluetoothDeviceReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "开始搜索可发现的蓝牙设备...")
when (intent!!.action) {
BluetoothDevice.ACTION_FOUND -> {
// 发现的蓝牙设备
val device: BluetoothDevice? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE,
BluetoothDevice::class.java
)
} else {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
device!!
if (mBluetoothDeviceList.size > 0 && !mBluetoothDeviceList.contains(device)) {
if (device.name != null) {
mBluetoothDeviceData.add("${device.name},物理地址:${device.address}")
mBluetoothDeviceList.add(device)
}
}
if (mBluetoothDeviceList.size == 0 && device.name != null) {
mBluetoothDeviceData.add("${device.name},物理地址:${device.address}")
mBluetoothDeviceList.add(device)
}
Log.d(TAG, "搜索到蓝牙设备:name = ${device.name}")
}
BluetoothAdapter.ACTION_DISCOVERY_STARTED -> {
Toast.makeText(context, "开始搜索可供发现的蓝牙设备...", Toast.LENGTH_SHORT)
.show()
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
Toast.makeText(context, "搜索完成...", Toast.LENGTH_SHORT).show()
}
}
deviceViewAdapter = ArrayAdapter(context!!, android.R.layout.simple_list_item_1, mBluetoothDeviceData)
deviceListView.adapter = deviceViewAdapter
}
}
/**
* 清除数据的工具类
*/
private fun clearDeviceList() {
if (mBluetoothDeviceList.size > 0) {
Log.d(TAG, "清除数据!")
mBluetoothDeviceData.clear()
mBluetoothDeviceList.clear()
deviceViewAdapter.notifyDataSetChanged()
Log.d(
TAG,
"清除结果:deviceData.size=${mBluetoothDeviceData.size},deviceList.size=${mBluetoothDeviceList.size}"
)
}
}
4.通过ListView的适配器,添加item的点击事件
private val handler = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
Log.d(TAG, "msg.what=${msg.what}")
when (msg.what) {
BluetoothMessageType.MESSAGE_READ -> {
val getDataView = findViewById<TextView>(R.id.get_data)
// 处理数据 byte[] 转 String
val receiveData = msg.obj as ByteArray
val data = String(receiveData, StandardCharsets.UTF_8)
Log.d(TAG, "接收到数据:$data")
getDataView.text = data
}
BluetoothMessageType.MESSAGE_TOAST -> {
val data = msg.data
val toastMsg = data.getString("toast")
Toast.makeText(this@MainActivity, toastMsg, Toast.LENGTH_SHORT).show()
}
// 连接成功后,发送handler消息,操作蓝牙展示的组件隐藏,发送数据的组件显示
BluetoothMessageType.CONNECT_SUCCESS -> {
Log.d(TAG, "连接成功!")
val btStateView = findViewById<TextView>(R.id.bt_is_open)
btStateView.text = "设备配对中"
btStateView.setTextColor(Color.GREEN)
clearDeviceList()
findViewById<LinearLayout>(R.id.device_list).visibility = View.GONE
findViewById<LinearLayout>(R.id.send_data_view).visibility = View.VISIBLE
findViewById<Button>(R.id.scan_bt).visibility = View.GONE
}
}
}
}
// onCreate()
deviceListView.onItemClickListener =
AdapterView.OnItemClickListener { parent, view, position, long ->
Log.d(TAG, "clicking index = $position")
val clickingDevice = mBluetoothDeviceList[position]
AlertDialog.Builder(this).setTitle("蓝牙连接")
.setMessage("是否连接蓝牙:${clickingDevice.name}")
.setIcon(R.mipmap.icon)
.setNegativeButton("取消", null)
.setPositiveButton("确认") { p0, p1 ->
Log.d(TAG, "准备连接蓝牙")
mBluetoothHelper.mBluetoothAdapter?.cancelDiscovery()
// 连接蓝牙应该在子线程中进行,为耗时操作
mBluetoothTransferController = BluetoothTransferController(handler)
mBluetoothTransferController.connectBluetoothDevice(clickingDevice)
if (mBluetoothTransferController.isConnected()) {
handler.obtainMessage(BluetoothMessageType.CONNECT_SUCCESS,"连接成功!").sendToTarget()
}
// 开启广播,监听蓝牙连接结果
}.create().show()
}
5.编写设备互联的工具类
耗时、阻塞的方法,不能在主线程中运行
class BluetoothTransferController(private val handler: Handler) {
private val TAG = javaClass.simpleName
private val bluetoothUUID = UUID.fromString("fc5deb71-9d4b-460b-b725-b06ea79bda5a")
private var connectThread: ConnectThread? = null
private var mBluetoothSocket:BluetoothSocket? = null
/**
* 连接设备的线程
*/
inner class ConnectThread(private val bluetoothDevice: BluetoothDevice) : Thread() {
// 懒加载
private val bluetoothSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
Log.d(TAG,"开始建立socket")
bluetoothDevice.createRfcommSocketToServiceRecord(bluetoothUUID)
}
override fun run() {
super.run()
bluetoothSocket?.run {
try {
mBluetoothSocket = this
Log.d(TAG,"开始配对设备连接!")
connect()
initTransferDataThread()
} catch (e: IOException) {
try {
close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
fun cancel() {
try {
bluetoothSocket?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
fun connectBluetoothDevice(bluetoothDevice: BluetoothDevice) {
Log.d(TAG,"连接蓝牙设备!")
if (connectThread != null) {
connectThread?.cancel()
connectThread = null
}
connectThread = ConnectThread(bluetoothDevice)
connectThread?.start()
}
fun isConnected() : Boolean{
Log.i(TAG,"是否连接?")
if (mBluetoothSocket == null) {
return false
}
return mBluetoothSocket!!.isConnected
}
inner class TransferDataThread(private val bluetoothSocket: BluetoothSocket) : Thread() {
private var inputStream: InputStream? = null
private var outputStream: OutputStream? = null
private var connected = false
private val mBuffer = ByteArray(1024)
init {
Log.d(TAG,"传输数据的线程初始化中!")
connected = bluetoothSocket.isConnected
try {
if (connected) {
handler.obtainMessage(BluetoothMessageType.CONNECT_SUCCESS,"连接成功!").sendToTarget()
Log.d(TAG,"初始化input和output流...")
inputStream = bluetoothSocket.inputStream
outputStream = bluetoothSocket.outputStream
}
} catch (e: IOException) {
Log.e(TAG,"初始化出现错误!",e)
e.printStackTrace()
}
}
override fun run() {
Log.d(TAG,"传输数据的run线程运行中")
var numBytes:Int
while (true) {
numBytes = try {
inputStream!!.read(mBuffer)
} catch (e: IOException) {
Log.e(TAG,"读取数据发生错误!",e)
break
}
val readMsg = handler.obtainMessage(BluetoothMessageType.MESSAGE_READ,numBytes,-1,mBuffer)
readMsg.sendToTarget()
}
}
fun writeData(byte: ByteArray) {
Log.d(TAG,"传输数据的writeData函数...")
try {
outputStream!!.write(byte)
} catch (e: IOException) {
Log.e(TAG,"发送数据发生错误!",e)
cancel()
handler.obtainMessage(BluetoothMessageType.MESSAGE_TOAST,"不能发送数据到互联的设备中...").sendToTarget()
return
}
}
fun cancel(){
try {
bluetoothSocket.close()
} catch (e: IOException) {
Log.e(TAG,"关闭socket连接失败!",e)
}
}
}
// 提取工具类,直接调用
fun initTransferDataThread(){
if (mBluetoothSocket == null) {
Log.d(TAG,"socket未建立,无法写入数据")
handler.obtainMessage(MessageType.MESSAGE_TOAST,"socket未建立,无法写入数据")
return
}
if (transferDataThread == null) {
Log.d(TAG,"传输数据线程未开启,准备初始化!")
transferDataThread = TransferDataThread(mBluetoothSocket!!)
}
Log.d(TAG,"准备写入数据!")
transferDataThread!!.start()
}
fun writeData(data: ByteArray) {
transferDataThread!!.writeData(data)
}
}
6.连接设备
findViewById<Button>(R.id.send_btn).setOnClickListener {
Thread {
val sendData = findViewById<EditText>(R.id.send_data)
if (sendData.text != null) {
// socket只能传输byte[]数据
val toByte = sendData.text.toString().toByteArray()
// 启动handler
mBluetoothTransferController.writeData(toByte)
}
}.start()
}
3、服务端开发
添加相应设备互联的权限
1.连接线程
public class BluetoothConnectController extends Thread {
private final String TAG = getClass().getSimpleName();
private final UUID bluetoothUUID = UUID.fromString("fc5deb71-9d4b-460b-b725-b06ea79bda5a");
private final BluetoothAdapter mAdapter;
private final BluetoothServerSocket mSocket;
private final Handler mHandler;
@SuppressLint("MissingPermission")
public BluetoothConnectController(BluetoothAdapter adapter, Handler handler) {
mHandler = handler;
mAdapter = adapter;
BluetoothServerSocket tmp;
try {
tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord("bluetooth_connection", bluetoothUUID);
} catch (IOException e) {
throw new RuntimeException(e);
}
mSocket = tmp;
}
@Override
public void run() {
Log.i(TAG, "建立连接!");
BluetoothSocket socket;
// 建立长连接
while (true) {
try {
Log.i(TAG, "socket开始连接!");
socket = mSocket.accept();
Log.i(TAG, "socket连接完成!");
// 连接成功,开启传输数据的线程
transferData(socket);
} catch (IOException e) {
Log.i(TAG, "socket建立出现错误!", e);
break;
}
}
}
public void transferData(BluetoothSocket socket) {
new Thread(() -> {
while (true) {
byte[] res = new byte[1024];
// todo 建立连接处理数据
try {
InputStream inputStream = socket.getInputStream();
inputStream.read(res);
Log.i("MainActivity", "res=" + new String(res));
// 发送到主线程
Message message = new Message();
message.what = 1;
message.obj = res;
mHandler.sendMessage(message);
OutputStream outputStream = socket.getOutputStream();
String reply = "receive data";
outputStream.write(reply.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
cancel();
break;
}
}
}).start();
}
public void cancel() {
try {
mSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2.创建service
public class BluetoothHelperBinder extends Binder {
private final String TAG = "MainActivity";
private BluetoothConnectController mThread = null;
public void initConnection(BluetoothAdapter bluetoothAdapter, Handler handler) {
Log.i(TAG, "initConnection()");
mThread = new BluetoothConnectController(bluetoothAdapter, handler);
Log.i(TAG, "建立socket流!");
mThread.start();
Log.i(TAG, "开启线程成功!");
}
public void closeConnection(){
mThread.cancel();
}
}
public class BluetoothService extends Service {
private final BluetoothHelperBinder mBinder = new BluetoothHelperBinder();
public BluetoothService() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
Log.i("MainActivity", "bluetoothService's onCreate!");
}
@Override
public void onDestroy() {
super.onDestroy();
mBinder.closeConnection();
}
}
3.开启服务,socket进行数据传输
public class MainActivity extends AppCompatActivity {
private final String TAG = getClass().getSimpleName();
private final MyHandler handler = new MyHandler(this);
private BluetoothAdapter mAdapter = null;
private BluetoothServiceConnection connection = null;
private boolean isBinder = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
Log.i(TAG, "onCreate!");
// 因为蓝牙和热点的连接都是长连接,无法同时开启两个长连接,则重新开启一个activity
findViewById(R.id.btn).setOnClickListener(view -> {
startActivity(new Intent(this,APActivity.class));
});
// 开启一个蓝牙状态的监听器
IntentFilter intentFilter = new IntentFilter();
// 拦截断开连接的行为,监测到后,重新创建蓝牙的服务器等待被连接
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
registerReceiver(new bluetoothStateReceiver(), intentFilter);
openBluetoothConnection();
}
private void openBluetoothConnection() {
mAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
connection = new BluetoothServiceConnection();
//开启service
isBinder = bindService(
new Intent(this, BluetoothService.class),
connection,
Context.BIND_AUTO_CREATE);
if (isBinder) {
Toast.makeText(this, "绑定服务成功!", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
private static class MyHandler extends Handler {
WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
byte[] obj = ((byte[]) msg.obj);
String data = new String(obj, StandardCharsets.UTF_8);
Log.i("MainActivity", "接收到数据:" + data);
MainActivity activity = mActivity.get();
if (msg.what == 1) {
TextView dataView = activity.findViewById(R.id.bluetooth_data);
dataView.setText(data);
}
}
}
private class bluetoothStateReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 断开连接
if (Objects.equals(action, BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
Log.i(TAG, "设备断开连接!");
unbindService(connection);
// 重新开启线程,开始等待被连接
openBluetoothConnection();
}
}
}
private class BluetoothServiceConnection implements ServiceConnection {
private BluetoothHelperBinder bluetoothHelperBinder = null;
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.i(TAG, "onServiceConnected()");
bluetoothHelperBinder = ((BluetoothHelperBinder) iBinder);
bluetoothHelperBinder.initConnection(mAdapter, handler);
Log.i(TAG, "初始化连接成功!");
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
bluetoothHelperBinder.closeConnection();
}
}
}
三、热点互联的开发
热点互联与蓝牙很相似,连接成功后都是通过socket套接字传输数据
1、配置清单添加权限
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
2、客户端开发
1.连接线程
public class WifiConnectController extends Thread {
private final String TAG = getClass().getSimpleName();
private final Socket mSocket;
private final Handler mHandler;
private InputStream mInputStream;
private OutputStream mOutputStream;
public WifiConnectController(Socket socket, Handler handler) {
Log.i(TAG, "ConnectThread()");
mSocket = socket;
mHandler = handler;
}
@Override
public void run() {
if (mSocket == null) {
return;
}
// 标志设备连接中
mHandler.obtainMessage(WifiMessageType.CONNECT_SUCCESS).sendToTarget();
while (true) {
try {
mInputStream = mSocket.getInputStream();
mOutputStream = mSocket.getOutputStream();
// 每次读取1kb缓存的数据
byte[] buffer = new byte[1024];
int bytes;
// 执行循环开始操作数据
bytes = mInputStream.read(buffer);
Log.d(TAG, "读取到数据:" + Arrays.toString(buffer));
if (bytes > 0) {
mHandler.obtainMessage(WifiMessageType.MESSAGE_READ,buffer).sendToTarget();
}
} catch (IOException e) {
mHandler.obtainMessage(WifiMessageType.TOAST, "socket流读取input和output出现错误!");
Log.e(TAG, "socket流读取input和output出现错误!", e);
break;
}
}
}
public void write(byte[] bytes) {
if (mOutputStream == null) {
Log.e(TAG, "outputStream流为null,无法写入数据!");
}
Log.i(TAG, "正在执行写数据!");
try {
Log.i(TAG,"数据:" + Arrays.toString(bytes));
mOutputStream.write(bytes);
} catch (IOException e) {
Log.e(TAG, "写数据出现错误!", e);
mHandler.obtainMessage(WifiMessageType.TOAST,"写数据出现错误!").sendToTarget();
}
}
}
2.启动线程
public class WifiActivity extends AppCompatActivity {
private final String TAG = getClass().getSimpleName();
private WifiConnectController wifiConnectController;
private final MyHandler mHandler = new MyHandler(this);
private Socket socket = null;
private String ip = null;
private int port;
private static class MyHandler extends Handler {
WeakReference<WifiActivity> mActivity;
public MyHandler(WifiActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
Log.i("MainActivity", "handleMessage");
WifiActivity activity = mActivity.get();
TextView dataView = activity.findViewById(R.id.data1);
switch (msg.what) {
case WifiMessageType.TOAST:{
Toast.makeText(activity, msg.obj.toString(), Toast.LENGTH_SHORT).show();
}
case WifiMessageType.MESSAGE_READ:{
byte[] obj = ((byte[]) msg.obj);
String data = new String(obj, StandardCharsets.UTF_8);
Log.i("MainActivity", "接收到数据:" + data);
dataView.setText(data);
}
case WifiMessageType.CONNECT_SUCCESS:{
Toast.makeText(activity, "socket连接成功!", Toast.LENGTH_SHORT).show();
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wifi);
findViewById(R.id.connect_btn).setOnClickListener(view -> {
Editable ipText = ((EditText) findViewById(R.id.connect_ip)).getText();
Editable portText = ((EditText) findViewById(R.id.connect_port)).getText();
if (ipText != null && portText != null) {
ip = ipText.toString();
port = Integer.parseInt(portText.toString());
}
Log.i(TAG, "连接的服务器:" + ip + ":" + port);
new Thread(() -> {
try {
// 先判断是否已经连接,如果已经连接,就不需要再次连接
if (socket == null) {
//socket = new Socket(InetAddress.getByName(IP),PORT);
socket = new Socket();
socket.connect(new InetSocketAddress(ip, port), 5000);
Log.i(TAG, "连接成功!");
}else {
Log.i(TAG,"已经连接到服务器,不需要再次重连!");
mHandler.obtainMessage(WifiMessageType.TOAST,"已经连接到服务器,不需要再次重连!").sendToTarget();
}
} catch (IOException e) {
Log.e(TAG, "连接socket出现错误!", e);
mHandler.obtainMessage(WifiMessageType.TOAST,"socket连接错误,无法发送数据!").sendToTarget();
return;
}
// 开启ap互联的线程
wifiConnectController = new WifiConnectController(socket, mHandler);
wifiConnectController.start();
}).start();
});
// 获取需要发送的数据
EditText dataView = findViewById(R.id.send_data1);
findViewById(R.id.send_btn1).setOnClickListener(view1 -> {
if (socket != null) {
new Thread(() -> {
// 转换为byte[],用于socket中传输
byte[] bytes = dataView.getText().toString().getBytes(StandardCharsets.UTF_8);
wifiConnectController.write(bytes);
}).start();
} else {
Toast.makeText(this, "socket为空,无法传输数据", Toast.LENGTH_SHORT).show();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
3、服务器开发
1.连接线程
public class WifiConnectController extends Thread {
private final String TAG = getClass().getSimpleName();
private ServerSocket mSocket;
private final Handler mHandler;
private InputStream mInputStream;
private OutputStream mOutputStream;
public WifiConnectController(Handler handler) {
Log.i(TAG, "ConnectThread()构造方法中");
mHandler = handler;
}
@Override
public void run() {
Socket socket = null;
try {
InetSocketAddress inetSocketAddress = new InetSocketAddress(InetAddress.getByName("192.168.144.89"),3150);
mSocket = new ServerSocket();
mSocket.bind(inetSocketAddress);
Log.i(TAG, "初始化socket服务器,端口为:3150");
} catch (IOException e) {
Log.e(TAG, "初始化socket出现错误!", e);
return;
}
// 开启长连接
while (true) {
// 阻塞调用
try {
Log.i(TAG, "socket初始化完成,等待被连接!");
socket = mSocket.accept();
// 如果服务器建立完成,获取输入流和输出流
mInputStream = socket.getInputStream();
mOutputStream = socket.getOutputStream();
Log.i(TAG, "socket连接完成!");
} catch (IOException e) {
mHandler.sendEmptyMessage(-1);
Log.i(TAG, "socket初始化错误!");
break;
}
// 读取数据
readData();
}
}
public void readData(){
while (true) {
try {
// 每次读取1kb缓存的数据
byte[] buffer = new byte[1024];
int bytes;
bytes = mInputStream.read(buffer);
Log.i(TAG, "buffer=" + new String(buffer) + ",bytes=" + bytes);
if (bytes > 0) {
Message message = Message.obtain();
message.what = 2;
message.obj = buffer;
// 发送到handler
mHandler.sendMessage(message);
String reply = "服务器接受到数据!";
byte[] replyBytes = reply.getBytes(StandardCharsets.UTF_8);
mOutputStream.write(replyBytes);
}
} catch (IOException e) {
mHandler.sendEmptyMessage(-1);
Log.e(TAG, "读取数据出现错误!即将关闭socket", e);
cancel();
break;
}
}
}
public void cancel() {
try {
if (mSocket != null) {
mHandler.sendEmptyMessage(-1);
mSocket.close();
mInputStream.close();
mOutputStream.close();
}
} catch (IOException e) {
Log.e(TAG, "关闭socket通道失败!", e);
}
}
}
2.开启线程
public class APActivity extends AppCompatActivity {
private WifiConnectController wifiConnectController;
private final MyHandler handler = new MyHandler(this);
private static class MyHandler extends Handler {
WeakReference<APActivity> mActivity;
public MyHandler(APActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
byte[] obj = ((byte[]) msg.obj);
String data = new String(obj, StandardCharsets.UTF_8);
Log.i("MainActivity", "接收到数据:" + data);
APActivity activity = mActivity.get();
// 接受到客户端传输的数据
if (msg.what == 2) {
TextView dataView = activity.findViewById(R.id.ap_data);
dataView.setText(data);
}
// wifi断开连接
if (msg.what == -1) {
activity.openAPConnection();
}
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_wifi);
openAPConnection();
}
private void openAPConnection(){
wifiConnectController = new WifiConnectController(handler);
wifiConnectController.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
wifiConnectController.cancel();
}
}
4、ap互联发现的问题:
1.客户端报连接失败的异常
- linux的防火墙未关闭,被拦截
# 清空防火墙规则
iptables -F
- 服务端开启的热点的ip和port未生效,或者在代码中传入的ip和port与开启的热点不一样
# 查看ip
adb shell su
ifconfig
# 显示tcp相关的在监听服务状态的ip地址和socket程序识别码和程序名称
netstat -nlpt