手機和手錶的通訊是通過數據層來完成的,數據層這個概念這裏解釋一下:手機和手錶通過藍牙連接起來,相同包的應用(比如手機有一個包名爲com.soufun.app的應用,手錶也有一個相同包名的應用)由系統自動分配一個數據層的概念,所有涉及手機手錶之間的數據通訊的工作都通過數據層來實現。只要有相同包名的應用同時安裝在了手機和手錶上,則系統就自動生成一個數據層來管理手機和手錶的數據變化。
手機和手錶之間的數據交流通過數據層來完成,兩者都可以向數據層發送消息,從而引起數據層數據事件發生,也可以分別設置各自的監聽器來監聽數據層的這些事件,從而實現接收數據的功能。
手機和手錶的監聽器可以看做是客戶端,兩者共同監聽‘數據層’的數據變化,無論是從手機還是手錶發送消息,都會引起數據層的狀態變化。這個僅限於發送數據項請求,發送消息請求 sendMessage 是單方向的請求,發送請求之前需要事先知道接收方的節點信息,這個稍後會講到。
同步數據項的時候(即發送dataItemRequest),凡是有監聽器的地方都可以接收到這個請求。這一點類似Android中的intent機制,只不過需要程序員自己去管理和篩選這些請求。
數據的發送
數據的發送分兩部分,一個是數據項DataItem的同步,這個是多向的,這個發送方明確,接收方不明確;還有一種是定向發送,即指定一臺設備發送數據,這個發送方和接收方都是明確的。前者發送出消息之後在手機和手錶的數據層事件監聽器中都會觸發數據事件,後者,只會觸發接收端與消息監聽相關的回調方法。
同步數據項(DataItem)介紹
同步數據之前需要先與數據層建立一個連接,建立連接的一般代碼如下:
GoogleApiClient mGoogleAppiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(new ConnectionCallbacks() {
@Override
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "onConnected: " + connectionHint);
//連接成功
}
@Override
public void onConnectionSuspended(int cause) {
Log.d(TAG, "onConnectionSuspended: " + cause);
}
})
.addOnConnectionFailedListener(new OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.d(TAG, "onConnectionFailed: " + result);
}
})
.addApi(Wearable.API)
.build();
mGoogleAppiClient.connect();
在調用了connect()方法之後,監聽onConnected事件。 至於發送數據的方法可以在onConnected方法中寫。也可以在確保連上之後後續的代碼中寫。在手錶上這部分的代碼需要做一個處理,就是在onConnected方法中寫發送數據的邏輯,在發送數據的結果有一個回調接口,在這個接口中再把這個已經和數據層建立的連接斷開,官方demo這麼寫的,估計是爲了省電。當然也可以保持一個長連接,所有向數據層發送數據的方法都可以通過這個連接發送。
連接已經建好之後,再要了解一下數據通信的載體Dataitem:
一個DataItem定義了手機和手錶之間的數據交互接口,一個DataItem由兩部分組成:1、payload 存放數據;2、Path 用於數據層監聽器的識別 路徑唯一,DataItem可以看成是對手機手錶之間交互數據的一個載體。
但是通常不直接使用Dataitem,而是使用一些系統API
使用方法:
- 創建一個PutDataMapRequest對象,設置DataItem路徑path。
- 調用PutDataMapRequest.getDataMap() 方法來獲取一個DataMap
- 調用DataMap的各種put...()方法來設置值。
- 調用PutDataMapRequest.asPutDataRequest()方法來獲得一個PutDataRequest對象。
- 調用DataApi.putDataItem()方法來請求系統創建DataItem。 如果手機和可穿戴設備斷開了連接,那麼數據會被緩存並在下次重建連接的時候同步數據。
一般的使用示例:
if (gaApiClient.isConnected()) {
PutDataMapRequest mapRequest = PutDataMapRequest.create("/samedata");
mapRequest.getDataMap().putString("key", "soufun" + (flag++));
PutDataRequest dataRequest = mapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(gaApiClient, dataRequest).setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
@Override
public void onResult(DataItemResult arg0) {
if (!arg0.getStatus().isSuccess()) {
} else {
Log.i("info", "發送成功了");
Toast.makeText(MainActivity.this, "Notification Success", Toast.LENGTH_LONG).show();
}
}
});
} else {
Log.i("info", "沒連上");
}
可以發送簡單的數據,使用Datamap以鍵值對的形式發送數據,但是大小有限制在100kb左右。如果要發送大的數據,可以爲DataItem附加一個Asset,這個Asset可以足夠的大。一個通常的使用場景是,手機端的APP通過網絡下載了一張大圖,然後壓縮到一個適合手錶查看的大小,然後放置到一個Asset中發送給手錶。
這裏有兩種使用方式:
1、 使用PutDataRequest
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
Asset asset = createAssetFromBitmap(bitmap);
PutDataRequest request = PutDataRequest.create("/image");
request.putAsset("profileImage", asset);
Wearable.DataApi.putDataItem(mGoogleApiClient, request);
2、 使用PutDataMapRequest
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
Asset asset = createAssetFromBitmap(bitmap);
PutDataMapRequest dataMap = PutDataMapRequest.create("/image");
dataMap.getDataMap().putAsset("profileImage", asset)
PutDataRequest request = dataMap.asPutDataRequest();
PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi
.putDataItem(mGoogleApiClient, request);
使用Asset發送數據的本質是,使用Asset在手機和手錶之間建立一個流的通道。關於流的一切操作也可以通過Asset來完成。
Google服務包提供的數據訪問API共有三個 DataAPI、NodeApi和MessageApi 其中DataApi主要負責管理同步Dataitem相關的功能。NodeApi和MessageApi要結合起來使用,NodeApi得到所有連接的節點,MessageApi需要這個節點才能完成它自己的與消息有關的一些業務邏輯。 DataApI還有一個比較常用的方法,刪除Dataitem
下邊是一個 簡單的例子:
final Uri dataItemUri = new Uri.Builder().scheme(WEAR_URI_SCHEME)
.path(Constants.BOTH_PATH).build();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Deleting Uri: " + dataItemUri.toString());
}
Wearable.DataApi.deleteDataItems(mGoogleApiClient, dataItemUri)
.setResultCallback(this);
這個適用於發送一些簡單的請求,比如刪除通知,掛斷電話,取消提醒等。
發送消息相關
發送消息有兩個重要的方法:
獲取已經連接的設備的節點:
public Collection<String> getNodes() {
HashSet<String> results = new HashSet<String>();
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi
.getConnectedNodes(mGoogleApiClient).await();
for (Node node : nodes.getNodes()) {
results.add(node.getId());
}
return results;
}
和發送消息:
SendMessageResult result = Wearable.MessageApi.sendMessage(
mGoogleApiClient, node, pathString, null).await();
if (!result.getStatus().isSuccess()) {
handler.sendEmptyMessage(SEND_MESSAGE_FAIL);
} else {
Log.e("info", "發送成功");
}
注意到這兩個重要步驟裏邊都有一個await方法,這個方法是同步的,過程中會一直阻塞,所以需要各自放在線程中去執行。
以下是發送消息的一個典型例子:
public void openOnPhone(final String openactivity) {
if (mGoogleApiClient.isConnected()) {
new Thread() {
public void run() {
Collection<String> nodes = getNodes();
if (nodes.size() > 0)
for (String string : nodes) {
Log.e("info", "" + string);
Message msg = new Message();
Bundle data = new Bundle();
data.putString("node", string);
data.putString("path", "" + openactivity);
msg.setData(data);
handler.sendMessage(msg);
}
};
}.start();
} else {
Toast.makeText(mContext, "無連接", Toast.LENGTH_LONG).show();
}
}
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == SEND_MESSAGE_FAIL) {
Toast.makeText(mContext, "打開失敗", Toast.LENGTH_LONG).show();
} else
sendMessage2Phone(msg);
}
};
protected void sendMessage2Phone(Message msg) {
final String pathString = msg.getData().getString("path");
final String node = msg.getData().getString("node");
new Thread() {
public void run() {
String threadNameString = Thread.currentThread().getName();
Log.e("info", "當前的線程:" + threadNameString);
SendMessageResult result = Wearable.MessageApi.sendMessage(
mGoogleApiClient, node, pathString, null).await();
if (!result.getStatus().isSuccess()) {
handler.sendEmptyMessage(SEND_MESSAGE_FAIL);
} else {
Log.e("info", "發送成功");
}
};
}.start();
}
發送消息和發出Dataitem的相關請求,都需要在請求中附加一個來標識這個請求唯一性的path。path以反斜槓開頭,例如"/openOneActivity"。
數據的接收
數據的接收即監聽數據層數據的變化,當監聽器中的事件觸發的時候再進行後續的業務處理。
監聽數據層的數據變化有兩種實現,一種在service中使用,實現類WearableListenerService 並監聽onDataChanged等事件。一種是在Activity中設置DataApi.DataListener, NodeApi.NodeListener, MessageListener 等。兩種監聽器的實現的區別也很明顯,前一種無界,長期運行在後臺,後一種Activity消失的話監聽器也失效了。
使用這個service的時候要注意,這個service的生命週期是由系統來管理,所以註冊的時候要加上一個特定的action,這個action是爲了在手機和手錶連接起來的時候由Google服務自動啓動的。
以下是一個實例:
<service android:name=".DataService" >
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
<intent-filter>
<action android:name="com.soufun.app.android.wearable.synchronizednotifications.DISMISS" />
</intent-filter>
</service>
例子中還有一個action是自定義的功能,這個service也是一個普通的service,只不過它的生命週期是由系統來控制的,但仍然可以當做一個普通的service來使用。注意不需要程序員手動去啓動這個service。
在這個service的類中重寫onDataChanged方法,來處理數據層事件,也是間接地實現了接收前邊講到的各種方法發送過來的數據的功能。有onMessageReceived,來接收另一端發送過來的各種消息。
消息的處理比較簡單
下邊這個例子要做的是接收其他設備發送過來的消息打開相應的activity:
@Override
public void onMessageReceived(final MessageEvent messageEvent) {
if (messageEvent.getPath().equals(Constants.OPEN_PATH)) {//通過path識別消息類型
Intent startIntent = new Intent(this, OpenActivity.class);
startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(startIntent);
}
}
下邊是一個典型的onDataChanged方法的實現
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
super.onDataChanged(dataEvents);
Log.i("info", "數據發生變化嘍");
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
String typeString = event.getDataItem().getUri().getPath();
DataItem item = event.getDataItem();
DataMap map = DataMapItem.fromDataItem(item).getDataMap();
if ("/image".equals(typeString)) {
Log.i("info", "是圖片");
Asset mAsset = map.getAsset("asset");
Bitmap bitmap = loadBitmapFromAsset(mAsset);
sendPicNoti(bitmap);
} else {
sendNotification();
String getString = map.getString("key");
Log.i("info", "數據變化類型事件:" + getString);
}
} else if (event.getType() == DataEvent.TYPE_DELETED) {
Log.i("info", "數據刪除");
}
}
}
/**
* 從asset中抽取出一個bitmap
*
* @param asset
* @return
*/
public Bitmap loadBitmapFromAsset(Asset asset) {
if (asset == null) {
throw new IllegalArgumentException("Asset must be non-null");
}
ConnectionResult result = mGoogleApiClient.blockingConnect(20000,
TimeUnit.MILLISECONDS);
if (!result.isSuccess()) {
return null;
}
// convert asset into a file descriptor and block until it's ready
InputStream assetInputStream = Wearable.DataApi
.getFdForAsset(mGoogleApiClient, asset).await()
.getInputStream();
mGoogleApiClient.disconnect();
if (assetInputStream == null) {
Log.w("info", "Requested an unknown Asset.");
return null;
}
// decode the stream into a bitmap
return BitmapFactory.decodeStream(assetInputStream);
}
注意代碼中的方法 loadBitmapFromAsset 是從接收到的Asset中提取出來包含的圖片,
可以看到onDatachangged方法是可能同時接收好多個事件的,比如手機向數據層發送了很多事件,而手錶上的APP中是註冊了數據層事件監聽器的,但是兩者之間並沒有連接所以這些‘積蓄’的事件在手錶連上的時候便會陸續過來,這點之前也提到了。當然也可能手機連續的向手錶發送了很多的事件遍歷這些事件。
在onDataChanged方法中遍歷這些事件,系統將事件分爲了兩類—TYPECHANGED和TYPEDELETED兩種,這兩種類型的事件分別通過系統不同的API來發送請求。 TYPE_CHANGED類,顧名思義,就是數據變化的事件類型,即DataItem必須發生了變化,這裏的DataItem的變化是指在一個與數據層的連接從連接成功到連接斷開這之間,如果有重複發送的完全相同的DataItem(指的DataItem的兩個元素—儲存數據的payload 和標識該DataItem唯一性的path,同時相同),則第二次發送出的DataItem並不會觸發數據層監聽器的onDataChanged方法。那麼如果就想兩次都發送相同的數據怎麼做呢?要想連續發送相同的內容,則前後兩次發送的DataItem不能相同,Dataitem的唯一標識path是不能改變的,那麼只能改變內部的數據,在前邊介紹的兩種發送數據的方式中都是以鍵值對的形式加入到Dataitem中的,這裏可以向裏邊添加一個無意義但是能保證不重複的鍵值對,只在發送請求的時候加入,在接收數據的時候卻不對這個鍵值對做處理,這樣既保證每次發送的Dataitem都不一樣,也可以拿到自己所期望的相同的數據。
而發送消息的方法,sendMessage則不會有這種現象發生。
另一種數據層監聽器的實現是使用在Activity中實現。
下邊是一個典型的使用Activity來監聽數據層變化事件的例子。
首先連接數據層
public class MainActivity extends Activity implements ConnectionCallbacks, OnConnectionFailedListener,
DataApi.DataListener, NodeApi.NodeListener, MessageListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gaApiClient = new GoogleApiClient.Builder(this).addApi(Wearable.API).addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).build();
}
@Override
protected void onResume() {
super.onResume();
gaApiClient.connect();
}
@Override
public void onConnected(Bundle arg0) {
Log.i("info", "手機連上了");
// 註冊各種監聽器,在activity上用
Wearable.DataApi.addListener(gaApiClient, this);
Wearable.MessageApi.addListener(gaApiClient, this);
Wearable.NodeApi.addListener(gaApiClient, this);
flag = 0;
}
@Override
protected void onStop() {
super.onStop();
if (gaApiClient.isConnected()) {
//移除各種監聽器
Wearable.DataApi.removeListener(gaApiClient, this);
Wearable.MessageApi.removeListener(gaApiClient, this);
Wearable.NodeApi.removeListener(gaApiClient, this);
gaApiClient.disconnect();
}
}
@Override
public void onConnectionSuspended(int arg0) {
}
@Override
public void onConnectionFailed(ConnectionResult arg0) {
}
@Override
public void onPeerConnected(Node arg0) {
Log.i("info", "手機和手錶連上了");
}
@Override
public void onPeerDisconnected(Node arg0) {
Log.i("info", "手機和手錶斷開了");
}
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
Log.i("info", "手機上有數據變化");
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
String typeString = event.getDataItem().getUri().getPath();
DataItem item = event.getDataItem();
DataMap map = DataMapItem.fromDataItem(item).getDataMap();
if (pString.equals(typeString)) {
Intent intent = new Intent(this, PicActivity.class);
startActivity(intent);
}
} else if (event.getType() == DataEvent.TYPE_DELETED) {
Log.i("info", "數據刪除");
}
}
}
@Override
public void onMessageReceived(MessageEvent arg0) {
Log.i("info", "手機上有消息過來");
}
}
而監聽到數據變化或者消息的處理,都是取出這個請求中的唯一標識path,判斷這個請求要執行什麼樣的業務,然後做相應的處理。