注:這里只是個成功的示例,compileSdkVersion和27.1.1有關(guān),也可以換成你需要的

在璧山等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都網(wǎng)站設(shè)計、網(wǎng)站制作 網(wǎng)站設(shè)計制作按需定制網(wǎng)站,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),成都品牌網(wǎng)站建設(shè),全網(wǎng)整合營銷推廣,成都外貿(mào)網(wǎng)站建設(shè),璧山網(wǎng)站建設(shè)費用合理。
提供下顏色
清單AndroidManifest
內(nèi)存泄漏:
舉例:
請注意以下的例子是虛構(gòu)的
內(nèi)存抖動
源自Android文檔中的 Memory churn 一詞,中文翻譯為內(nèi)存抖動。
指快速頻繁的創(chuàng)建對象從而產(chǎn)生的性能問題。
引用Android文檔原文:
Java內(nèi)存泄漏的根本原因是 長生命周期 的對象持有 短生命周期 對象的引用就很可能發(fā)生內(nèi)存泄漏。
盡管短生命周期對象已經(jīng)不再需要,但因為長生命周期依舊持有它的引用,故不能被回收而導(dǎo)致內(nèi)存泄漏。
靜態(tài)集合類引起的內(nèi)存泄漏
如果僅僅釋放引用本身(tO = null), ArrayList 依然在引用該對象,GC無法回收。
監(jiān)聽器
在Java應(yīng)用中,通常會用到很多監(jiān)聽器,一般通過 addXXXXListener() 實現(xiàn)。但釋放對象時通常會忘記刪除監(jiān)聽器,從而增加內(nèi)存泄漏的風(fēng)險。
各種連接
如數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接(Socket)和I/O連接。忘記顯式調(diào)用 close() 方法引起的內(nèi)存泄漏。
內(nèi)部類和外部模塊的引用
內(nèi)部類的引用是很容易被遺忘的一種,一旦沒有釋放可能會導(dǎo)致一系列后續(xù)對象無法釋放。此外還要小心外部模塊不經(jīng)意的引用,內(nèi)部類是否提供相應(yīng)的操作去除外部引用。
單例模式
由于單例的靜態(tài)特性,使其生命周期與應(yīng)用的生命周期一樣長,一旦使用不恰當(dāng)極易造成內(nèi)存泄漏。如果單利持有外部引用,需要注意提供釋放方式,否則當(dāng)外部對象無法被正常回收時,會進而導(dǎo)致內(nèi)存泄漏。
集合類泄漏
如集合的使用范圍超過邏輯代碼的范圍,需要格外注意刪除機制是否完善可靠。比如由靜態(tài)屬性 static 指向的集合。
單利泄漏
以下為簡單邏輯代碼,只為舉例說明內(nèi)存泄漏問題,不保證單利模式的可靠性。
AppManager 創(chuàng)建時需要傳入一個 Context ,這個 Context 的生命周期長短至關(guān)重要。
1. 如果傳入的是 Application 的 Context ,因為 Application 的生命周期等同于應(yīng)用的生命周期,所以沒有任何問題。
2. 如果傳入的是 Activity 的 Context ,則需要考慮這個 Activity 是否在整個生命周期都不會被回收了,如果不是,則會造成內(nèi)存泄漏。
非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例造成的內(nèi)存泄漏
應(yīng)該將該內(nèi)部類單獨封裝為一個單例來使用。
匿名內(nèi)部類/異步線程
Runnable都使用了匿名內(nèi)部類,將持有MyActivity的引用。如果任務(wù)在Activity銷毀前未完成,將導(dǎo)致Activity的內(nèi)存無法被回收,從而造成內(nèi)存泄漏。
解決方法:將Runnable獨立出來或使用靜態(tài)內(nèi)部類,可以避免因持有外部對象導(dǎo)致的內(nèi)存泄漏。
Handler造成的內(nèi)存泄漏
Handler屬于TLS(Thread Local Storage)變量,生命周期與Activity是不一致的,容易導(dǎo)致持有的對象無法正確被釋放
當(dāng)Android應(yīng)用程序啟動時,該應(yīng)用程序的主線程會自動創(chuàng)建一個Looper對象和與之關(guān)聯(lián)的MessageQueue。
當(dāng)主線程中實例化一個Handler對象后,它就會自動與主線程Looper的MessageQueue關(guān)聯(lián)起來。所有發(fā)送到MessageQueue的Messag都會持有Handler的引用,所以Looper會據(jù)此回調(diào)Handle的handleMessage()方法來處理消息。只要MessageQueue中有未處理的Message,Looper就會不斷的從中取出并交給Handler處理。
另外,主線程的Looper對象會伴隨該應(yīng)用程序的整個生命周期。
在Java中,非靜態(tài)內(nèi)部類和匿名類內(nèi)部類都會潛在持有它們所屬的外部類的引用,但是靜態(tài)內(nèi)部類卻不會。
當(dāng)該 Activity 被 finish() 掉時,延遲執(zhí)行任務(wù)的 Message 還會繼續(xù)存在于主線程中,它持有該 Activity 的 Handler 引用,所以此時 finish() 掉的 Activity 就不會被回收了從而造成內(nèi)存泄漏(因 Handler 為非靜態(tài)內(nèi)部類,它會持有外部類的引用,在這里就是指 SampleActivity)。
避免不必要的靜態(tài)成員變量
對于BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap等資源的使用,應(yīng)在Activity銷毀前及時關(guān)閉或注銷。
不使用WebView對象時,應(yīng)調(diào)用`destroy()`方法銷毀。
給你個示例吧:
package com.water.activity;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.telephony.CellLocation;
import android.telephony.NeighboringCellInfo;
import android.telephony.TelephonyManager;
import android.util.Log;
public class MyList extends Activity {
/**
* android API中的TelephonyManager對象,可以取得SIM卡中的信息
*/
private TelephonyManager telMgr;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
telMgr = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
if (telMgr.getSimState() == telMgr.SIM_STATE_READY) {
Log.i("MyList", "良好");
} else if (telMgr.getSimState() == telMgr.SIM_STATE_ABSENT) {
Log.i("MyList", "無SIM卡");
} else {
Log.i("MyList", "SIM卡被鎖定或未知的狀態(tài)");
}
Log.i("MyList", "電話狀態(tài)[0 無活動/1 響鈴/2 摘機]:" + getCallState());
Log.i("MyList", "電話方位:" + getCellLocation());
Log.i("MyList", "唯一的設(shè)備ID:" + getDeviceId());
Log.i("MyList", "設(shè)備的軟件版本號:" + getDeviceSoftwareVersion());
Log.i("MyList", "手機號:" + getLine1Number());
Log.i("MyList", "附近的電話的信息:" + getNeighboringCellInfo());
Log.i("MyList", "獲取ISO標(biāo)準(zhǔn)的國家碼,即國際長途區(qū)號:" + getNetworkCountryIso());
Log.i("MyList", "MCC+MNC:" + getNetworkOperator());
Log.i("MyList", "(當(dāng)前已注冊的用戶)的名字:" + getNetworkOperatorName());
Log.i("MyList", "當(dāng)前使用的網(wǎng)絡(luò)類型:" + getNetworkType());
Log.i("MyList", "手機類型:" + getPhoneType());
Log.i("MyList", "SIM卡的國家碼:" + getSimCountryIso());
Log.i("MyList", "獲取SIM卡提供的移動國家碼和移動網(wǎng)絡(luò)碼.5或6位的十進制數(shù)字:" + getSimOperator());
Log.i("MyList", "服務(wù)商名稱:" + getSimOperatorName());
Log.i("MyList", "SIM卡的序列號:" + getSimSerialNumber());
Log.i("MyList", "SIM的狀態(tài)信息:" + getSimState());
Log.i("MyList", "唯一的用戶ID:" + getSubscriberId());
Log.i("MyList", "取得和語音郵件相關(guān)的標(biāo)簽,即為識別符:" + getVoiceMailAlphaTag());
Log.i("MyList", "獲取語音郵件號碼:" + getVoiceMailNumber());
Log.i("MyList", "ICC卡是否存在:" + hasIccCard());
Log.i("MyList", "是否漫游:" + isNetworkRoaming());
Log.i("MyList", "獲取數(shù)據(jù)活動狀態(tài):" + getDataActivity());
Log.i("MyList", "獲取數(shù)據(jù)連接狀態(tài):" + getDataState());
}
/**
* 電話狀態(tài):br/
* CALL_STATE_IDLE 無任何狀態(tài)時br/
* CALL_STATE_OFFHOOK 接起電話時br/
* CALL_STATE_RINGING 電話進來時
*
* @return
*/
private int getCallState() {
return telMgr.getCallState();
}
/**
* 返回當(dāng)前移動終端的位置 br/
*
* @return
*/
private CellLocation getCellLocation() {
CellLocation location = telMgr.getCellLocation();
// 請求位置更新,如果更新將產(chǎn)生廣播,接收對象為注冊LISTEN_CELL_LOCATION的對象,需要的permission名稱為ACCESS_COARSE_LOCATION。
// location.requestLocationUpdate();
return location;
}
/**
* 唯一的設(shè)備ID:br/
* 如果是GSM網(wǎng)絡(luò),返回IMEI;如果是CDMA網(wǎng)絡(luò),返回MEIDbr/
* 需要權(quán)限:android.permission.READ_PHONE_STATE
*
* @return null if device ID is not available.
*/
private String getDeviceId() {
return telMgr.getDeviceId();
}
/**
* 返回移動終端的軟件版本:br/
* 例如:GSM手機的IMEI/SV碼。br/
*
* @return null if the software version is not available.
*/
private String getDeviceSoftwareVersion() {
return telMgr.getDeviceSoftwareVersion();
}
/**
* 手機號:br/
* 對于GSM網(wǎng)絡(luò)來說即MSISDN
*
* @return null if it is unavailable.
*/
private String getLine1Number() {
return telMgr.getLine1Number();
}
/**
* 返回當(dāng)前移動終端附近移動終端的信息:br/
* 類型:ListNeighboringCellInfobr/
* 需要權(quán)限:android.Manifest.permission#ACCESS_COARSE_UPDATES
*
* @return
*/
private ListNeighboringCellInfo getNeighboringCellInfo() {
// ListNeighboringCellInfo infos = telMgr.getNeighboringCellInfo();
// for (NeighboringCellInfo info : infos) {
// // 獲取鄰居小區(qū)號
// int cid = info.getCid();
//
// // 獲取鄰居小區(qū)LAC,LAC:
// // 位置區(qū)域碼。為了確定移動臺的位置,每個GSM/PLMN的覆蓋區(qū)都被劃分成許多位置區(qū),LAC則用于標(biāo)識不同的位置區(qū)。
// info.getLac();
// info.getNetworkType();
// info.getPsc();
//
// // 獲取鄰居小區(qū)信號強度
// info.getRssi();
// }
return telMgr.getNeighboringCellInfo();
}
/**
* 獲取ISO標(biāo)準(zhǔn)的國家碼,即國際長途區(qū)號。br/
* 注意:僅當(dāng)用戶已在網(wǎng)絡(luò)注冊后有效。br/
* 在CDMA網(wǎng)絡(luò)中結(jié)果也許不可靠。br/
*
* @return
*/
private String getNetworkCountryIso() {
return telMgr.getNetworkCountryIso();
}
/**
* MCC+MNC(mobile country code + mobile network code)br/
* 注意:僅當(dāng)用戶已在網(wǎng)絡(luò)注冊時有效。br/
* 在CDMA網(wǎng)絡(luò)中結(jié)果也許不可靠。br/
*
* @return
*/
private String getNetworkOperator() {
return telMgr.getNetworkOperator();
}
/**
* 按照字母次序的current registered operator(當(dāng)前已注冊的用戶)的名字br/
* 注意:僅當(dāng)用戶已在網(wǎng)絡(luò)注冊時有效。br/
* 在CDMA網(wǎng)絡(luò)中結(jié)果也許不可靠。
*
* @return
*/
private String getNetworkOperatorName() {
return telMgr.getNetworkOperatorName();
}
/**
* 當(dāng)前使用的網(wǎng)絡(luò)類型:br/
* NETWORK_TYPE_UNKNOWN 網(wǎng)絡(luò)類型未知 0br/
* NETWORK_TYPE_GPRS GPRS網(wǎng)絡(luò) 1br/
* NETWORK_TYPE_EDGE EDGE網(wǎng)絡(luò) 2br/
* NETWORK_TYPE_UMTS UMTS網(wǎng)絡(luò) 3br/
* NETWORK_TYPE_HSDPA HSDPA網(wǎng)絡(luò) 8br/
* NETWORK_TYPE_HSUPA HSUPA網(wǎng)絡(luò) 9br/
* NETWORK_TYPE_HSPA HSPA網(wǎng)絡(luò) 10br/
* NETWORK_TYPE_CDMA CDMA網(wǎng)絡(luò),IS95A 或 IS95B. 4br/
* NETWORK_TYPE_EVDO_0 EVDO網(wǎng)絡(luò), revision 0. 5br/
* NETWORK_TYPE_EVDO_A EVDO網(wǎng)絡(luò), revision A. 6br/
* NETWORK_TYPE_1xRTT 1xRTT網(wǎng)絡(luò) 7br/
* 在中國,聯(lián)通的3G為UMTS或HSDPA,移動和聯(lián)通的2G為GPRS或EGDE,電信的2G為CDMA,電信的3G為EVDObr/
*
* @return
*/
private int getNetworkType() {
return telMgr.getNetworkType();
}
/**
* 返回移動終端的類型:br/
* PHONE_TYPE_CDMA 手機制式為CDMA,電信br/
* PHONE_TYPE_GSM 手機制式為GSM,移動和聯(lián)通br/
* PHONE_TYPE_NONE 手機制式未知br/
*
* @return
*/
private int getPhoneType() {
return telMgr.getPhoneType();
}
/**
* 獲取ISO國家碼,相當(dāng)于提供SIM卡的國家碼。
*
* @return Returns the ISO country code equivalent for the SIM provider's
* country code.
*/
private String getSimCountryIso() {
return telMgr.getSimCountryIso();
}
/**
* 獲取SIM卡提供的移動國家碼和移動網(wǎng)絡(luò)碼.5或6位的十進制數(shù)字.br/
* SIM卡的狀態(tài)必須是 SIM_STATE_READY(使用getSimState()判斷).
*
* @return Returns the MCC+MNC (mobile country code + mobile network code)
* of the provider of the SIM. 5 or 6 decimal digits.
*/
private String getSimOperator() {
return telMgr.getSimOperator();
}
/**
* 服務(wù)商名稱:br/
* 例如:中國移動、聯(lián)通br/
* SIM卡的狀態(tài)必須是 SIM_STATE_READY(使用getSimState()判斷).
*
* @return
*/
private String getSimOperatorName() {
return telMgr.getSimOperatorName();
}
/**
* SIM卡的序列號:br/
* 需要權(quán)限:READ_PHONE_STATE
*
* @return
*/
private String getSimSerialNumber() {
return telMgr.getSimSerialNumber();
}
/**
* SIM的狀態(tài)信息:br/
* SIM_STATE_UNKNOWN 未知狀態(tài) 0br/
* SIM_STATE_ABSENT 沒插卡 1br/
* SIM_STATE_PIN_REQUIRED 鎖定狀態(tài),需要用戶的PIN碼解鎖 2br/
* SIM_STATE_PUK_REQUIRED 鎖定狀態(tài),需要用戶的PUK碼解鎖 3br/
* SIM_STATE_NETWORK_LOCKED 鎖定狀態(tài),需要網(wǎng)絡(luò)的PIN碼解鎖 4br/
* SIM_STATE_READY 就緒狀態(tài) 5
*
* @return
*/
private int getSimState() {
return telMgr.getSimState();
}
/**
* 唯一的用戶ID:br/
* 例如:IMSI(國際移動用戶識別碼) for a GSM phone.br/
* 需要權(quán)限:READ_PHONE_STATE
*
* @return
*/
private String getSubscriberId() {
return telMgr.getSubscriberId();
}
/**
* 取得和語音郵件相關(guān)的標(biāo)簽,即為識別符br/
* 需要權(quán)限:READ_PHONE_STATE
*
* @return
*/
private String getVoiceMailAlphaTag() {
return telMgr.getVoiceMailAlphaTag();
}
/**
* 獲取語音郵件號碼:br/
* 需要權(quán)限:READ_PHONE_STATE
*
* @return
*/
private String getVoiceMailNumber() {
return telMgr.getVoiceMailNumber();
}
/**
* ICC卡是否存在
*
* @return
*/
private boolean hasIccCard() {
return telMgr.hasIccCard();
}
/**
* 是否漫游:(在GSM用途下)
*
* @return
*/
private boolean isNetworkRoaming() {
return telMgr.isNetworkRoaming();
}
/**
* 獲取數(shù)據(jù)活動狀態(tài)br/
* DATA_ACTIVITY_IN 數(shù)據(jù)連接狀態(tài):活動,正在接受數(shù)據(jù)br/
* DATA_ACTIVITY_OUT 數(shù)據(jù)連接狀態(tài):活動,正在發(fā)送數(shù)據(jù)br/
* DATA_ACTIVITY_INOUT 數(shù)據(jù)連接狀態(tài):活動,正在接受和發(fā)送數(shù)據(jù)br/
* DATA_ACTIVITY_NONE 數(shù)據(jù)連接狀態(tài):活動,但無數(shù)據(jù)發(fā)送和接受br/
*
* @return
*/
private int getDataActivity() {
return telMgr.getDataActivity();
}
/**
* 獲取數(shù)據(jù)連接狀態(tài)br/
* DATA_CONNECTED 數(shù)據(jù)連接狀態(tài):已連接br/
* DATA_CONNECTING 數(shù)據(jù)連接狀態(tài):正在連接br/
* DATA_DISCONNECTED 數(shù)據(jù)連接狀態(tài):斷開br/
* DATA_SUSPENDED 數(shù)據(jù)連接狀態(tài):暫停br/
*
* @return
*/
private int getDataState() {
return telMgr.getDataState();
}
}
萬物互聯(lián)的物聯(lián)網(wǎng)時代的已經(jīng)來臨,ble藍牙開發(fā)在其中扮演著舉重若輕的角色。最近剛好閑一點,抽時間梳理下這塊的知識點。
涉及ble藍牙通訊的客戶端(開啟、掃描、連接、發(fā)送和接收數(shù)據(jù)、分包解包)和服務(wù)端(初始化廣播數(shù)據(jù)、開始廣播、配置Services、Server回調(diào)操作)整個環(huán)節(jié)以及一些常見的問題即踩過的一些坑。
比如
1、在Android不同版本或不同手機的適配問題,掃描不到藍牙設(shè)備
2、如何避免ble藍牙連接出現(xiàn)133錯誤?
3、單次寫的數(shù)據(jù)大小有20字節(jié)限制,如何發(fā)送長數(shù)據(jù)
藍牙有傳統(tǒng)(經(jīng)典)藍牙和低功耗藍牙BLE(Bluetooth Low Energy)之分,兩者的開發(fā)的API不一樣,本文主講Ble藍牙開發(fā),傳統(tǒng)藍牙不展開,有需要的可以自行了解。
相對傳統(tǒng)藍牙,BLE低功耗藍牙,主要特點是快速搜索,快速連接,超低功耗保持連接和數(shù)據(jù)傳輸。
客戶端
服務(wù)端
Android4.3(API Level 18)開始引入BLE的核心功能并提供了相應(yīng)的 API。應(yīng)用程序通過這些 API 掃描藍牙設(shè)備、查詢 services、讀寫設(shè)備的 characteristics(屬性特征)等操作。
BLE藍牙協(xié)議是GATT協(xié)議, BLE相關(guān)類不多, 全都位于android.bluetooth包和android.bluetooth.le包的幾個類:
android.bluetooth.
.BluetoothGattService 包含多個Characteristic(屬性特征值), 含有唯一的UUID作為標(biāo)識
.BluetoothGattCharacteristic 包含單個值和多個Descriptor, 含有唯一的UUID作為標(biāo)識
.BluetoothGattDescriptor 對Characteristic進行描述, 含有唯一的UUID作為標(biāo)識
.BluetoothGatt 客戶端相關(guān)
.BluetoothGattCallback 客戶端連接回調(diào)
.BluetoothGattServer 服務(wù)端相關(guān)
.BluetoothGattServerCallback 服務(wù)端連接回調(diào)
android.bluetooth.le.
.AdvertiseCallback 服務(wù)端的廣播回調(diào)
.AdvertiseData 服務(wù)端的廣播數(shù)據(jù)
.AdvertiseSettings 服務(wù)端的廣播設(shè)置
.BluetoothLeAdvertiser 服務(wù)端的廣播
.BluetoothLeScanner 客戶端掃描相關(guān)(Android5.0新增)
.ScanCallback 客戶端掃描回調(diào)
.ScanFilter 客戶端掃描過濾
.ScanRecord 客戶端掃描結(jié)果的廣播數(shù)據(jù)
.ScanResult 客戶端掃描結(jié)果
.ScanSettings 客戶端掃描設(shè)置
BLE設(shè)備分為兩種設(shè)備: 客戶端(也叫主機/中心設(shè)備/Central), 服務(wù)端(也叫從機/外圍設(shè)備/peripheral)
客戶端的核心類是 BluetoothGatt
服務(wù)端的核心類是 BluetoothGattServer 和 BluetoothLeAdvertiser
BLE數(shù)據(jù)的核心類是 BluetoothGattCharacteristic 和 BluetoothGattDescriptor
下面詳細講解下客戶端和服務(wù)端的開發(fā)步驟流程
安卓手機涉及藍牙權(quán)限問題,藍牙開發(fā)需要在AndroidManifest.xml文件中添加權(quán)限聲明:
在搜索設(shè)備之前需要詢問打開手機藍牙:
注意: BLE設(shè)備地址是動態(tài)變化(每隔一段時間都會變化),而經(jīng)典藍牙設(shè)備是出廠就固定不變了!
通過掃描BLE設(shè)備,根據(jù)設(shè)備名稱區(qū)分出目標(biāo)設(shè)備targetDevice,下一步實現(xiàn)與目標(biāo)設(shè)備的連接,在連接設(shè)備之前要停止搜索藍牙;停止搜索一般需要一定的時間來完成,最好調(diào)用停止搜索函數(shù)之后加以100ms的延時,保證系統(tǒng)能夠完全停止搜索藍牙設(shè)備。停止搜索之后啟動連接過程;
BLE藍牙的連接方法相對簡單只需調(diào)用connectGatt方法;
參數(shù)說明
與設(shè)備建立連接之后與設(shè)備通信,整個通信過程都是在BluetoothGattCallback的異步回調(diào)函數(shù)中完成;
BluetoothGattCallback中主要回調(diào)函數(shù)如下:
上述幾個回調(diào)函數(shù)是BLE開發(fā)中不可缺少的;
當(dāng)調(diào)用targetdDevice.connectGatt(context, false, gattCallback)后系統(tǒng)會主動發(fā)起與BLE藍牙設(shè)備的連接,若成功連接到設(shè)備將回調(diào)onConnectionStateChange方法,其處理過程如下:
判斷newState == BluetoothGatt.STATE_CONNECTED表明此時已經(jīng)成功連接到設(shè)備;
mBluetoothGatt.discoverServices();
掃描BLE設(shè)備服務(wù)是安卓系統(tǒng)中關(guān)于BLE藍牙開發(fā)的重要一步,一般在設(shè)備連接成功后調(diào)用,掃描到設(shè)備服務(wù)后回調(diào)onServicesDiscovered()函數(shù),函數(shù)原型如下:
BLE藍牙開發(fā)主要有負責(zé)通信的BluetoothGattService完成的。當(dāng)且稱為通信服務(wù)。通信服務(wù)通過硬件工程師提供的UUID獲取。獲取方式如下:
具體操作方式如下:
開啟監(jiān)聽,即建立與設(shè)備的通信的首發(fā)數(shù)據(jù)通道,BLE開發(fā)中只有當(dāng)客戶端成功開啟監(jiān)聽后才能與服務(wù)端收發(fā)數(shù)據(jù)。開啟監(jiān)聽的方式如下:
BLE單次寫的數(shù)據(jù)量大小是有限制的, 通常是20字節(jié) ,可以嘗試通過requestMTU增大,但不保證能成功。分包寫是一種解決方案,需要定義分包協(xié)議,假設(shè)每個包大小20字節(jié),分兩種包,數(shù)據(jù)包和非數(shù)據(jù)包。對于數(shù)據(jù)包,頭兩個字節(jié)表示包的序號,剩下的都填充數(shù)據(jù)。對于非數(shù)據(jù)包,主要是發(fā)送一些控制信息。
監(jiān)聽成功后通過向 writeCharacteristic寫入數(shù)據(jù)實現(xiàn)與服務(wù)端的通信。寫入方式如下:
其中:value一般為Hex格式指令,其內(nèi)容由設(shè)備通信的藍牙通信協(xié)議規(guī)定;
若寫入指令成功則回調(diào)BluetoothGattCallback中的onCharacteristicWrite()方法,說明將數(shù)據(jù)已經(jīng)發(fā)送給下位機;
若發(fā)送的數(shù)據(jù)符合通信協(xié)議,則服務(wù)端會向客戶端回復(fù)相應(yīng)的數(shù)據(jù)。發(fā)送的數(shù)據(jù)通過回調(diào)onCharacteristicChanged()方法獲取,其處理方式如下:
通過向服務(wù)端發(fā)送指令獲取服務(wù)端的回復(fù)數(shù)據(jù),即可完成與設(shè)備的通信過程;
當(dāng)與設(shè)備完成通信之后之后一定要斷開與設(shè)備的連接。調(diào)用以下方法斷開與設(shè)備的連接:
源碼上傳在CSDN上了,有需要的可以借鑒。
===== Android藍牙Ble通訊Demo示例源碼–掃描,連接,發(fā)送和接收數(shù)據(jù),分包解包
BLE單次寫的數(shù)據(jù)量大小是有限制的,通常是20字節(jié),可以嘗試通過requestMTU增大,但不保證能成功。分包寫是一種解決方案,需要定義分包協(xié)議,假設(shè)每個包大小20字節(jié),分兩種包,數(shù)據(jù)包和非數(shù)據(jù)包。對于數(shù)據(jù)包,頭兩個字節(jié)表示包的序號,剩下的都填充數(shù)據(jù)。對于非數(shù)據(jù)包,主要是發(fā)送一些控制信息。
總體流程如下:
1、定義通訊協(xié)議,如下(這里只是個舉例,可以根據(jù)項目需求擴展)
2、封裝通用發(fā)送數(shù)據(jù)接口(拆包)
該接口根據(jù)會發(fā)送數(shù)據(jù)內(nèi)容按最大字節(jié)數(shù)拆分(一般20字節(jié))放入隊列,拆分完后,依次從隊列里取出發(fā)送
3、封裝通用接收數(shù)據(jù)接口(組包)
該接口根據(jù)從接收的數(shù)據(jù)按協(xié)議里的定義解析數(shù)據(jù)長度判讀是否完整包,不是的話把每條消息累加起來
4、解析完整的數(shù)據(jù)包,進行業(yè)務(wù)邏輯處理
5、協(xié)議還可以引入加密解密,需要注意的選算法參數(shù)的時候,加密后的長度最好跟原數(shù)據(jù)長度一致,這樣不會影響拆包組包
一般都是Android版本適配以及不同ROM機型(小米/紅米、華為/榮耀等)(EMUI、MIUI、ColorOS等)的權(quán)限問題
藍牙開發(fā)中有很多問題,要靜下心分析問題,肯定可以解決的,一起加油;
第一種點擊事件
在xml中設(shè)置onclick屬性
android:onClick="myOnclick"
第二種;獲取Button然后一個一個單獨綁定點擊事件
"
xmlns:tools=" "
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"?
android:id="@+id/btn_imgBtn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="myOnclick"
android:text="imageButton"
/
android:id="@+id/btn_imgView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="myOnclick"
android:text="imageView"
/
public class MainActivity extends ActionBarActivity {
private Button btnImageBtn;
private Button btnImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
? ? super.onCreate(savedInstanceState);
? ? setContentView(R.layout.activity_main);
? ? btnImageBtn = (Button) findViewById(R.id.btn_imgBtn);
? ? btnImageView = ?(Button) findViewById(R.id.btn_imgView);
btnImageBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "點擊ImageButton", Toast.LENGTH_SHORT).show();
}
});
? ? btnImageView.setOnClickListener(new MyListener());
}
第三種:寫一個類(MyListener)實現(xiàn)OnClickListener接口,然后Button在設(shè)置onclickListener的時候new一個MyListener
btnImageView.setOnClickListener(new MyListener());
class MyListener implements OnClickListener{
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_imgBtn:
Toast.makeText(MainActivity.this, "點擊ImageButton", Toast.LENGTH_SHORT).show();
break;
? ? ?case R.id.btn_imgView:
? ? ? Toast.makeText(MainActivity.this, "點擊imageView", Toast.LENGTH_SHORT).show();
break;
}?
第四種:整個類(MianActivity)實現(xiàn)onclickListener的接口
跳轉(zhuǎn)界面
Intent:意圖,用于訪問android中的組件
用Intent跳轉(zhuǎn)界面(activity)
第一步:new一個Intent()
Intent intent1 = new Intent(MainActivity.this,ImageButtonActivity.class);
startActivity(intent1);
public void myOnclick(View view){
?switch (view.getId()) {
case R.id.btn_imgBtn:
Intent intent1 = new Intent(MainActivity.this,ImageButtonActivity.class);
startActivity(intent1);
break;
? ? case R.id.btn_imgView:
? ? ?Intent intent2 = new Intent(MainActivity.this,ImageViewActivity.class);
startActivity(intent2);
break;
Intent intent = new Intent(當(dāng)前的activity,跳轉(zhuǎn)到的acticvity.class);
startActivity(intent);
3.ImageView
展示方式:scaleType:
4.ImageButton:
觸摸事件:當(dāng)控件或者屏幕唄觸摸的時候,產(chǎn)生的反應(yīng)
public boolean onTouchEvent(MotionEvent event) {
}
imageButton:現(xiàn)在已經(jīng)唄button代替,用于展示圖片的按鈕。不能顯示文字。
imageView
scaleType:圖片展示的方式
fitStart:展示在控件的上方
fitCenter:展示在控件的中間
fitEnd;展示在控件的下方
fitXY:不按照比例拉伸
matrix:矩陣模式
matrix可以設(shè)置圖片旋轉(zhuǎn),縮放。移動
獲取圖片的高度和寬度
int h = imgView.getDrawable().getIntrinsicHeight();
int w = imgView.getDrawable().getIntrinsicWidth();
Matrix m = new Matrix();
m.postRotate(45);
m.postRotate(45, w/2, h/2);
imgView.setImageMatrix(m);
移動事件:
按下:MotionEvent.ACTION_DOWN
抬起:MotionEvent.ACTION_UP
移動:MotionEvent.ACTION_MOVE
獲取當(dāng)前的移動事件,
event.getAction()
"
xmlns:tools=" "
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/img_01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/ss"
android:scaleType="fitXY"/
android:id="@+id/img_02"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/gl"
android:visibility="gone"
android:scaleType="fitXY"/
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/bird"
android:layout_gravity="center"
/
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#ff0000"
android:gravity="center"
android:text="小鳥飛"/
public class MainActivity extends Activity {
private ImageView img01;
private ImageView img02;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img01 = (ImageView) findViewById(R.id.img_01);
img02 = (ImageView) findViewById(R.id.img_02);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//System.out.println("被摸了");
if(event.getAction()==MotionEvent.ACTION_UP){
Log.v("TAG", "被抬起來");
if(img01.getVisibility()==View.VISIBLE){
img01.setVisibility(View.GONE);
img02.setVisibility(View.VISIBLE);
}else{
img01.setVisibility(View.VISIBLE);
img02.setVisibility(View.GONE);
}
}else if(event.getAction()==MotionEvent.ACTION_DOWN){
Log.v("TAG", "被按下了");
}else if(event.getAction()==MotionEvent.ACTION_MOVE){
Log.v("TAG", "移動了");
}
return super.onTouchEvent(event);
}
當(dāng)前題目:android示例,Android開發(fā)詳解
網(wǎng)頁鏈接:http://chinadenli.net/article10/dsijego.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計、企業(yè)網(wǎng)站制作、搜索引擎優(yōu)化、定制開發(fā)、網(wǎng)站內(nèi)鏈、靜態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)