博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android项目实战(二十六):蓝牙连接硬件设备开发规范流程
阅读量:6902 次
发布时间:2019-06-27

本文共 24655 字,大约阅读时间需要 82 分钟。

前言:

  最近接触蓝牙开发,主要是通过蓝牙连接获取传感器硬件设备的数据,并进行处理。

  网上学习一番,现整理出一套比较标准的 操作流程代码。

  如果大家看得懂,将来只需要改下 硬件设备的MAC码 和 改下对接收数据的处理 即可。  一切都是套路~~~

 

现在以一个小型项目需求来学习Android蓝牙编程

需求: 通过蓝牙获取硬件数据,并显示在一个随数据即时变化的动态折线图中。

实现思路:

(1) 配对蓝牙设备  

(2) 连接蓝牙设备    ,根据MAC地址,代码中修改

(3) 接收数据

(4) 处理数据          ,根据硬件厂商提供给你的数据转换公式,在BluetoothService类中 修改

(5) 传数据给折线图,展现实时变化

-----------------------------------------------------------------------

 

蓝牙知识了解:

(1)、MAC地址:每个设备都有全球唯一的,根据此MAC地址判断蓝牙设备

(2)、蓝牙传输数据,通常一秒钟会传输很多个包,每个包的数据情况如下:

  此时,这个包有11个字节,0x55 是首码,通常通过他来判断一个包的开始

                                     SUM是验证码,会有一套公式来计算,判断当前包是不是一个有效的完整的包

              中间的即是数据,然后硬件方面会给我们一套计算公式,可以以此获取我们要的数据。

  当然每个硬件的包的数据大小都是不同的,有的可能有21个字节,每个硬件的数据的计算方式也不想同

 

 

代码实现:

一共就三部分,因为代码篇幅可能较大,不适合一段段代码讲解,直接贴出整个代码。所有的解释都在注释当中。

其中:

(1)、红色部分是需要大家根据个人硬件情况进行修改的

(2)、紫色部分是根据个人数据情况添加删除修改的。

一:MainActivity

