今天來聊一下關於Android 中的藍芽開發吧!

近年來的藍芽技術不斷發展,廣為人知的從2.0版的經典藍芽,到4.0低功耗藍牙

最後來到現在的5.0藍芽,可見技術真的不斷在發展然後工程師的白頭髮越來越多

 

而今天的主旨不在闡述什麼藍芽的歷史這種大眾都可以了解的嘗試,而是來闡述關於"Android手機的藍芽開發"

 

其實我在學期間就已經多次開發應用藍芽相關技術了,但是很遺憾,當時也是一知半解地學,其實還是很不熟...

然後就這麼到了出來工作,公司在藍芽連接設備上採用的是低功耗藍牙BLE的相關技術,對我一個菜逼八真的是不小的挑戰(只是自己弱好嗎...)

當時也算是從頭研究,加上第一份工作又是剛上工,壓力山大啊...( ゜ρ゜)

 

閒聊到此,今天要撰寫的是關於藍芽BLE在Android中裝置上的開發

因為我在這項技術上吃了不少苦頭,故將之記錄下來

首先在閱讀本文前,也要先告訴大家幾件事

1. 本文沒有一定程度的Java知識會比較難閱讀

2. 本文會有其他UI方面的相關技術,我都會貼給你

3. 建議不要只看我這篇文章,我後面貼的相關文章也一並參考會比較能理解

4. 本文將會介紹藍芽開發中的搜尋裝置裝置連線讀取藍芽資料讀取藍芽資料這4大環節,但由於篇幅過長,故分為上下兩篇

 

以下是其他參考資料

Google官方源碼Demo(我試過了可以執行,請記得手動開定位權限

->https://github.com/googlesamples/android-BluetoothLeGatt

 

官方介紹

->https://developer.android.com/guide/topics/connectivity/bluetooth-le.html

 

 

至於今天Demo用到的藍芽裝置,我是自己買Arduino跟藍芽晶片來自己寫,詳情請參考這一篇

->碼農日常-『Arduino』低功耗藍牙藍芽BLE

此外,(上)篇的進度只會到搜尋藍芽並顯示的部分,請特別注意⚠️

(備註:本文之程式碼內容於2021/6/19號更新過)

 

好的,貼完參考資料後,來看一下今天的功能吧

以及Github

->https://github.com/thumbb13555/BLE_Example/tree/master

 


 

1.載入相關權限

原理我真的不想介紹了...網路隨便搜一篇都寫得比我好QQ

針對Android的部分,首先要確認的權限有以下幾個

1.手機版本是否高於API18(Android 4.3)

2.是否支援藍芽?(通常高過4.3就有)

3.是否開啟藍芽?

4.是否開啟定位?(BLE藍芽需要有定位才可以掃到裝置)

再來,展示一下今天進度的所有需要的檔案

截圖 2021-06-19 下午9.09.45

 

 

確認完以上後,以下就是該加入的內容

首先是AndroidManifest.xml的部分

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jetec.ble_example">

    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

其中<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />

便是開啟藍芽啟用權,其他便是藍芽權限與取得手機位置權限等

再來來到MainActivity.java

onCreate中加入下面程式

/**確認手機版本是否在API18以上,否則退出程式*/
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
    /**確認是否已開啟取得手機位置功能以及權限*/
    int hasGone = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
    if (hasGone != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(
                new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                REQUEST_FINE_LOCATION_PERMISSION);
    }
    /**確認手機是否支援藍牙BLE*/
    if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
        Toast.makeText(this,"Not support Bluetooth", Toast.LENGTH_SHORT).show();
        finish();
    }
    /**開啟藍芽適配器*/
    if(!mBluetoothAdapter.isEnabled()){
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent,REQUEST_ENABLE_BT);
    }
}else finish();

 

忘記補上,全域變數可以先宣告如下

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName()+"My";
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    private static final int REQUEST_FINE_LOCATION_PERMISSION = 102;
    private static final int REQUEST_ENABLE_BT = 2;
    private boolean isScanning = false;
    ArrayList<ScannedData> findDevice = new ArrayList<>();
    RecyclerViewAdapter mAdapter;

    @Override

這時候執行,就會看到這個畫面囉!

Screenshot_20200625-153829_Permission controller


 

2. 畫介面

兩個介面一次送你(・ωー)~☆

activity_main.xml

