📜 ⬆️ ⬇️

Development experience for Android Wear

I want to share with colleagues the experience gained in the development of Android Wear.

All the important points are easiest to show by the example of an application that shows the battery level on the clock and smartphone.

Download Android Studio.

Create a new project:
')


We choose both devices:



Further, everything is standard:






As a result, we get a good billet for both devices, with empty activities:



ListenerService itself will not appear, below will tell how to add it.

To connect our devices, a tricky layer was invented. Applications can exchange messages through this layer. Sending messages should take place in a separate thread. Very detailed implementation of this task is described here .

You must connect to GoogleApiClient, then send a message in a separate thread. In the examples, this is described in detail, but I decided to put all the work with messages into a separate service and it turned out quite compact.
Here is our ListenerService , it is the same for both parts of the project.

package com.rusdelphi.batterywatcher; import android.content.Intent; import android.content.SharedPreferences; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.wearable.MessageApi; import com.google.android.gms.wearable.MessageEvent; import com.google.android.gms.wearable.Node; import com.google.android.gms.wearable.NodeApi; import com.google.android.gms.wearable.Wearable; import com.google.android.gms.wearable.WearableListenerService; import java.util.concurrent.TimeUnit; /** * Created by User on 04.01.2015. */ public class ListenerService extends WearableListenerService { private SharedPreferences prefs; private boolean mIsAlarmOn; GoogleApiClient googleClient; public static final String ACTION_SM = "com.rusdelphi.batterywatcher.action.SM"; public static final String ACTION_SM_PARAM = "com.rusdelphi.batterywatcher.action.SM.PARAM"; private static final String WEAR_MESSAGE_PATH = "batterywatcher_message_path"; public ListenerService() { } @Override public void onCreate() { super.onCreate(); googleClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .build(); googleClient.connect(); } @Override public void onDestroy() { if (null != googleClient && googleClient.isConnected()) { googleClient.disconnect(); } super.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) { final String action = intent.getAction(); if (ACTION_SM.equals(action)) { final String param1 = intent.getStringExtra(ACTION_SM_PARAM); if (googleClient.isConnected()) { new Thread(new Runnable() { @Override public void run() { NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleClient).await(); for (Node node : nodes.getNodes()) { MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(googleClient, node.getId(), WEAR_MESSAGE_PATH, param1.getBytes()).await(); if (result.getStatus().isSuccess()) { Log.d("main", "Message: {" + param1 + "} sent to: " + node.getDisplayName()); } else { // Log an error Log.d("main", "ERROR: failed to send Message"); } } } }).start(); } if (!googleClient.isConnected()) new Thread(new Runnable() { @Override public void run() { ConnectionResult connectionResult = googleClient.blockingConnect(30, TimeUnit.SECONDS); NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleClient).await(); for (Node node : nodes.getNodes()) { MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(googleClient, node.getId(), WEAR_MESSAGE_PATH, param1.getBytes()).await(); if (result.getStatus().isSuccess()) { Log.d("main", "Message: {" + param1 + "} sent to: " + node.getDisplayName()); } else { // Log an error Log.d("main", "ERROR: failed to send Message"); } } } }).start(); } } return super.onStartCommand(intent, flags, startId); } @Override public void onMessageReceived(MessageEvent messageEvent) { if (messageEvent.getPath().equals(WEAR_MESSAGE_PATH)) { final String message = new String(messageEvent.getData()); Intent messageIntent = new Intent(); messageIntent.setAction(Intent.ACTION_SEND); messageIntent.putExtra("message", message); LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent); } else { super.onMessageReceived(messageEvent); } } } 

In both manifest you need to add it:

 <service android:name=".ListenerService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter> </service> 

During the creation of the service, we collect GoogleApiClient and connect to the data layer.

The onMessageReceived event is raised when a message is received. At the received event (MessageEvent) we look at the destination folder (getPath ()). If this is our folder, we get the data (messageEvent.getData ()). Further, these data can be saved in the settings database. In general, use as needed. And we will send them to our main program (MainActivity) using LocalBroadcastManager. But for this, the local receiver in it must be registered. We will do this at the start, and we will unregister at the exit.

To send a message to another device, we will start the service with the necessary data.

GoogleApiClient may or may not already be connected to the service. If it is not yet connected, then you need to start blockingConnect, in other words, make it connect directly, blocking the connection. Do all this in a separate thread, because This all works asynchronously.

Here is the MainActivity code for the mobile device:

 package com.rusdelphi.batterywatcher; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.BatteryManager; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; public class MainActivity extends Activity { public static String mWatchLevel = "?", mSmartphoneLevel = "?"; private TextView mWatch; private TextView mSmartphone; MessageReceiver messageReceiver = new MessageReceiver(); private BroadcastReceiver mBatteryLevelReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWatch = (TextView) findViewById(R.id.tv_watch); mSmartphone = (TextView) findViewById(R.id.tv_smartphone); IntentFilter batteryLevelFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); mBatteryLevelReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent i) { int level = i.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); int scale = i.getIntExtra(BatteryManager.EXTRA_SCALE, -1); mSmartphoneLevel = new java.text.DecimalFormat("0.0") .format((((float) level / (float) scale) * 100.0f)) + "%"; sendMessage(MainActivity.this, mSmartphoneLevel); updateUI(); } }; registerReceiver(mBatteryLevelReceiver, batteryLevelFilter); IntentFilter messageFilter = new IntentFilter(Intent.ACTION_SEND); LocalBroadcastManager.getInstance(this).registerReceiver(messageReceiver, messageFilter); } public static void sendMessage(Context context, String param1) { Intent intent = new Intent(context, ListenerService.class); intent.setAction(ListenerService.ACTION_SM); intent.putExtra(ListenerService.ACTION_SM_PARAM, param1); context.startService(intent); } public void updateUI() { mWatch.setText(mWatchLevel); mSmartphone.setText(mSmartphoneLevel); } @Override protected void onResume() { super.onResume(); updateUI(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } @Override protected void onDestroy() { LocalBroadcastManager.getInstance(this).unregisterReceiver(messageReceiver); if (mBatteryLevelReceiver!=null) { unregisterReceiver(mBatteryLevelReceiver); mBatteryLevelReceiver=null; } super.onDestroy(); } public class MessageReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String message = intent.getStringExtra("message"); mWatchLevel = message; updateUI(); } } } 

Here at the start we create a receiver that receives messages about the device’s batteries. As soon as we received the message (onReceive), we send it with a message to the data layer (sendMessage) and update the values ​​of the variables (updateUI). Then we register the local receiver (MessageReceiver), it will also update the application screen (updateUI) when received.

Here is the MainActivity code for the wear device, i.e. for hours:

 package com.rusdelphi.batterywatcher; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.BatteryManager; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.support.wearable.view.WatchViewStub; import android.widget.TextView; public class MainActivity extends Activity { public static String mWatchLevel = "?", mSmartphoneLevel = "?"; private TextView mWatch; private TextView mSmartphone; MessageReceiver messageReceiver = new MessageReceiver(); private BroadcastReceiver mBatteryLevelReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub); stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() { @Override public void onLayoutInflated(WatchViewStub stub) { mWatch = (TextView) stub.findViewById(R.id.tv_watch); mSmartphone = (TextView) stub.findViewById(R.id.tv_smartphone); updateUI(); } }); IntentFilter batteryLevelFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); mBatteryLevelReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent i) { int level = i.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); int scale = i.getIntExtra(BatteryManager.EXTRA_SCALE, -1); mWatchLevel = new java.text.DecimalFormat("0.0") .format((((float) level / (float) scale) * 100.0f)) + "%"; sendMessage(MainActivity.this, mWatchLevel); updateUI(); } }; registerReceiver(mBatteryLevelReceiver, batteryLevelFilter); IntentFilter messageFilter = new IntentFilter(Intent.ACTION_SEND); LocalBroadcastManager.getInstance(this).registerReceiver(messageReceiver, messageFilter); } public void updateUI() { if (mWatch != null) mWatch.setText(mWatchLevel); if (mSmartphone != null) mSmartphone.setText(mSmartphoneLevel); } public static void sendMessage(Context context, String param1) { Intent intent = new Intent(context, ListenerService.class); intent.setAction(ListenerService.ACTION_SM); intent.putExtra(ListenerService.ACTION_SM_PARAM, param1); context.startService(intent); } @Override protected void onDestroy() { LocalBroadcastManager.getInstance(this).unregisterReceiver(messageReceiver); if (mBatteryLevelReceiver != null) { unregisterReceiver(mBatteryLevelReceiver); mBatteryLevelReceiver = null; } super.onDestroy(); } public class MessageReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String message = intent.getStringExtra("message"); mSmartphoneLevel = message; updateUI(); } } } 

