安卓第六次作业

1. 实验环境:

Windows系统,Android Studio

2. 实现功能

使用手机蓝牙实现会话功能,即两个设备之间通过蓝牙互发消息。

3. 蓝牙通信的步骤(参考文档

  1. 首先开启蓝牙
  2. 搜索可用设备
  3. 创建蓝牙socket,获取输入输出流
  4. 读取和写入数据
  5. 断开连接关闭蓝牙

首先要知道几个类,BluetoothAdapter,BluetoothGatt,BluetoothDevice,BluetoothCattService,BluetoothCattCharacteristic。

第一个是蓝牙设配器,对蓝牙的操作都需要用到它,很重要,BluetoothGatt作为中央来使用和处理数据,使用时有一个回调方法BluetoothGattCallback返回中央的状态和周边提供的数据,BluetoothCattService作为周边来提供数据;BluetoothGattServerCallback返回周边的状态。BluetoothDevice是蓝牙设备,BluetoothCattCharacteristic是蓝牙设备的特征。

4. 蓝牙通信的实现

4.1 界面展示

虚拟机上界面如下:

发送消息界面

 寻找与连接好友界面

 

为了实现连接的效果,我们在两台真机上面下载该软件,然后进行蓝牙通信,结果如图:

 可以看到一台真机名为OPPO A1,一台为嘿嘿,两台之间成功通过蓝牙完成了通信。

4.2 实现代码

4.2.1 在清单文件中注册权限

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

这里我们需要添加接下来需要用到的一些权限。

4.2.2 添加程序运行过程中的状态描述文本及配色代码等配置

<resources>
    <string name="app_name">BlueToothDemo</string>
    <string name="send">发送</string>
    <string name="not_connected">你没有链接一个设备</string>
    <string name="bt_not_enabled_leaving">蓝牙不可用,离开聊天室</string>
    <string name="title_connecting">链接中...</string>
    <string name="title_connected_to">连接到:</string>
    <string name="title_not_connected">无链接</string>
    <string name="scanning">蓝牙设备搜索中...</string>
    <string name="select_device">选择一个好友链接</string>
    <string name="none_paired">没有配对好友</string>
    <string name="none_found">附近没有发现好友</string>
    <string name="title_paired_devices">已配对好友</string>
    <string name="title_other_devices">其它可连接好友</string>
    <string name="button_scan">搜索好友</string>
    <string name="connect">我的好友</string>
    <string name="discoverable">设置在线</string>
    <string name="back">退出</string>
    <string name="startVideo">开始聊天</string>
    <string name="stopVideo">结束聊天</string>
</resources>

4.2.3 activity_main.xml文件配置

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#E6CAFF">
    tools:context=".MainActivity">
    <!--新版Android支持的Toolbar,对标题栏布局-->
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/title_left_text"
                style="?android:attr/windowTitleStyle"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_alignParentLeft="true"
                android:layout_weight="1"
                android:gravity="left"
                android:ellipsize="end"
                android:singleLine="true" />
            <TextView
                android:id="@+id/title_right_text"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_alignParentRight="true"
                android:layout_weight="1"
                android:ellipsize="end"
                android:gravity="right"
                android:singleLine="true"
                android:textColor="#fff" />
        </LinearLayout>
    </androidx.appcompat.widget.Toolbar>

    <ListView android:id="@+id/in"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:stackFromBottom="true"
        android:transcriptMode="alwaysScroll"
        android:layout_weight="1" />
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <EditText
            android:id="@+id/edit_text_out"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:layout_weight="1"
            tools:ignore="SpeakableTextPresentCheck" />
        <Button android:id="@+id/button_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/send"/>
    </LinearLayout>

</LinearLayout>

    在这里我们要添加1个Toolbar控件,其内包含2个水平的TextView控件;在Toolbar控件的下方添加1个ListView控件,用于显示聊天内容;最后在ListView控件的下方添加水平放置的1个EditText控件和一个Button控件。使用垂直线性布局并嵌套水平线性布局

4.2.4 用于蓝牙会话的服务组件ChatService(这里只展示部分代码,全部代码可查看git仓库

/*
    本程序ChatService是蓝牙会话的服务程序
    定义了3个内部类:AcceptThread(接受新连接)、ConnectThread(发出连接)和ConnectedThread (已连接)
*/
public class ChatService {
    //本应用的主Activity组件名称
    private static final String NAME = "BluetoothChat";
    // UUID:通用唯一识别码,是一个128位长的数字,一般用十六进制表示
    //算法的核心思想是结合机器的网卡、当地时间、一个随机数来生成
    //在创建蓝牙连接
    private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
    private final BluetoothAdapter mAdapter;
    private final Handler mHandler;
    private AcceptThread mAcceptThread;
    private ConnectThread mConnectThread;
    private ConnectedThread mConnectedThread;
    private int mState;
    public static final int STATE_NONE = 0;
    public static final int STATE_LISTEN = 1;
    public static final int STATE_CONNECTING = 2;
    public static final int STATE_CONNECTED = 3;

    //构造方法,接收UI主线程传递的对象
    public ChatService(Context context, Handler handler) {
        //构造方法完成蓝牙对象的创建
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mState = STATE_NONE;
        mHandler = handler;
    }

    private synchronized void setState(int state) {
        mState = state;
        mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
    }

    public synchronized int getState() {
        return mState;
    }
}

本组件ChatService是蓝牙会话的服务程序, 定义了3个内部线程类:AcceptThread(接受新连接线程)、ConnectThread(发出连接线程)和ConnectedThread (已连接线程)

4.2.5 分别建立供主Activity使用的菜单文件res/menu/optionmenu.xml、选择好友(即已经配对过的蓝牙设备)的界面布局文件devicelist.xml

  • 菜单文件option_menu.xml的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/scan"
        android:icon="@android:drawable/ic_menu_myplaces"
        android:title="@string/connect" />
    <item android:id="@+id/discoverable"
        android:icon="@android:drawable/ic_menu_view"
        android:title="@string/discoverable" />
    <item android:id="@+id/back"
        android:icon="@android:drawable/ic_menu_close_clear_cancel"
        android:title="@string/back" />
</menu>
  • 选择好友界面的布局文件device_list.xml的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView android:id="@+id/title_paired_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/title_paired_devices"
        android:visibility="gone"
        android:background="#666"
        android:textColor="#fff"
        android:paddingLeft="5dp" />
    <ListView android:id="@+id/paired_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1" />
    <TextView android:id="@+id/title_new_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/title_other_devices"
        android:visibility="gone"
        android:background="#666"
        android:textColor="#fff"
        android:paddingLeft="5dp" />
    <!--android:visibility="gone"表示不占空间的隐藏,invisible是占空间-->
    <ListView android:id="@+id/new_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2" />
    <Button android:id="@+id/button_scan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_scan" />
</LinearLayout>

4.2.6 Activity组件DeviceList (这里只展示部分代码,全部代码可查看git仓库

/*
        本程序供菜单项主界面的选项菜单“我的好友”调用,用于:
        (1)显示已配对的好友列表;
        (2)搜索可配对的好友进行配对
        (3)新选择并配对的蓝牙设备将刷新好友列表
        注意:发现新的蓝牙设备并请求配对时,需要对应接受
        关键技术:动态注册一个广播接收者,处理蓝牙设备扫描的结果
    */
public class DeviceList extends AppCompatActivity {
    private BluetoothAdapter mBtAdapter;
    private ArrayAdapter<String> mPairedDevicesArrayAdapter;
    private ArrayAdapter<String> mNewDevicesArrayAdapter;
    public static String EXTRA_DEVICE_ADDRESS = "device_address";  //Mac地址
    //定义广播接收者,用于处理扫描蓝牙设备后的结果
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                if (mNewDevicesArrayAdapter.getCount() == 0) {
                    String noDevices = getResources().getText(R.string.none_found).toString();
                    mNewDevicesArrayAdapter.add(noDevices);
                }
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.device_list);
        //在被调用活动里,设置返回结果码
        setResult(Activity.RESULT_CANCELED);
        init();  //活动界面
    }
}

本组件用于菜单项主界面的选项菜单“我的友好”调用:
(1)显示已配对的好友列表;
(2)搜索可配对的好友进行配对
(3)新选择并配对的蓝牙设备将刷新好友列表
这里我们选择的是动态注册一个广播接收者,处理蓝牙设备扫描的结果

4.2.7 主程序BluetoothChat  (这里只展示部分代码,全部代码可查看git仓库

public class BluetoothChat extends AppCompatActivity {

    public static final int MESSAGE_STATE_CHANGE = 1;
    public static final int MESSAGE_READ = 2;
    public static final int MESSAGE_WRITE = 3;
    public static final int MESSAGE_DEVICE_NAME = 4;
    public static final int MESSAGE_TOAST = 5;
    public static final String DEVICE_NAME = "device_name";
    public static final String TOAST = "toast";
    private static final int REQUEST_CONNECT_DEVICE = 1;  //请求连接设备
    private static final int REQUEST_ENABLE_BT = 2;
    private TextView mTitle;
    private ListView mConversationView;
    private EditText mOutEditText;
    private Button mSendButton;
    private String mConnectedDeviceName = null;
    private ArrayAdapter<String> mConversationArrayAdapter;
    private StringBuffer mOutStringBuffer;
    private BluetoothAdapter mBluetoothAdapter = null;
    private ChatService mChatService = null;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportActionBar().hide();  //隐藏标题栏
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
            }
        }
        Toolbar toolbar = findViewById(R.id.toolbar);
        //创建选项菜单
        toolbar.inflateMenu(R.menu.option_menu);
        //选项菜单监听
        toolbar.setOnMenuItemClickListener(new MyMenuItemClickListener());
        mTitle = findViewById(R.id.title_left_text);
        mTitle.setText(R.string.app_name);
        mTitle = findViewById(R.id.title_right_text);
        // 得到本地蓝牙适配器
        checkBlePermission();
        checkBleDevice();
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "蓝牙不可用", Toast.LENGTH_LONG).show();
            finish();
            return;
        }
        if (!mBluetoothAdapter.isEnabled()) { //若当前设备蓝牙功能未开启
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent, REQUEST_ENABLE_BT); //
        } else {
            if (mChatService == null) {
                setupChat();  //创建会话
            }
        }
    }
}