截圖 2020-06-28 下午4.15.14

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@color/colorPrimary"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="掃描裝置"
                android:textColor="@android:color/white"
                android:textSize="20dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <Button
                android:id="@+id/button_Scan"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:background="@android:color/transparent"
                android:text="停止掃描"
                android:textColor="@android:color/white"
                android:textStyle="bold"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.appcompat.widget.Toolbar>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView_ScannedList"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar" />


 

 

scanned_item.xml

(RecyclerView用的item)

截圖 2020-06-28 下午4.17.51

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/textView_DeviceName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="裝置名稱"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView_Address"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:text="位址Address"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView_DeviceName" />

    <TextView
        android:id="@+id/textView_ScanRecord"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="16dp"
        android:text="挾帶的資訊ScanRecord"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/textView_Rssi"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView_Address" />

    <TextView
        android:id="@+id/textView_Rssi"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:text="訊號強度Rssi"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/textView_DeviceName" />

</androidx.constraintlayout.widget.ConstraintLayout>

 


 

3. 撰寫功能

3-1 建立實體類別ScannedData.java

一般來說,在藍芽掃描的時候可以獲得以下資訊

1.裝置名稱

2.位址(這很重要,除了連線要用以外,他也是所有裝置的唯一碼)

3.Rssi(訊號強度)

4.挾帶的資訊(以一串byteArray傳輸,部分韌體工程師會將部分需告知的資訊放在這邊,提供終端必要資訊)

也因此,我們的實體類別就要有這四個資訊

ScannedData.java

class ScannedData {
    /**這邊是拿取掃描到的所有資訊*/
    private String deviceName;
    private String rssi;
    private String deviceByteInfo;
    private String address;

    public ScannedData(String deviceName, String rssi, String deviceByteInfo, String address) {
        this.deviceName = deviceName;
        this.rssi = rssi;
        this.deviceByteInfo = deviceByteInfo;
        this.address = address;
    }

    public String getAddress() {
        return address;
    }

    public String getRssi() {
        return rssi;
    }

    public String getDeviceByteInfo() {
        return deviceByteInfo;
    }

    public String getDeviceName() {
        return deviceName;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        ScannedData p = (ScannedData)obj;

        return this.address.equals(p.address);
    }

    @NonNull
    @Override
    public String toString() {
        return this.address;
    }
}

 

補充一下,

@Override
public boolean equals(@Nullable Object obj) {
    ScannedData p = (ScannedData)obj;

    return this.address.equals(p.address);
}

@NonNull
@Override
public String toString() {
    return this.address;
}

是後面演算法需濾除重複的address用的,先加著吧

相關資料在這邊

Java List去掉重复对象-java8

->https://blog.csdn.net/jiaobuchong/article/details/54412094

 

3-2 RecyclerView之相關功能RecyclerViewAdapter.java

關於RecyclerView的部分我就不再多詳述,不會的請往這邊->碼農日常-『Android studio』基本RecyclerView用法

RecyclerView要有的功能總共兩項

1.清除資料 clearDevice()

2.加入資料 addDevice(List<ScannedData> arrayList)

 

以下就是RecyclerViewAdapter.java的內容

RecyclerViewAdapter.java

class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
    private List<ScannedData> arrayList = new ArrayList<>();
    private Activity activity;

    public RecyclerViewAdapter(Activity activity) {
        this.activity = activity;
    }
    /**清除搜尋到的裝置列表*/
    public void clearDevice(){
        this.arrayList.clear();
        notifyDataSetChanged();
    }
    /**若有不重複的裝置出現,則加入列表中*/
    public void addDevice(List<ScannedData> arrayList){
        this.arrayList = arrayList;
        notifyDataSetChanged();
    }
    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvName,tvAddress,tvInfo,tvRssi;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.textView_DeviceName);
            tvAddress = itemView.findViewById(R.id.textView_Address);
            tvInfo = itemView.findViewById(R.id.textView_ScanRecord);
            tvRssi = itemView.findViewById(R.id.textView_Rssi);

        }
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.scanned_item,parent,false);
        return new ViewHolder(view);
    }

    @SuppressLint("SetTextI18n")
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.tvName.setText(arrayList.get(position).getDeviceName());
        holder.tvAddress.setText("裝置位址:"+arrayList.get(position).getAddress());
        holder.tvInfo.setText("裝置挾帶的資訊:\n"+arrayList.get(position).getDeviceByteInfo());
        holder.tvRssi.setText("訊號強度:"+arrayList.get(position).getRssi());
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }


}

 

3-3 開啟藍芽掃描相關功能

