At the time of writing (January 2017) the Android Wear 2.0 consumer release is approaching. There are some significant changes which may affect existing Wear apps in different ways. Regular readers of Styling Android will know that I have two Android Wear apps published Something O’Clock (which I blogged about here) and Match Timer (which I blogged about here). Both of these apps are affected in different ways and will need updating to work with Android Wear 2.0. We’ll begin by looking at Something O’Clock and look at what changes we need to make in preparation for Wear 2.0.
Something O’Clock is a very simple watch face for Android Wear. I won’t bother going in to the specifics of how it works as the previous posts on the subject already cover that. The watch-face APIs have been augmented in Wear 2.0, but the fundamental behaviour has not changed, so the app itself is already fully Wear 2.0 compatible. However we still have some work to do to make it available on Wear 2.0 devices.
Once big change with Wear 2.0 is that there is now a version of the Google Play app on Wear devices. To enable this there has been a fundamental change to the way that Wear apps are packaged and distributed. Previously the Wear APK was added to a mobile APK at build time, and when this mobile APK was installed on a phone that was paired with a Wear device the Wear APK would get automatically installed on the Wear device. This wasn’t great if you had a purely standalone Wear app (which did not require a companion app on the paired mobile device) but isn’t an issue with Something O’Clock where there is an accompanying mobile app.
With Wear 2.0 the two APKs are each distributed via their own Play Store channel so the way that we package and publish our APKs must change accordingly.
The simple difference is that we now must generate two separate APKs, one for the Wear device and one for the mobile device. These will then get published to the Play Store using multi-APK delivery which enables different APKs to be served to different devices. For this to work we need to have different version numbers for the two APKs to be able to use them for multi-APK delivery.
As well as all of this, we also need to keep things compatible with Wear 1.* if we want to continue to support Wear 1.0.
That sounds like quite a lot to do, but actually it’s relatively simple. We start by creating two distinct build flavours for the Wear APK:
apply plugin: 'com.android.application' android { compileSdkVersion commonCompileSdkVersion buildToolsVersion "${commonBuildToolsVersion}" publishNonDefault true defaultConfig { applicationId "com.stylingandroid.something.oclock" minSdkVersion 20 targetSdkVersion commonTargetSdkVersion versionCode appVersionCode + 1 versionName "${appVersionName}" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } productFlavors { wear1 { } wear2 { minSdkVersion 24 } } } dependencies { compile project(':common') compile "com.google.android.support:wearable:${wearableVersion}" compile "com.google.android.gms:play-services-wearable:${playServicesVersion}" } apply from: '../config/static_analysis.gradle'
We specify a default minSdkVersion
of 20, and this gets overridden in the wear2 flavour.
Also we add one to the common appVersionCode
which we inherit from the build.gradle
of the parent project.
The other thing worth noting is the inclusion of publishNonDefault true
. Without this only a single, default APK will be produced but when we perform a build we want both wear1 and wear2 flavours to be built. Adding this setting will cause both flavours to be built.
Next we need to update the build config of the mobile APK:
apply plugin: 'com.android.application' android { compileSdkVersion commonCompileSdkVersion buildToolsVersion "${commonBuildToolsVersion}" defaultConfig { applicationId "com.stylingandroid.something.oclock" minSdkVersion commonMinSdkVersion targetSdkVersion commonTargetSdkVersion versionCode appVersionCode versionName "${appVersionName}" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } } dependencies { compile project(':common') compile "com.android.support:design:${supportLibraryVersion}" compile "com.android.support:appcompat-v7:${supportLibraryVersion}" compile "com.android.support:recyclerview-v7:${supportLibraryVersion}" compile "com.google.android.gms:play-services-wearable:${playServicesVersion}" wearApp project(path: ':wear', configuration: 'wear1Release' ) } apply from: '../config/static_analysis.gradle'
There’s not very much actually changed here. One thing worth noting is that we do not add one to the version number. This ensures that the Wear APK will have a version number one higher than the mobile APK. It is important to get this the correct way round because Play Store will try to serve up the highest available version number to any device attempting to download an APK. My giving the APK which is minSdkVersion 24
a higher version number than the minSdkVersion 20
one we ensure that devices supporting SKD 24 or later will get the correct APK.
We also need to change what gets included within the mobile app – in this case the output of the wear1 flavour.
if we look at the top level build config file we can see the version number that is being defined:
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } ext { baseApplicationId = 'com.stylingandroid' buildTools = '23.0.3' playServicesVersion = '10.0.1' supportLibraryVersion = '25.1.0' wearableVersion = '2.0.0-beta1' commonBuildToolsVersion = '25.0.0' commonCompileSdkVersion = 25 commonTargetSdkVersion = 25 commonMinSdkVersion = 18 appVersionCode = 101000 appVersionName = "1.1.0" } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
So running this build will create a number of distinct APKs:
I’ve shown the output from my private project containing all of the signing (I’m not publishing my signing keys and keystore to a public GitHub repo!).
There are two separate APKs in the wear project, one for each build flavour; and there is one APK in the mobile project. The wear APKs will have the version code 101001, and the mobile APK will have the version 101000. The mobile APK will also have the wear1
APK embedded within it. It is important to sign both the wear2 APK and mobile APK with the same certificate key, and they must have the same package name. Full details can be found here.
Once these are both uploaded to the Play Developer Console they can both be published:
So, if a Wear 2.0 device attempts to download this APK it will be served the wear2 APK with version code 101001, but if it is downloaded to a mobile device it will get the mobile APK with version code 101000. This mobile APK will also contain the wear1 APK which can be pushed to a paired Wear 1.0 device.
There is just one more thing we need to consider: Does the Wear APK work completely standalone, or does it have a dependency on a companion mobile app? Previously this was not an issue because there was always a companion app, but now the wear APK may be distributed separately from the mobile APK. In the case of Something O’Clock there is a companion app required to configure the watch face, so we need to declare this dependency. This is done by adding some metadata to the wear project manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.stylingandroid.something.oclock"> <uses-feature android:name="android.hardware.type.watch" /> <!-- Required to act as a custom watch face. --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:allowBackup="false" android:fullBackupOnly="false" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@android:style/Theme.DeviceDefault" tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"> <meta-data android:name="com.google.android.wearable.standalone" android:value="false" /> . . . </application> </manifest>
We simply have to declare that this is not a standalone app, and Play Store should do the rest. I have also added an UnusedAttribute warning suppression because lint will warn that the com.google.android.wearable.standalone
attribute is not supported back to minSdkVersion 20.
That’s it, we’re done. We now have a fully compliant Wear 1.0 & Wear 2.0 solution which is published to Play Store.
While Something O’Clock did not require any code changes the same is not true for Match Timer. In a future article we’ll turn our attention to that and see what changes we need to make to get that Wear 2.0 compliant as well.
The source code for this article is available here.
© 2017, Mark Allison. All rights reserved.
Copyright © 2017 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.