public class MainActivity extends Activity {    private BluetoothService mBluetoothService; //自定义蓝牙服务类    private BluetoothAdapter mBluetoothAdapter;    private String mConnectedDeviceName = null; //连接设备的名称    //默认是1,因为程序启动时首先会连接一个蓝牙    private int current_pos = 1;    //hanlder消息标识 message.what    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;         // Toast    //传感器 ,这里默认同时需要和三个硬件连接,分别设置id 1,2,3进行区分,demo中实际只用到 MAGIKARE_SENSOR_DOWN = 1    //可以根据情况自行添加删除    public static final int MAGIKARE_SENSOR_UP = 2;    public static final int MAGIKARE_SENSOR_DOWN = 1;    public static final int MAGIKARE_SENSOR_CENTER = 3;    public static float[] m_receive_data_up;                    //传感器的数据    public static float[] m_receive_data_down;                  //传感器的数据 ,demo中我们只需要这一个,因为只有一个硬件设备,    public static float[] m_receive_data_center;                //传感器的数据    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //获取蓝牙适配器        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();        // 1、判断设备是否支持蓝牙功能        if (mBluetoothAdapter == null) {            //设备不支持蓝牙功能            Toast.makeText(this, "当前设备不支持蓝牙功能!", Toast.LENGTH_SHORT).show();            return;        }        // 2、打开设备的蓝牙功能        if (!mBluetoothAdapter.isEnabled()) {            boolean enable = mBluetoothAdapter.enable(); //返回值表示 是否成功打开了蓝牙设备            if (enable) {                Toast.makeText(this, "打开蓝牙功能成功!", Toast.LENGTH_SHORT).show();            } else {                Toast.makeText(this, "打开蓝牙功能失败,请到'系统设置'中手动开启蓝牙功能!", Toast.LENGTH_SHORT).show();                return;            }        }        // 3、创建自定义蓝牙服务对象        if (mBluetoothService == null) {            mBluetoothService = new BluetoothService(MainActivity.this, mHandler);        }        if (mBluetoothService != null) {            //根据MAC地址远程获取一个蓝牙设备,这里固定了,实际开发中,需要动态设置参数(MAC地址)            BluetoothDevice sensor_down = mBluetoothAdapter.getRemoteDevice("20:16:06:15:78:76");            if (sensor_down != null) {                //成功获取到远程蓝牙设备(传感器),这里默认只连接MAGIKARE_SENSOR_DOWN = 1这个设备                mBluetoothService.connect(sensor_down, MAGIKARE_SENSOR_DOWN);            }        }    }    private Handler mHandler = new Handler(new Handler.Callback() {        @Override        public boolean handleMessage(Message msg) {            switch (msg.what){                case MESSAGE_READ:                    try {                        String str=msg.getData().getString("index");                        int index=Integer.valueOf(str);                        switch (index)                        {                            //获取到蓝牙传输过来的数据                            case MAGIKARE_SENSOR_UP:                                m_receive_data_up=msg.getData().getFloatArray("Data");                                break;                            //实际只用到这个case ,因为demo只连接了一个硬件设备                            case MAGIKARE_SENSOR_DOWN:                                m_receive_data_down=msg.getData().getFloatArray("Data");                                break;                            case MAGIKARE_SENSOR_CENTER:                                m_receive_data_center=msg.getData().getFloatArray("Data");                                break;                        }                    } catch (Exception e) {                        // TODO: handle exception                    }                    break;                case MESSAGE_STATE_CHANGE://                    连接状态                    switch (msg.arg1) {                        case BluetoothService.STATE_CONNECTED:                            break;                        case BluetoothService.STATE_CONNECTING:                            break;                        case BluetoothService.STATE_LISTEN:                            break;                        case BluetoothService.STATE_NONE:                            break;                    }                    break;                case MESSAGE_DEVICE_NAME:                    mConnectedDeviceName = msg.getData().getString("device_name");                    Log.i("bluetooth","成功连接到:"+mConnectedDeviceName);                    Toast.makeText(getApplicationContext(),"成功连接到设备" + mConnectedDeviceName,Toast.LENGTH_SHORT).show();                    break;                case MESSAGE_TOAST:                    int index=msg.getData().getInt("device_id");                    Toast.makeText(getApplicationContext(),msg.getData().getString("toast"), Toast.LENGTH_SHORT).show();                    //当失去设备或者不能连接设备时,重新连接                    Log.d("Magikare","当失去设备或者不能连接设备时,重新连接");                         //重新连接硬件设备                    if(mBluetoothService!=null)                    {                        switch (index) {                            case MAGIKARE_SENSOR_DOWN:                     //根据你的硬件的MAC地址写参数,每一个硬件设备都有一个MAC地址,此方法是根据MAC地址得到蓝牙设备                                BluetoothDevice sensor_down = mBluetoothAdapter.getRemoteDevice("20:16:06:15:78:76");                                if (sensor_down != null)                                    mBluetoothService.connect(sensor_down, MAGIKARE_SENSOR_DOWN);                                break;                            case MAGIKARE_SENSOR_UP:                                BluetoothDevice sensor_up = mBluetoothAdapter.getRemoteDevice("");  //参数写你这个设备的MAC码                                if (sensor_up != null)                                    mBluetoothService.connect(sensor_up, MAGIKARE_SENSOR_UP);                                break;                            case MAGIKARE_SENSOR_CENTER:                                BluetoothDevice center = mBluetoothAdapter.getRemoteDevice("");    //参数写你这个设备的MAC码                                if (center != null)                                    mBluetoothService.connect(center, MAGIKARE_SENSOR_CENTER);                                break;                        }                    }                    break;            }            return false;        }    });    public synchronized void onResume() {        super.onResume();        if (mBluetoothService != null) {            if (mBluetoothService.getState() == BluetoothService.STATE_NONE) {                mBluetoothService.start();            }        }    }    @Override    public void onDestroy() {        super.onDestroy();        if (mBluetoothService != null) mBluetoothService.stop();    }        // 硬件通过蓝牙传输的byte类型已经转换为float类型,并且通过handler传输到 m_receive_data_down[]数组中,一下操作是获取这个数据,根据个人情况使用    //获取角度    public float[] GetAngle(int index)    {        float[] angles=new float[3];        if(m_receive_data_up==null                ||m_receive_data_down==null                )        {            return angles;        }        switch (index)        {            case  MAGIKARE_SENSOR_DOWN:                angles[0]=m_receive_data_down[6];                angles[1]=m_receive_data_down[7];                angles[2]=m_receive_data_down[8];                break;            case MAGIKARE_SENSOR_UP:                angles[0]=m_receive_data_up[6];                angles[1]=m_receive_data_up[7];                angles[2]=m_receive_data_up[8];                Log.d("安卓 Up 角度",angles[0]+","+angles[1]+","+angles[2]);                break;        }        return angles;    }    //获取角速度    public static float[] GetAngleSpeed(int index)    {        float [] anglespeed=new float[3];        if(m_receive_data_down==null)        {            return anglespeed;        }        switch (index)        {            case MAGIKARE_SENSOR_DOWN:                anglespeed[0]=m_receive_data_down[3];                anglespeed[1]=m_receive_data_down[4];                anglespeed[2]=m_receive_data_down[5];                break;            case MAGIKARE_SENSOR_UP:                anglespeed[0]=m_receive_data_up[3];                anglespeed[1]=m_receive_data_up[4];                anglespeed[2]=m_receive_data_up[5];                break;        }        return  anglespeed;    }    public float[] GetQuaternion(int index)    {        float[] quaternion=new float[4];        if(m_receive_data_down==null)        {            return quaternion;        }        switch (index)        {            case  MAGIKARE_SENSOR_DOWN:                quaternion[0]=m_receive_data_down[23];                quaternion[1]=m_receive_data_down[24];                quaternion[2]=m_receive_data_down[25];                quaternion[3]=m_receive_data_down[26];                Log.i("saveinfo","m_receive_data_down23"+m_receive_data_down[23]);                Log.i("saveinfo","m_receive_data_down24"+m_receive_data_down[24]);                Log.i("saveinfo","m_receive_data_down25"+m_receive_data_down[25]);                Log.i("saveinfo","m_receive_data_down26"+m_receive_data_down[26]);                break;            case MAGIKARE_SENSOR_UP:                quaternion[0]=m_receive_data_up[23];                quaternion[1]=m_receive_data_up[24];                quaternion[2]=m_receive_data_up[25];                quaternion[3]=m_receive_data_up[26];                break;            case MAGIKARE_SENSOR_CENTER:                quaternion[0]=m_receive_data_center[23];                quaternion[1]=m_receive_data_center[24];                quaternion[2]=m_receive_data_center[25];                quaternion[3]=m_receive_data_center[26];        }        return  quaternion;    }}

 

二、BluetoothService

public class BluetoothService {
  private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); private Context context; //蓝牙适配器 private BluetoothAdapter mAdapter; private Handler mHandler; //当前传感器设备的个数,即要开启的线程个数,用于设置线程数组的大小 //这里默认为1,因为我们目前只需要和一个传感器连接, 比如:你要连接两个硬件设备,那就设置值为2,这样就会开启两个线程,分别去执行想要操作 public static final int SENSEOR_NUM=1; private AcceptThread mAcceptThread;// 请求连接的监听进程 private ConnectThread mConnectThread;// 连接一个设备的进程 public ConnectedThread[] mConnectedThread=new ConnectedThread[SENSEOR_NUM];// 已经连接之后的管理进程 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; //已经连接 public BluetoothService(Context context, Handler mHandler) { this.context = context; this.mHandler = mHandler; mAdapter = BluetoothAdapter.getDefaultAdapter();//获取蓝牙适配器 mState = STATE_NONE ; //当前连接状态:未连接 } // 参数 index 是 硬件设备的id ,随便设的,目的在于当 同时连接多个硬件设备的时候,根据此id进行区分 public synchronized void connect(BluetoothDevice device, int index) { //连接一个蓝牙时,将该设备 的蓝牙连接线程关闭,如果有的话 //demo 就只有一个硬件设备,默认该设备id 取值index=1; if (mConnectedThread[index-1] != null) { mConnectedThread[index-1].cancel(); mConnectedThread[index-1]=null; } mConnectThread=new ConnectThread(device,index); mConnectThread.start(); setState(STATE_CONNECTING); } private class ConnectThread extends Thread{ private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; private int index; public ConnectThread(BluetoothDevice device,int index) { mmDevice = device; this.index=index; BluetoothSocket tmp = null; try { tmp = device.createRfcommSocketToServiceRecord(MY_UUID);// Get a BluetoothSocket for a connection with the given BluetoothDevice } catch (IOException e) {} mmSocket = tmp; } public void run() { setName("ConnectThread"); //当连接成功,取消蓝牙适配器搜索蓝牙设备的操作,因为搜索操作非常耗时 mAdapter.cancelDiscovery();// Always cancel discovery because it will slow down a connection try { mmSocket.connect();// This is a blocking call and will only return on a successful connection or an exception } catch (IOException e) { connectionFailed(this.index); try { mmSocket.close(); } catch (IOException e2) {} BluetoothService.this.start();// 引用来说明要调用的是外部类的方法 run return; } synchronized (BluetoothService.this) {
// Reset the ConnectThread because we're done mConnectThread = null; } connected(mmSocket, mmDevice,index);// Start the connected thread } public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } } class ConnectedThread extends Thread{ private BluetoothSocket mmSocket; private InputStream mmInStream; private OutputStream mmOutStream; private int index; private Queue
queueBuffer = new LinkedList
(); private byte[] packBuffer = new byte[11]; //构造方法 public ConnectedThread(BluetoothSocket socket,int index) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; this.index=index; // Get the BluetoothSocket input and output streams try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) {} mmInStream = tmpIn; mmOutStream = tmpOut; } // 数组大小看你的数据需求,这里存的是你处理蓝牙传输来的字节数据之后实际要用到的数据 private float [] fData=new float[31]; @Override public void run() { byte[] tempInputBuffer = new byte[1024]; int acceptedLen = 0; //记录每次读取数据的数据长度 byte sHead; long lLastTime = System.currentTimeMillis(); //获取开始时间 while(true){ try { acceptedLen = mmInStream.read(tempInputBuffer);//返回接收的长度 //从缓冲区中读取数据 for (int i = 0; i < acceptedLen; i++) { queueBuffer.add(tempInputBuffer[i]); } // 这里需要按个人硬件数据的情况自行修改了 // 如果你的硬件蓝牙传输 一个包有11个字节,那queueBuffer.size()>=11 // 如果你的硬件蓝牙传输 一个包有21个字节,那queueBuffer.size()>=21 while (queueBuffer.size()>=11){ //返回队首并删除,判断队首是不是0x55,如果不是,说明不是一个包的数据,跳过, //注意这里的0x55是你的包的首字节 if (queueBuffer.poll()!=0x55) continue; // 进入到这里,说明得到一个包的数据了,然后就要根据个人硬件的数据情况,将byte类型的数据转换为float类型的数据 sHead = queueBuffer.poll(); //返回队首并删除                        // 现在得到的就是你数据部分了,如果有9位字节代表数据,j<9 ,如果有19位字节代表数据,j<19                                 //将字节数组存到packBuffer[]数据中,用于byte-->float数据的转换 for (int j = 0; j < 9; j++) {
packBuffer[j] = queueBuffer.poll(); } switch (sHead) {// case 0x52://角速度 fData[3] = ((((short) packBuffer[1]) << 8) | ((short) packBuffer[0] & 0xff)) / 2000; fData[4] = ((((short) packBuffer[3]) << 8) | ((short) packBuffer[2] & 0xff)) / 200; fData[5] = ((((short) packBuffer[5]) << 8) | ((short) packBuffer[4] & 0xff)) / 200; fData[17] = ((((short) packBuffer[7]) << 8) | ((short) packBuffer[6] & 0xff)) / 100.0f; break; case 0x53://角度 fData[6] = ((((short) packBuffer[1]) << 8) | ((short) packBuffer[0] & 0xff)) / 180; fData[7] = ((((short) packBuffer[3]) << 8) | ((short) packBuffer[2] & 0xff)) / 180; fData[8] = ((((short) packBuffer[5]) << 8) | ((short) packBuffer[4] & 0xff)) / 180; fData[17] = ((((short) packBuffer[7]) << 8) | ((short) packBuffer[6] & 0xff)) / 100.0f; break; case 0x59://四元数 fData[23] = ((((short) packBuffer[1]) << 8) | ((short) packBuffer[0] & 0xff)) / 327.0f; fData[24] = ((((short) packBuffer[3]) << 8) | ((short) packBuffer[2] & 0xff)) /327.0f; fData[25] = ((((short) packBuffer[5]) << 8) | ((short) packBuffer[4] & 0xff)) /327.0f; fData[26] = ((((short) packBuffer[7]) << 8) | ((short) packBuffer[6] & 0xff)) /327.0f; break; } } long lTimeNow = System.currentTimeMillis(); // 获取收据转换之后的时间 // 如果数据处理后的时间 与 接收到数据的时间 的时间差>80 则发送消息传输数据, // 这个时间需要看你硬件一秒钟发送的包的个数 if (lTimeNow - lLastTime > 80) {
lLastTime = lTimeNow; Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_READ); Bundle bundle = new Bundle(); bundle.putString("index",String.valueOf(this.index)); bundle.putFloatArray("Data", fData); msg.setData(bundle); mHandler.sendMessage(msg); } } catch (IOException e) { connectionLost(this.index); e.printStackTrace(); } } } public void cancel() { try { mmSocket.close(); } catch (IOException e) {} } } //连接失败 private void connectionFailed(int index) { setState(STATE_LISTEN); // Send a failure message back to the Activity Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString("toast", "未能连接设备"+index); bundle.putInt("device_id",index); msg.setData(bundle); mHandler.sendMessage(msg); } // 连接丢失 private void connectionLost(int index) { setState(STATE_LISTEN); Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString("toast", "设备丢失"+index); bundle.putInt("device_id",index); msg.setData(bundle); mHandler.sendMessage(msg); } //用于 蓝牙连接的Activity onResume()方法 public synchronized void start() { // Cancel any thread attempting to make a connection if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mAcceptThread == null) { mAcceptThread = new AcceptThread(); mAcceptThread.start(); } setState(STATE_LISTEN); } public synchronized void connected(BluetoothSocket socket,BluetoothDevice device,int index) { Log.d("MAGIKARE","连接到线程"+index); // Cancel the thread that completed the connection if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } // Cancel the accept thread because we only want to connect to one device if (mAcceptThread != null) { mAcceptThread.cancel(); mAcceptThread = null; } // Start the thread to manage the connection and perform transmissions mConnectedThread[index-1] = new ConnectedThread(socket,index); mConnectedThread[index-1].start(); // Send the name of the connected device back to the UI Activity Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_DEVICE_NAME); Bundle bundle = new Bundle(); bundle.putString("device_name", device.getName()+" "+index); msg.setData(bundle); mHandler.sendMessage(msg); setState(STATE_CONNECTED); } private synchronized void setState(int state) { mState = state; // Give the new state to the Handler so the UI Activity can update mHandler.obtainMessage(MainActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget(); } private class AcceptThread extends Thread { // The local server socket private final BluetoothServerSocket mmServerSocket; //private int index; public AcceptThread() { BluetoothServerSocket tmp = null; // this.index=index; // Create a new listening server socket try { tmp = mAdapter.listenUsingRfcommWithServiceRecord("BluetoothData", MY_UUID); } catch (IOException e) {} mmServerSocket = tmp; } public void run() { new Thread(new Runnable() { @Override public void run() { } }).start(); } public void cancel() { try { if(mmServerSocket!=null) { mmServerSocket.close(); } } catch (IOException e) {} } } public synchronized int getState() { return mState; } public synchronized void stop() { if (mConnectedThread != null) { for(int i=0;i

 