再給你一次全域變數,比較好閱讀

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName()+"My";
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    private static final int REQUEST_FINE_LOCATION_PERMISSION = 102;
    private static final int REQUEST_ENABLE_BT = 2;
    private boolean isScanning = false;
    ArrayList<ScannedData> findDevice = new ArrayList<>();
    RecyclerViewAdapter mAdapter;

 

首先,要先開啟藍芽的Adapter

如下

/**啟用藍牙適配器*/
final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

 

然後分解一下,執行掃描的指令是這個

mBluetoothAdapter.startLeScan();

 

然後停止掃描的是這個

mBluetoothAdapter.stopLeScan();

 

而他們括號內都必須要有個複寫,其覆寫長這樣

mBluetoothAdapter.stopLeScan(new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        
    }
});

 

為求方便,我把它寫在外面了

mBluetoothAdapter.stopLeScan(mLeScanCallback);
/**顯示掃描到物件*/
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        
    }
};

 

大致是這樣,就完成了掃描的雛形了

那麼,這時候我再貼給你全部,應該就可以理解了吧!

 

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName()+"My";
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    private static final int REQUEST_FINE_LOCATION_PERMISSION = 102;
    private static final int REQUEST_ENABLE_BT = 2;
    private boolean isScanning = false;
    ArrayList<ScannedData> findDevice = new ArrayList<>();
    RecyclerViewAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /**權限相關認證*/
        checkPermission();
        /**初始藍牙掃描及掃描開關之相關功能*/
        bluetoothScan();
        /**取得欲連線之裝置後跳轉頁面*/
        mAdapter.OnItemClick(itemClick);

    }
    /**權限相關認證*/
    private void checkPermission() {
        /**確認手機版本是否在API18以上,否則退出程式*/
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
            /**確認是否已開啟取得手機位置功能以及權限*/
            int hasGone = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
            if (hasGone != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        REQUEST_FINE_LOCATION_PERMISSION);
            }
            /**確認手機是否支援藍牙BLE*/
            if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
                Toast.makeText(this,"Not support Bluetooth", Toast.LENGTH_SHORT).show();
                finish();
            }
            /**開啟藍芽適配器*/
            if(!mBluetoothAdapter.isEnabled()){
                Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(enableBtIntent,REQUEST_ENABLE_BT);
            }
        }else finish();
    }
    /**初始藍牙掃描及掃描開關之相關功能*/
    private void bluetoothScan() {
        /**啟用藍牙適配器*/
        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();
        /**開始掃描*/
        mBluetoothAdapter.startLeScan(mLeScanCallback);
        isScanning = true;
        /**設置Recyclerview列表*/
        RecyclerView recyclerView = findViewById(R.id.recyclerView_ScannedList);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new RecyclerViewAdapter(this);
        recyclerView.setAdapter(mAdapter);
        /**製作停止/開始掃描的按鈕*/
        final Button btScan = findViewById(R.id.button_Scan);
        btScan.setOnClickListener((v)-> {
                if (isScanning) {
                    /**關閉掃描*/
                    isScanning = false;
                    btScan.setText("開始掃描");
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }else{
                    /**開啟掃描*/
                    isScanning = true;
                    btScan.setText("停止掃描");
                    findDevice.clear();
                    mBluetoothAdapter.startLeScan(mLeScanCallback);
                    mAdapter.clearDevice();
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        final Button btScan = findViewById(R.id.button_Scan);
        isScanning = true;
        btScan.setText("停止掃描");
        findDevice.clear();
        mBluetoothAdapter.startLeScan(mLeScanCallback);
        mAdapter.clearDevice();
    }
    /**避免跳轉後掃描程序係續浪費效能,因此離開頁面後即停止掃描*/
    @Override
    protected void onStop() {
        super.onStop();
        final Button btScan = findViewById(R.id.button_Scan);
        /**關閉掃描*/
        isScanning = false;
        btScan.setText("開始掃描");
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
    }

    /**顯示掃描到物件*/
    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            new Thread(()->{
                /**如果裝置沒有名字,就不顯示*/
                if (device.getName()!= null){
                    /**將搜尋到的裝置加入陣列*/
                    findDevice.add(new ScannedData(device.getName()
                            , String.valueOf(rssi)
                            , byteArrayToHexStr(scanRecord)
                            , device.getAddress()));
                    /**將陣列中重複Address的裝置濾除,並使之成為最新數據*/
                    ArrayList newList = getSingle(findDevice);
                    runOnUiThread(()->{
                        /**將陣列送到RecyclerView列表中*/
                        mAdapter.addDevice(newList);
                    });
                }
            }).start();
        }
    };
    /**濾除重複的藍牙裝置(以Address判定)*/
    private ArrayList getSingle(ArrayList list) {
        ArrayList tempList = new ArrayList<>();
        try {
            Iterator it = list.iterator();
            while (it.hasNext()) {
                Object obj = it.next();
                if (!tempList.contains(obj)) {
                    tempList.add(obj);
                } else {
                    tempList.set(getIndex(tempList, obj), obj);
                }
            }
            return tempList;
        } catch (ConcurrentModificationException e) {
            return tempList;
        }
    }
    /**
     * 以Address篩選陣列->抓出該值在陣列的哪處
     */
    private int getIndex(ArrayList temp, Object obj) {
        for (int i = 0; i < temp.size(); i++) {
            if (temp.get(i).toString().contains(obj.toString())) {
                return i;
            }
        }
        return -1;
    }
    /**
     * Byte轉16進字串工具
     */
    public static String byteArrayToHexStr(byte[] byteArray) {
        if (byteArray == null) {
            return null;
        }

        StringBuilder hex = new StringBuilder(byteArray.length * 2);
        for (byte aData : byteArray) {
            hex.append(String.format("%02X", aData));
        }
        String gethex = hex.toString();
        return gethex;
    }

    /**取得欲連線之裝置後跳轉頁面*/
    private RecyclerViewAdapter.OnItemClick itemClick = new RecyclerViewAdapter.OnItemClick() {
        @Override
        public void onItemClick(ScannedData selectedDevice) {

            Intent intent = new Intent(MainActivity.this, DeviceInfoActivity.class);
            intent.putExtra(DeviceInfoActivity.INTENT_KEY,selectedDevice);
            startActivity(intent);
        }
    };

}

 

 