5. 实验小结

       本次实验通过老师给的案例,虽然中间有许多的错误,但是在CSDN的帮助下,也是一一解决了这些问题,然后因为虚拟机的原因,不能进行蓝牙的连接,所以我查询相关文档,知道了打包成apk的方法,随后在两台手机上进行真机操作,最后实现了两台手机之间的蓝牙通信。

点击查看我的源码

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我来为您解答。 首先,我们需要在布局文件中添加一个 RelativeLayout 控件: ```xml <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 这里添加九宫格子的布局 --> </RelativeLayout> ``` 然后,我们需要在 RelativeLayout 中添加九宫格子的布局。这里我们采用 ImageView 控件作为九宫格子,每个格子的大小相同,可以采用权重的方式实现。 ```xml <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/image1" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="5dp" android:layout_centerInParent="true" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_weight="1" android:src="@drawable/image1" /> <ImageView android:id="@+id/image2" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="5dp" android:layout_centerInParent="true" android:layout_alignParentTop="true" android:layout_toRightOf="@id/image1" android:layout_toEndOf="@id/image1" android:layout_weight="1" android:src="@drawable/image2" /> <ImageView android:id="@+id/image3" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="5dp" android:layout_centerInParent="true" android:layout_alignParentTop="true" android:layout_toRightOf="@id/image2" android:layout_toEndOf="@id/image2" android:layout_weight="1" android:src="@drawable/image3" /> <ImageView android:id="@+id/image4" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="5dp" android:layout_centerInParent="true" android:layout_below="@id/image1" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_weight="1" android:src="@drawable/image4" /> <ImageView android:id="@+id/image5" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="5dp" android:layout_centerInParent="true" android:layout_below="@id/image2" android:layout_toRightOf="@id/image4" android:layout_toEndOf="@id/image4" android:layout_weight="1" android:src="@drawable/image5" /> <ImageView android:id="@+id/image6" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="5dp" android:layout_centerInParent="true" android:layout_below="@id/image3" android:layout_toRightOf="@id/image5" android:layout_toEndOf="@id/image5" android:layout_weight="1" android:src="@drawable/image6" /> <ImageView android:id="@+id/image7" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="5dp" android:layout_centerInParent="true" android:layout_below="@id/image4" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_weight="1" android:src="@drawable/image7" /> <ImageView android:id="@+id/image8" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="5dp" android:layout_centerInParent="true" android:layout_below="@id/image5" android:layout_toRightOf="@id/image7" android:layout_toEndOf="@id/image7" android:layout_weight="1" android:src="@drawable/image8" /> <ImageView android:id="@+id/image9" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="5dp" android:layout_centerInParent="true" android:layout_below="@id/image6" android:layout_toRightOf="@id/image8" android:layout_toEndOf="@id/image8" android:layout_weight="1" android:src="@drawable/image9" /> </RelativeLayout> ``` 在上述布局中,我们使用了 `android:layout_weight` 属性来实现九宫格子的等分布局,每个 ImageView 控件的权重都设置为 1,这样每个格子的大小就相等了。同时,我们使用了 `android:layout_centerInParent` 属性来使每个格子都居中显示,使用了 `android:layout_alignParentTop` 和 `android:layout_alignParentLeft`(或 `android:layout_alignParentStart`)属性来使第一个格子位于 RelativeLayout 控件的左上角,使用了 `android:layout_below`、`android:layout_toRightOf` 和 `android:layout_toEndOf` 属性来设置每个格子的位置。 最后,我们可以在 Java 代码中获取每个 ImageView 控件,并为它们设置点击事件,以实现九宫格的功能。 ```java ImageView image1 = findViewById(R.id.image1); ImageView image2 = findViewById(R.id.image2); ImageView image3 = findViewById(R.id.image3); ImageView image4 = findViewById(R.id.image4); ImageView image5 = findViewById(R.id.image5); ImageView image6 = findViewById(R.id.image6); ImageView image7 = findViewById(R.id.image7); ImageView image8 = findViewById(R.id.image8); ImageView image9 = findViewById(R.id.image9); image1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 处理第一个格子的点击事件 } }); image2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 处理第二个格子的点击事件 } }); // 其他格子的点击事件同理 ``` 好了,以上就是使用相对布局实现九宫格的方法。希望对您有帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值