三、自定义即时变化的折线图:

public class MyView extends View {    /*http://www.cnblogs.com/aibuli/p/950c34f2bc0d02cbd290dd6a8339d42a.html*/    //坐标轴原点的位置    private int xPoint=60;    private int yPoint=260;    //刻度长度    private int xScale=8;  //8个单位构成一个刻度    private int yScale=40;    //x与y坐标轴的长度    private int xLength=580;    private int yLength=480;    private int MaxDataSize=xLength/xScale;   //横坐标  最多可绘制的点    private List
data=new ArrayList
(); //存放 纵坐标 所描绘的点 private String[] yLabel=new String[yLength/yScale]; //Y轴的刻度上显示字的集合 private Handler mh=new Handler(){ public void handleMessage(android.os.Message msg) { if(msg.what==0){ //判断接受消息类型 MyView.this.invalidate(); //刷新View } }; }; public MyView(Context context, AttributeSet attrs) { super(context, attrs); for (int i = 0; i
MaxDataSize){ //判断集合的长度是否大于最大绘制长度 data.remove(0); //删除头数据 } // 这里得到蓝牙设备得到的数据 float[] floats = MainActivity.GetAngleSpeed(1); data.add(floats[0]); mh.sendEmptyMessage(0); //发送空消息通知刷新 } } }).start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint=new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setAntiAlias(true); paint.setColor(Color.RED); //绘制Y轴 canvas.drawLine(xPoint, yPoint-yLength, xPoint, yPoint, paint); //绘制Y轴左右两边的箭头 canvas.drawLine(xPoint, yPoint-yLength, xPoint-3,yPoint-yLength+6, paint); canvas.drawLine(xPoint, yPoint-yLength, xPoint+3,yPoint-yLength+6, paint); //Y轴上的刻度与文字 for (int i = 0; i * yScale< yLength; i++) { canvas.drawLine(xPoint, yPoint-i*yScale, xPoint+5, yPoint-i*yScale, paint); //刻度 canvas.drawText(yLabel[i], xPoint-50, yPoint-i*yScale, paint);//文字 } //X轴 canvas.drawLine(xPoint, yPoint, xPoint+xLength, yPoint, paint); //如果集合中有数据 if(data.size()>1){ for (int i = 1; i < data.size(); i++) { //依次取出数据进行绘制 canvas.drawLine(xPoint+(i-1)*xScale, yPoint-data.get(i-1)*yScale, xPoint+i*xScale, yPoint-data.get(i)*yScale, paint); } } }}

 

 

相关知识:

有问题欢迎留言交流!

 

你可能感兴趣的文章
神舟战神调节风扇热键_显瘦又有肌肉 神舟战神Z7M-KP5GZ评测
查看>>
投光灯外壳_LED投光灯主要有哪些优点,如何保养?
查看>>
spark中dataframe解析_Spark SQL | 目前Spark社区最活跃的组件之一
查看>>
发那科冲压直线搬运机器人_传统的机械加工升级了,直线七轴工业机器人搬运系统可会操作?...
查看>>
java中sort函数comparator的使用_用Java中的Comparable和Comparator排序
查看>>
两线怎么接三线插座图_多个插座之间到底怎么接线?
查看>>
redis zse如何取值_Redis 开发陷阱及避坑指南!
查看>>
js判断上午下午_深度水解奶粉和普通奶粉有什么区别?怎么判断孩子过敏是否好转?...
查看>>
20捕获范围_常见python基础面试题20题!
查看>>
pureftpd mysql.conf_pureftpd-mysql.conf配置文件
查看>>
keil obj 文件 结构_在MDK开发环境下的STM32工程结构
查看>>
mysql my large.ini_mysql 配置文件my-small.ini、my-medium.ini、my-large.ini、my-huge.ini 说明...
查看>>
python bootstrap 文件上传_文件上传控件bootstrap-fileinput与Python交互
查看>>
mysql分片备份不一致问题_光大银行分布式实战:国内最大缴费平台的数据库架构转型...
查看>>
java 参数对象一起封装成json_0基础掌握Django框架(29)HttpResponse对象
查看>>
mysql导入没有选择字段_[MySQL]load data local infile向MySQL数据库中导入数据时,无法导入和字段不分离问题。...
查看>>
php对mysql最匹配输出_php – 在十进制列中查找MySQL中最接近的匹配项
查看>>
thinkphp5 mysql缓存_thinkphp+redis实现秒杀,缓存等功能
查看>>
usb一转多 树莓派zero_windows下一根数据线玩转树莓派zero (w)
查看>>
mysql写保护_84个MySQL性能优化的首选技巧
查看>>