如何从可穿戴应用程序中检索崩溃报告?

How to retrieve crash reports from a wearable app?

由于 Crashlytics 不适用于开箱即用的可穿戴应用程序,我正在寻找一种最佳方法来拦截和报告运行时抛出的任何潜在异常。我想知道为什么他们没有自动报告给 Google Play Developer Console?

Google 已经宣布未来的 Android Wear 更新将内置 Wi-Fi 支持,但即便如此,并非每台设备都会配备足够的硬件。

在那种情况下,我最初的想法是创建一个Application的子类并实现Thread.UncaughtExceptionHandler。然后,每个异常都必须使用 MessageApi 编组并发送到手机。手机上 WearableListenerService 的分机将收到一条消息,解组异常并将其传递给例如 Crashlytics。

然而,这又提出了几个问题。可穿戴设备和手机之间的蓝牙连接存在中断的风险,因此所有错误都应排队并存储在可穿戴设备的文件系统中。

对于一个简单的崩溃报告来说,这似乎有点矫枉过正。有更简单的方法吗?

不要为此目的使用 MessageApi,而是 DataApi。这样你就不用担心蓝牙连接丢失了。

它的工作方式:

  1. 当发生崩溃时,在可穿戴设备上设置一个DataItem与崩溃;

  2. 最终会传送到移动设备。

  3. 从手机发送崩溃信息并删除DataItem.

这里有更多信息:http://developer.android.com/training/wearables/data-layer/index.html

我通过以下方式解决了这个问题:

this lib 集成到您的项目中。此库会将所有异常从 wear 应用程序传输到移动应用程序。

如果您不使用 proguard - 您可以简单地使用 ExceptionWear 库并将移动应用端的异常记录到 crashlytics 中。

否则

当您在移动应用程序端收到 throwable - 您可以将其登录到 crashlytics,但有一个问题:
如果我们使用 android plugin feature 构建 mobile+wear 应用程序,我们将得到如下内容:

dependencies {
   compile 'com.google.android.gms:play-services:5.0.+@aar'
   ...lots of cool libs...
   wearApp project(':wear')
}

并在两个应用程序(移动和磨损)上应用 crashlytics 插件,然后在构建磨损应用程序期间,您可以看到在 proguard 任务和 dex 任务(gradle 任务)之后,crashlytics 插件不会存储和上传 Deobs 和结果 - stacktraces 未在 crashlytics 仪表板上重新映射(回溯)

:wear:crashlyticsCleanupResourcesRelease//预期 :磨损:crashlyticsUploadStoredDeobsRelease //预期 :wear:crashlyticsGenerateResourcesRelease//预期
:wear:generateReleaseResValues 最新
:wear:generateReleaseResources
:wear:mergeReleaseResources
:wear:processReleaseResources
:wear:generateReleaseSources
:wear:compileReleaseJava
:wear:proguardRelease
:wear:dexRelease//NO crashlytics存储和上传Deobs任务 :wear:processReleaseJavaRes 最新
:wear:shrinkReleaseResources


但是当构建 Wear 应用程序时(Wear ap 就像对移动应用程序的依赖)然后移动应用程序构建开始并且在移动构建过程中 crashlytics 插件运行良好:

:mobile:crashlyticsCleanupResourcesRelease//预期 :移动:crashlyticsUploadStoredDeobsRelease //预期 :mobile:crashlyticsGenerateResourcesRelease//预计
:mobile:generateReleaseResValues 最新
:mobile:generateReleaseResources
:mobile:mergeReleaseResourcesknown
:mobile:processReleaseResources
:mobile:generateReleaseSources
:mobile:compileReleaseJava
:mobile:proguardRelease
:mobile:dexRelease
:mobile:crashlyticsStoreDeobsRelease//预期 :移动:crashlyticsUploadDeobsRelease //预期 :mobile:crashlyticsCleanupResourcesAfterUploadRelease//预计
:移动:lintVitalRelease
:mobile:compileReleaseNdk 最新
:mobile:processReleaseJavaRes 最新
:mobile:shrinkReleaseResources

因此,在标准构建过程中 wear 模块 deobs 未上传,但有解决方法:

if start build wear app separately and then manualy package wear apk in mobile module resources, 然后穿上成功上传的deobs,你可以在仪表盘上观察回溯的崩溃。

但我个人不喜欢 manual way 构建 apk,所以我尝试执行以下操作:
首先只构建 wear 应用程序。 deobs 将上传到 crashlytics。然后 运行 使用 'wearApp project(':wear')' 功能进行完整构建,看起来它有效。

无论如何,我正在等待 android-开箱即用的 crashlytics 支持。

这是我的解决方案草稿。作为 ,我正在使用 DataApi

可穿戴应用程序:

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);
    }

}

穿戴式服务:

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());
                }
            }
        });
    }
}

手机服务:

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:
                    // ...
            }
        }
    }
}

可以在构建过程中上传移动和磨损的deobs。

概念:
1. 确保 mobile 和 wear 具有唯一映射
2. 上传前将磨损映射合并到移动映射

1.配置proquard(通常是proguard-rules.pro)
磨损添加:

-useuniqueclassmembernames

对于移动添加:

-useuniqueclassmembernames
-applymapping ../wear/build/outputs/mapping/release/mapping.txt

此更改通过将可穿戴设备版本的映射应用到移动设备版本,确保您在移动设备和可穿戴设备上拥有唯一的名称。

2.配置构建合并mapping.txt
添加到手机build.gradle:

// allows to use Crashlytics also for wear by merging the mappings of wear into the
// mappings of mobile
//noinspection GroovyAssignabilityCheck
task mergeMappings(dependsOn: "transformClassesAndResourcesWithProguardForRelease") << {
  File wearMappingFile = new File("wear/build/outputs/mapping/release/mapping.txt");
  File mobileMappingFile = new File("mobile/build/outputs/mapping/release/mapping.txt");
  if (wearMappingFile.exists() && mobileMappingFile.exists()) {
    println("merge mapping.txt")
    java.nio.file.Files.copy(wearMappingFile.toPath(),
                             new FileOutputStream(mobileMappingFile, true))
  } // else we are on the wear build and the mobile build was not yet executed
}

afterEvaluate {
  project.("crashlyticsStoreDeobsRelease").dependsOn(mergeMappings);
}

在 crashlyticsStoreDeobsRelease 之前将磨损映射附加到移动映射。

现有解决方案要求 phone 当前在范围内。随着 Wear 2.0 提供手表自主性,我们需要能够存储崩溃并在我们连接后发送它们。 WearCrashReporter 正是这样做的。

我们在手表虚拟机上安装了一个崩溃处理程序。当崩溃被捕获时,其跟踪和类型被序列化为 json,保存到文件系统,然后在 phone 可用时作为 MessageApi 消息与服务一起发送。在 phone 应用程序中的 WearableListenerService 接收后,它被反序列化并传递给安装的 Phone 虚拟机的崩溃报告器。