Since Crashlytics doesn't work on wearable apps out of the box, I'm looking for an optimal way to intercept and report any potential exception thrown in the runtime. I wonder why they're not being automatically reported to Google Play Developer Console?
Google already announced that the future Android Wear update will have Wi-Fi support built-in, but even then, not every device is going to be equipped with the adequate hardware.
In that case, my initial idea was to create a subclass of Application
and implement Thread.UncaughtExceptionHandler
. Then, every exception would have to be marshalled and sent to a handset, using MessageApi
. An extension of WearableListenerService
on the handset would receive a message, unmarshal the exception and pass it to, for instance, Crashlytics.
However, that raises a few more questions. There's a risk that the Bluetooth connection between wearable and handset is disrupted, so all errors should be queued and stored on the wearable device's file system.
This seems like an overkill for a simple crash report. Is there an easier way to do this?
Don't use MessageApi
for this purpose but DataApi
. Then you don't have to worry about lost bluetooth connection.
The way it works:
when a crash occurs, set a DataItem
with the crash on the wearable;
eventually it will be delivered to the Mobile device.
send the information about the crash from the Mobile and delete the DataItem
.
More information here: http://developer.android.com/training/wearables/data-layer/index.html
Here's a draft of my solution. As @gruszczy suggested, I'm using DataApi
.
Wearable Application:
public class WApplication extends Application
implements Thread.UncaughtExceptionHandler {
private static final String LOG_TAG = WApplication.class.getSimpleName();
private Thread.UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;
...
@Override
public void onCreate() {
super.onCreate();
mDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread thread, final Throwable throwable) {
Log.e(LOG_TAG, "Uncaught exception thrown.");
WearableService.launchService(throwable, WApplication.this);
mDefaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
}
}
Wearable Service:
public class WearableService extends Service {
...
public static void launchService(Throwable throwable, Context context) {
Intent startServiceIntent = new Intent(context, WearableService.class);
startService.putExtra(EXTRA_KEY_EXCEPTION, throwable);
context.startService(startServiceIntent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Throwable throwable = (Throwable) intent.getSerializableExtra(KEY_EXCEPTION);
sendExceptionToMobile(throwable);
return super.onStartCommand(intent, Service.START_REDELIVER_INTENT, startId);
}
private void sendExceptionToMobile(final Throwable throwable) {
if (throwable == null) {
return;
}
Log.d(LOG_TAG, "Sending exception to mobile...");
PutDataMapRequest putDataMapReq = PutDataMapRequest
.create(WearCommunicationConstants.PATH_EXCEPTION);
DataMap dataMap = putDataMapReq.getDataMap();
StringWriter sw = new StringWriter();
throwable.printStackTrace(new PrintWriter(sw));
String stackTrace = sw.toString();
dataMap.putString(WearCommunicationConstants.KEY_STACK_TRACE, stackTrace);
PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
PendingResult<DataApi.DataItemResult> pendingResult =
Wearable.DataApi.putDataItem(mGoogleApiClient, putDataReq);
pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
@Override
public void onResult(final DataApi.DataItemResult result) {
if (result.getStatus().isSuccess()) {
Log.d(LOG_TAG,
"DataItem synced: " + result.getDataItem().getUri());
} else {
Log.e(LOG_TAG,
"Failed to sync DataItem: " + result.getStatus().getStatusCode() + ", "
+ result.getStatus().getStatusMessage());
}
}
});
}
}
Mobile Service:
public class MobileService extends WearableListenerService {
...
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
Log.d(LOG_TAG, "Data changed, data event(s) received.");
for (DataEvent event : dataEvents) {
Log.d(LOG_TAG, "Data event type: " + event.getType());
switch (event.getType()) {
case DataEvent.TYPE_CHANGED:
DataItem item = event.getDataItem();
DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
switch (item.getUri().getPath()) {
case WearCommunicationConstants.PATH_EXCEPTION:
Log.e(LOG_TAG, "Received exception from a wearable device.");
String stackTrace = dataMap
.getString(WearCommunicationConstants.KEY_STACK_TRACE);
Utils.logWithCrashlytics(stackTrace);
break;
// ...
}
break;
case DataEvent.TYPE_DELETED:
// ...
}
}
}
}
Existing solutions require that the phone is currently in range. With Wear 2.0 providing for watch autonomy, we need to be able to store the crashes and send them over once we are connected. WearCrashReporter does exactly this.
We install a crash handler on the watch Virtual Machine. When a crash is caught, its trace and type are serialized to json, saved to the FileSystem, then sent with a service as a MessageApi Message when the phone is available. Upon reception by a WearableListenerService in the phone app it is deserialized and passed to the installed Phone Virtual Machine's crash reporter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With