关于xUtils框架
导入xUtils包
在build.gradle(Module:app)下的"dependencies"加入以下代码:
implementation 'org.xutils:xutils:3.5.0'
添加权限
在AndroidManifest.xml中添加以下代码:
<!-- xutils3 需要的存储和联网权限 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
创建Application(MultiDexApplication)
项目中使用的MultiDexApplication而非原生的Application,原因是原生的Application支持最多65536个方法,大型项目引用了众多的第三方库之后几乎不可能避免这个问题。
引入MultiDexApplication,在build.gradle(Module:app)下的"dependencies"加入以下代码:
implementation 'com.android.support:multidex:1.0.3'
然后在MyApplication中的onCreate()中引用:
x.Ext.init(this);
x.Ext.setDebug(BuildConfig.DEBUG);
值的注意的是,在setDebug中就算是输入的"false",在Logcat中也会打印Log,但是不会打印LogUtil。
在AndroidMainfest文件中注册MyApplication
在AndroidManifest.xml中的标签中加入以下代码:
android:name=".MyApplication"
这样就完成了MyApplication的注册。
xUtils注解的使用
在Activity中使用注解
首先需要定位布局文件,使用如下注解:
@ContentView(R.layout.xxx)
这样Activity就可以获取到指定的布局文件。
然后,在布局文件中获取到指定的部件,使用如下注解:
@ViewInject(R.id.xxx)
private xxx xx;
这样Activity就可以找到指定布局文件中的某一个部件。
最后,在Activity中的onCreate()方法中添加,引用,代码如下:
x.view().inject(this);
只得注意的是,这一句话在项目中也基本没有使用过,但是页面仍然可以正常显示。
在Fragment中使用注解(本次项目中未使用)
具体样例代码如下:
@ContentView(R.layout.fragment_http)
public class HttpFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return x.view().inject(this, inflater, container);
}
@Override
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
}
}
具体的使用不详,在本项目中未使用Fragment。
给点击事件使用注解
在导入布局文件之后,使用如下代码,为指定的部件添加点击事件:
@Event(R.id.xxx)
代码意为,在当前的布局文件中找到id为"xxx"的部件,为其添加点击事件。
xUtils数据库模块的使用
初始化配置
在MyApplication中添加如下代码:
DbManager.DaoConfig daoConfig = new DbManager.DaoConfig()
//设置数据库名,默认xutils.db
.setDbName("myapp.db")
//设置数据库路径,默认存储在app的私有目录
.setDbDir(new File("/mnt/sdcard/"))
//设置数据库的版本号
.setDbVersion(2)
//设置数据库打开的监听
.setDbOpenListener(new DbManager.DbOpenListener() {
@Override
public void onDbOpened(DbManager db) {
//开启数据库支持多线程操作,提升性能,对写入加速提升巨大
db.getDatabase().enableWriteAheadLogging();
}
})
//设置数据库更新的监听
.setDbUpgradeListener(new DbManager.DbUpgradeListener() {
@Override
public void onUpgrade(DbManager db, int oldVersion, int newVersion) {
}
})
//设置表创建的监听
.setTableCreateListener(new DbManager.TableCreateListener() {
@Override
public void onTableCreated(DbManager db, TableEntity<?> table){
Log.i("JAVA", "onTableCreated:" + table.getName());
}
});
然后,采用单例模式:
DbManager db = x.getDb(daoConfig);
public DbManager getDbManager(){
return db!=null?db:x.getDb(daoConfig);
}
因为MyApplication是单例的,所以在使用的时候获得就能保证不会创建多个对象。
创建实体类
实体类样例代码如下:
@Table(name = "setting")
public class Setting {
@Column(name = "id", isId = true)
private int id;//主键
@Column(name = "userName")
private String userName;//用户名称
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public int hashCode() {
return Objects.hash(id, userName);
}
@Override
public String toString() {
return "Setting{" +
"id=" + id +
", userName='" + userName + '\'' +
'}';
}
}
值得注意的是,在网上查到的信息为@Table(name = “setting”,onCreate=“xxx”),意为在表创建的时候加入某些数据,而不是在MyApplication创建的时候自动建表然后有某些信息。
数据库操作(本次项目中常用的)
-
保存实体类或者实体类的List到数据库
void save(Object entity) throws DbException;if (db.findAll(Setting.class) == null){ setting.setUserName("xxxx"); db.save(setting); }
如果原数据库为空,则会在新数据入库的时候被创建。
-
按条件查找
List res = dbManager.selector(Setting.class)
.where(“username”,"=",aim).findAll();
将符合条件的所有对象返回到一个List中。 -
更新数据库数据
dbManager.update(res,“username”);
将res更新到名为"username"的Column中。
xUtils中异步任务的执行
x.task().run(new Runnable() {
@Override
public void run() {
//异步代码
}
}
);
关于蓝牙模块的实现
监听来自Activity的命令
Activity可以通过Command类的对象向蓝牙Service(BtService)发送以下四种命令:
1. 启动扫描并连接蓝牙设备(“startBLE”)
2. 断开当前连接的蓝牙设备(“stopBLE”)
3. 暂停扫描周围蓝牙设备(“stopScan”)
即实现一种很简单的命令模式。
具体的BtService.Command使用方法
当Service被启动时,在Service#onCreate()中会创建一个Command对象,在外部可以通过Service#getcommander()来获取Service内部的Command对象,然后在Activity中使用Command#ControlService()来向Service中发送命令,命令为String类型,使用时注意String串不要错。
收到命令并且执行命令
收到“启动扫描并连接蓝牙设备”的命令
当Command收到此条命令后,首先会先判断当前的连接流程是否正在执行,如果没有执行则再判断当前是否处于正在连接的状态,如果都没有,则进行当前是否是第一次连接而非重连,如果是第一次连接则会重置相关变量准备连接,如果是重连则会跳过重置变量的操作直接执行扫描并链接的流程。发出连接成功的广播(全局广播),通知到所有需要连接成功广播的地方。
检查阶段(BtService#checkBt())
- 会先询问当时是否处于正在执行流程的操作,如果是在执行流程,则不会执行本次操作。
- 如果之前没有在执行流程,则本次就是在执行流程,则会置running = true,然后通过BluetoothManager类对象来引用向系统请求来的蓝牙服务。
- 再通过上一步的BluetoothManager获取一个蓝牙适配器。
- 如果蓝牙适配器不为空,则证明当前设备存在蓝牙模块,即可以使用蓝牙。
- 再询问当前的蓝牙功能是否可用(BluetoothAdapter#isEnabled()),如果不可用则会通过BluetoothAdapter#enable()来开启蓝牙功能。
- 如果当前设备不可用,则会开启后退出流程,再重新走一次流程。
- 当蓝牙功能可用时,则会进入扫描前的准备阶段。
开始扫描蓝牙设备(BtService#scanLeDevice())
- 在开始此方法第一步,会先将当前状态置为正在扫描的状态(mScanning = true)。
- 判断蓝牙适配器是否为空,如果为空则会终止整个流程,重置相关变量,然后等待下一次被调用。
- 通过BluetoothAdapter#startLeScan()来扫描周围的蓝牙设备,将扫描到的结果会返回到BluetoothAdapter.LeScanCallback#onLeScan()中。
- 判断由BluetoothAdapter.LeScanCallback#onLeScan()中获得的蓝牙设备的RSSi信号值和设备名称来判断是否是目标设备(BtService#isOurBLE()),如果不是则重置相关变量,终止流程,等待下一次的调用。
- 进入连接设备阶段。
- 将当前状态置为停止扫描(mScanning = false)。
连接蓝牙设备(BtService#startConn())
- 先询问是否通过BluetoothAdapter.LeScanCallback#onLeScan()获得了蓝牙设备对象,如果获得了,则继续流程,如果没有获得,则会重置变量,停止流程,等待下一次连接。
- 查看当前的BluetoothGatt对象是否为空,如果为空则继续执行,如果不是空,则断开连接,为连接新的蓝牙设备做准备。
- 通过BluetoothDevice#connectGatt()来给BluetoothGatt对象赋值,其中BluetoothDevice#connectGatt()需要调用一个回调对象。
- 如果获得到了目标设备的BluetoothGatt服务,则继续执行流程,并将状态置为正在连接(conning = true)且没有在运行流程(running = false)。
- 再判断当前的设备是不是目标设备,当前的设备中的Service是否可以被扫描到,如果不是或者不行,再将状态置为不在连接(conning = false),然后断开连接。
- 然后再获得一次最新的设备名称。
- 如果不是出于重连连接成功的状态(reConntoBLE = false),则向教练机发送请求数据的请求。
- 将重连状态置为非重连状态(reConntoBLE = false)。
- 在获取到心率信息(或其他关于特征值的数据)之后,停止蓝牙适配器的扫描,如果提前停止会导致无法获取特征值信息。
具体的断开当前连接的过程
先询问BluetoothGatt对象是否为空,如果为空则继续执行其它逻辑,如果不为空,则先断开连接。
发出断开连接的全局广播。
收到“暂停扫描周围蓝牙设备”的命令
当Command收到此条命令后,会直接修改当前是否在执行连接流程的变量,使变量置为“true”,然后整个流程会被终止。
在蓝牙Service被创建时启动的定时任务
在定时任务中主要执行两个功能:
- 一个是在连接成功时,每隔一段时间(UPDATE_RSSI_PERIOD)通过BluetoothGatt#readRemoteRssi()更新一下RSSi值,来判定当前是否超出训练区域。
- 一个是在掉线时,计算掉线时间,如果超过判定时间(UPDATE_RSSI_PERIOD * OFF_LINE_LIMIT),则会发出掉线广播后重置相关变量。如果在超时之前成功重连,则会将状态置为成功重连(isReConn = true),然后在回调对象的方法中发出重连广播。
- 每一次判定是否超时都是在执行完一次搜索连接流程之后判定的。
在BtService中用到的几个回调对象的方法重写详解
BluetoothAdapter.LeScanCallback#onLeScan()
BluetoothAdapter#startLeScan()方法需要一个BluetoothAdapter.LeScanCallback的参数,该方法也就是在此时被调用的,这个方法被重写为,当扫描到的设备的名称不为空时,会更新BtService中的BluetoothDevice对象和换算的RSSi距离,然后用于判定。
BluetoothGattCallback#onConnectionStateChange()
在BluetoothDevice#connectGatt()中需要一个BluetoothGattCallback回调对象,在连接时会调用此方法,在BtService中主要对无法获取到目标设备的Gatt、成功连接到设备和与设备的连接断开这三种情况进行判断。
- 无法获取到目标设备的Gatt(status == BluetoothGatt.GATT_SUCCESS)
当无法获取到目标设备的Gatt服务时,会将状态置为不在进行流程和断开连接(conning = false,running = false),然后断开当前的连接。 - 成功连接到设备(newState == BluetoothProfile.STATE_CONNECTED)
会首先扫描目标设备上的Services。然后判断当前的连接是否是因为被动断开连接引起的,如果是则会发出设备重连的广播,并将状态置为不在处于尝试重新获得连接和不再处于掉线状态(reConntoBLE = false,isReConn = false)。 - 与设备的连接断开(newState == BluetoothProfile.STATE_DISCONNECTED)
如果收到的状态为断开,如果当前的BluetoothGatt对象不为空,会首先断开连接之后再置空,让后将状态置为尝试重新获得连接(reConntoBLE = true),重置连接过程中的相关变量然后发出设备掉线的广播,但是不会清空当前设备的信息。
BluetoothGattCallback#onServicesDiscovered()
该方法是BluetoothGatt#discoverServices()的回调方法。首先根据BluetoothGatt#getService()来获取目标UUID的Service,如果能获得则通过BluetoothGatt.getService#getCharacteristic()来获得目标UUID的特征值,然后如果特征值获得成功,则会开始订阅特征值(BluetoothGatt#setCharacteristicNotification()),如果返回值是true则说明可以订阅,然后再通过Characteristic#getDescriptor(BleDefinedUUIDs.Descriptor.CHAR_CLIENT_CONFIG)来获得描述器来描述收到的特征值,如果可以获得特征值,则会通过BluetoothGattDescriptor#setValue()来激活描述器,然后通过BluetoothGatt#writeDescriptor()来启动描述器。
BluetoothGattCallback#onCharacteristicChanged()
当订阅成功后,每当蓝牙设备上的特征值改变时,此方法会被调用,然后需要判断特征值,然后根据特征值的不同解析的方式也不同,解析完成之后会将解析出来的信息以广播的方式发送。
BluetoothGattCallback#onReadRemoteRssi()
该方法会被BluetoothGatt#readRemoteRssi()回调,在该方法中会首先判断当前是否还处于连接状态,如果处于则会首先换算RSSi信号值,如果换算后的距离处于合法的运动半径(DETERMINE_THE_RADIUS_SPORT)内,则会归零超限次数(outCount),如果当前处于正在连接的状态(conning = true)且当前的距离大于合法运动半径则会让超限次数+1,如果超限次数大于限制次数(OUT_SITE_TIMES_LIMIT),则会将当前的状态置为正在尝试重新连接的状态(reConntoBLE = true),断开连接然后重置相关变量,最后发出用户掉线广播。
BtService的生命周期
BtService#onCreate()
在BtService被创建时,会创建Command对象、注册监听来自发卡器广播的接收器和启动更新RSSi值或尝试重连的定时任务。
BtService#onDestroy()
当BtService被销毁时,如果当前的BluetoothGatt不为空,则会直接关闭BluetoothGatt与蓝牙设备的连接,不会发出广播或者将用户对象置空。
BtService内的工具方法
BtService#getMeters(int newRSSi)
这个方法,主要用于将获取到的RSSi信号强度,换算为米,其中的变量A为蓝牙设备距离接收端1m时RSSi信号强度的绝对值,如果出现换算距离不准确时,可以重新校准变量A。
值得注意的是,由于RSSi信号的强弱收到各种因素干扰而且由于RSSi信号会因为硬件信息产生波动(波动范围为1~10倍正常值),所以不可用于精确测量!
BtService#isOurBLE(BluetoothDevice device)
这个方法,主要是通过设备的名称来判断是否是目标设备。
补充
蓝牙适配器(BluetoothAdapter)
一经启动会自动开始扫描周围设备,而不是调用一次启动方法扫描一次!
项目心得
- 需要考虑到程序以后的扩展性,被师父点名批评。
- 需要考虑代码的可读性,毕竟是多人协作。
- 注意公共功能进行抽取,减少代码的冗余。
- 多线程编程时,注意用volatile修饰目标变量,这样此变量就变为线程之间可见的。
- 多线程编程时,注意如果多个线程同时访问某一个变量或者方法时,注意用synchronized关键字修饰,这样可以达到互斥的效果。
- 类注释要用/** xxx */,注意写清楚该类、类成员变量或者类方法的用途和执行思路。
- Java高级和java基础知识还是欠缺。