其中

/**濾除重複的藍牙裝置(以Address判定)*/
private ArrayList getSingle(ArrayList list) {
    ArrayList tempList = new ArrayList<>();
    try {
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Object obj = it.next();
            if (!tempList.contains(obj)) {
                tempList.add(obj);
            } else {
                tempList.set(getIndex(tempList, obj), obj);
            }
        }
        return tempList;
    } catch (ConcurrentModificationException e) {
        return tempList;
    }
}
/**
 * 以Address篩選陣列->抓出該值在陣列的哪處
 */
private int getIndex(ArrayList temp, Object obj) {
    for (int i = 0; i < temp.size(); i++) {
        if (temp.get(i).toString().contains(obj.toString())) {
            return i;
        }
    }
    return -1;
}

 

都是用來濾資料的,輸入一團資料後,會整理出不重複的資料

若資料已存在,則會更新該筆資料的內容

其判斷資料重複依據則是使用裝置的address來斷定

 

最後

/**
 * Byte轉16進字串工具
 */
public static String byteArrayToHexStr(byte[] byteArray) {
    if (byteArray == null) {
        return null;
    }

    StringBuilder hex = new StringBuilder(byteArray.length * 2);
    for (byte aData : byteArray) {
        hex.append(String.format("%02X", aData));
    }
    String gethex = hex.toString();
    return gethex;
}

的部分

則是用來將藍芽裝置所夾帶的資料化為字串用的,供餐考

 

寫到這邊就可以執行看看囉!沒問題的話應該就可以掃到裝置了

而且資料都會有動態變化喔!試試看吧(・∀・)


 

結語

其實藍芽功能早在半年前就想寫了...但是一直沒有什麼動力寫(´υ`)

因為我程度也沒有說很好,而且藍芽部分常常都覺得自己一知半解

現在會寫出來也不是因為我完全理解了,而是我覺得目前寫出來應該沒什麼大礙....還有就是因為放連假XD

 

我覺得寫一個完整的藍芽很不簡單,因為每個藍芽裝置的內容都不太一樣

雖然到目前為止還沒出現不一樣的地方,但是等下篇就會知道了

下篇的文章我會在下禮拜寫,但是Github已經連同下週的也寫好了,想直接參考程式碼的就去看Git吧

更新:

本篇完成的話,請移步到下一篇吧 :D

碼農日常-『Android studio』Android 低功耗藍牙藍芽BLE (下)

TK2

arrow
arrow

    碼農日常 發表在 痞客邦 留言(23) 人氣()