It is, in principle, similar to an older brother, but there are differences in the construction of the markup, since devices can be with square and round screens. The WatchViewStub component allows us to simplify the selection of markup, for this you just need to design 2 rect_activity_main.xml and round_activity_main.xml files.

Here is the code of the first:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" tools:deviceIds="wear_square"> <LinearLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"> <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:layout_width="50dp" android:layout_height="50dp" android:id="@+id/imageView" android:src="@drawable/watch" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tv_watch" android:layout_gravity="bottom" style="@style/TextAppearance.Wearable.Large" /> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp"> <ImageView android:layout_width="50dp" android:layout_height="50dp" android:id="@+id/imageView2" android:src="@drawable/smartphone" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tv_smartphone" android:layout_gravity="bottom" style="@style/TextAppearance.Wearable.Large" /> </LinearLayout> </LinearLayout> </RelativeLayout> 

Here is the second:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" tools:deviceIds="wear_round"> <LinearLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"> <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:layout_width="50dp" android:layout_height="50dp" android:id="@+id/imageView" android:src="@drawable/watch" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tv_watch" android:layout_gravity="bottom" style="@style/TextAppearance.Wearable.Large" /> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp"> <ImageView android:layout_width="50dp" android:layout_height="50dp" android:id="@+id/imageView2" android:src="@drawable/smartphone" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tv_smartphone" android:layout_gravity="bottom" style="@style/TextAppearance.Wearable.Large" /> </LinearLayout> </LinearLayout> </RelativeLayout> 

Here, when creating the markup, we get two TextView components and update their content (updateUI). The rest of the code works the same as in the mobile version. We start the application, get the current battery level, send it to the neighboring device and listen to its messages. How something has changed -> update testimony.

When publishing an application in the Google Market, both modules should have the same version (versionCode) of the code and the same package name. By default, Android Studio will do this work for us. When we collect the apk file for a mobile device, inside it will be apk for wear. This work can be done in Eclipse . In general, as anyone easier. When you install the application from the market, a thick apk will come to your mobile device, which will install apk for the device's wear.

Screenshots from devices:


Source: https://habr.com/ru/post/248479/


All Articles