Отлов крашей приложения и новые особенности Gradle

Искать и исправлять ошибки в приложении нужно на всех стадиях разработки, в том числе и после релиза для поддержания его дальнейшей работы. Однако при переходе на релиз делать это становится сложнее, поскольку краши приложения начинают выглядеть следующим образом:

10-17 07:35:03.149 3542-3542/? E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.NullPointerException
at ru.androidtools.system_app_manager.MainActivity.o(Unknown Source)
at ru.androidtools.system_app_manager.b.b.a(Unknown Source)
at ru.androidtools.system_app_manager.MainActivity.a(Unknown Source)
at ru.androidtools.system_app_manager.a$1.onClick(Unknown Source)
at android.view.View.performClick(View.java:4191)
at android.view.View$PerformClick.run(View.java:17229)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4960)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1038)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805)
at dalvik.system.NativeStart.main(Native Method)

Как видно, ни названий методов из проекта, ни строк, на которых произошла ошибка.

Чтобы сделать логи более информативными, нужно отредактировать файл proguard-rules.pro. ProGuard используется в проекте для того, чтобы удалять неиспользуемые фрагменты кода, а также для затруднения декомпиляции приложения (реверс-инжиниринга). Для того, чтобы оставить в приложении нужные нам данные, нужно воспользоваться атрибутом keepattributes, в котором прописывается фильтр тех данных, которые нужно сохранить. В итоге фильтр будет выглядеть следующим образом:

-keepattributes *Annotation*,SourceFile,LineNumberTable

После добавления этой строки ошибки становятся более информативными и их становится проще отслеживать и исправлять.

10-17 09:10:34.070 3944-3944/? E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.NullPointerException
at ru.androidtools.system_app_manager.MainActivity.o(MainActivity.java:253)
at ru.androidtools.system_app_manager.b.b.a(MainPresenterImpl.java:118)
at ru.androidtools.system_app_manager.MainActivity.a(MainActivity.java:245)
at ru.androidtools.system_app_manager.a$1.onClick(AppsAdapter.java:137)
at android.view.View.performClick(View.java:4191)
at android.view.View$PerformClick.run(View.java:17229)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4960)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1038)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805)
at dalvik.system.NativeStart.main(Native Method)

Кроме того, в новой версии Gradle 3.0.0-rc1, которая сейчас доступна на Android Studio 3.0, были добавлены новые особенности, которые необходимо использовать.

1. Использование flavorDimensions для управления зависимостями, зависящими от версии

Одной из таких особенностей является использование flavorDimensions. Начиная с этой версии включен новый механизм зависимостей, который автоматически соответствует сборкам при использовании библиотек. То есть, сборка debug приложения автоматически будет использовать сборку debug библиотеки и так далее. Он также работает при использовании flavor — сборка redDebug приложения будет использовать сборку redDebug библиотеки. Чтобы всё работало, нужно каждому flavor добавить dimension, содержащий имя, — даже если используете только один вариант. В противном случае получите ошибку:

Error:All flavors must now belong to a named flavor dimension.
The flavor 'flavor_name' is not assigned to a flavor dimension.

Чтобы избежать этой ошибки, присвойте имена каждому flavor, как показано в примере ниже. Поскольку Gradle автоматически сопоставляет зависимости для вас, вы должны тщательно называть свои flavors. Это даст больше контроля над тем, какой код и ресурсы ваших локальных зависимостей сопоставляются с каждой версией вашего приложения.

// Определим два имени flavor.
flavorDimensions "tier", "minApi"

productFlavors {
  free {
    // Назначьте имя для этой ветки. Указание
    // этого свойства необязательно, если вы используете только одну ветку.
    dimension "tier"
    ...
  }

  paid {
    dimension "tier"
    ...
  }

  minApi23 {
    dimension "minApi"
    ...
  }

  minApi18 {
    dimension "minApi"
    ...
  }
}

Однако представьте ситуацию, когда ваше приложение настраивает сборку staging, но одна из его зависимостей этого не делает. Когда плагин пытается создать сборку staging вашего приложения, он не знает, какую версию библиотеки использовать, и вы увидите следующее сообщение:

Error:Failed to resolve: Could not resolve project :mylibrary.
  Required by:
  project :app

Android Studio 3.0.0-beta4 и выше включает в себя элементы DSL, которые помогают контролировать, как плагин должен разрешать ситуации, когда сопоставление между приложением и зависимостью невозможно.

2. Использование новых конфигураций зависимостей

В Gradle 3.4 введены новые конфигурации плагинов библиотеки Java, которые позволяют контролировать, публикуется ли зависимость в compile и runtime классы проектов, которые содержат эту библиотеку. Android plugin 3.0.0 использует эти новые конфигурации зависимостей, а перенос больших проектов на них может значительно сократить время сборки. Следующая таблица поможет вам понять, какие конфигурации вы должны использовать.

Новая конфигурация Устаревшая конфигурация Поведение
implementation compile Когда ваш модуль настраивает зависимость implementation, он дает Gradle знать, что модуль не хочет утечки зависимости к другим модулям во время компиляции. То есть зависимость доступна для других модулей только во время выполнения. Использование этой конфигурации вместо api или compile может привести к значительным улучшениям времени сборки, поскольку это уменьшает количество проектов, которые система сборки должна перекомпилировать. Например, если зависимость implementation изменяет свой API, Gradle перекомпилирует только эту зависимость и модули, которые непосредственно зависят от неё. Большинство приложений и тестовых модулей должны использовать эту конфигурацию.
api compile Когда модуль использует зависимость api, он даёт Gradle знать, что модуль хочет транзитивно экспортировать эту зависимость для других модулей, чтобы он был доступен как во время выполнения, так и во время компиляции. Эта конфигурация ведёт себя точно также, как compile (которая теперь устарела), и вы должны как правило использовать её в библиотечных модулях. Это связано с тем, что если зависимость api изменяет внешний API, то Gradle перекомпилирует все модули, которые имеют доступ к этой зависимости во время компиляции. Таким образом, наличие большого количества зависимостей api может привести к увеличению времени сборки.
compileOnly provided Gradle добавляет зависимость только в classpath компиляции (он не добавляется в собранный билд). Это полезно, когда вы создаете модуль библиотеки Android и вам нужна зависимость только во время компиляции. Это помогает уменьшить размер конечно файла APK, не добавляя временные зависимости, которые не являются критическими. Эта конфигурация ведёт себя так же, как и provided (которая теперь устарела).
runtimeOnly apk Gradle добавляет зависимость только к выходному файлу сборки для использования во время выполнения. Эта конфигурация ведёт себя так же, как и apk (которая теперь устарела).

3. Использование переменных

Напоследок хотелось бы рассказать об использовании переменных в Gradle, хоть это и не является нововведение и используется относительно давно.

В больших проектах как правило используется большое число зависимостей, которые постоянно обновляются и за версиями которых нужно следить. Однако есть способ упростить себе обновление зависимостей, если вынести нужные версии в отдельные переменные и изменять в будущем только их. Например, добавим переменные в build.gradle (модуль app) для версий библиотеки поддержки и сервисов Google Play.

ext {
  supportLibrary = "26.1.0"
  playServices = "11.4.2"
}

Таким образом мы объявили новые переменные. Теперь, чтобы их использовать, их нужно записать вместо соответствующих версий в нужные зависимости следующим образом.

compile "com.android.support:design:${supportLibrary}"

Теперь мы сможем обновлять версии в одном месте, изменяя лишь значение переменной. Согласитесь, это делает работу немного проще.

Нашли ошибку в тексте?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *